@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
|
@@ -0,0 +1,2646 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name2 in all)
|
|
9
|
+
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
|
|
29
|
+
// src/index.ts
|
|
30
|
+
var src_exports = {};
|
|
31
|
+
__export(src_exports, {
|
|
32
|
+
AliasGenerator: () => AliasGenerator,
|
|
33
|
+
Enum: () => Enum,
|
|
34
|
+
ForbiddenError: () => ForbiddenError,
|
|
35
|
+
GraphQLError: () => GraphQLError,
|
|
36
|
+
ID_ALIAS: () => ID_ALIAS,
|
|
37
|
+
MigrationGenerator: () => MigrationGenerator,
|
|
38
|
+
NotFoundError: () => NotFoundError,
|
|
39
|
+
PermissionError: () => PermissionError,
|
|
40
|
+
SPECIAL_FILTERS: () => SPECIAL_FILTERS,
|
|
41
|
+
UserInputError: () => UserInputError,
|
|
42
|
+
actionableRelations: () => actionableRelations,
|
|
43
|
+
addJoin: () => addJoin,
|
|
44
|
+
and: () => and,
|
|
45
|
+
apply: () => apply,
|
|
46
|
+
applyFilters: () => applyFilters,
|
|
47
|
+
applyJoins: () => applyJoins,
|
|
48
|
+
applyPermissions: () => applyPermissions,
|
|
49
|
+
args: () => args,
|
|
50
|
+
checkCanWrite: () => checkCanWrite,
|
|
51
|
+
directive: () => directive,
|
|
52
|
+
directives: () => directives,
|
|
53
|
+
displayField: () => displayField,
|
|
54
|
+
document: () => document,
|
|
55
|
+
enm: () => enm,
|
|
56
|
+
fieldType: () => fieldType,
|
|
57
|
+
fields: () => fields,
|
|
58
|
+
generate: () => generate,
|
|
59
|
+
generateDefinitions: () => generateDefinitions,
|
|
60
|
+
generateMutations: () => generateMutations,
|
|
61
|
+
generatePermissions: () => generatePermissions,
|
|
62
|
+
get: () => get,
|
|
63
|
+
getEditEntityRelationsQuery: () => getEditEntityRelationsQuery,
|
|
64
|
+
getEntityListQuery: () => getEntityListQuery,
|
|
65
|
+
getEntityQuery: () => getEntityQuery,
|
|
66
|
+
getEntityToMutate: () => getEntityToMutate,
|
|
67
|
+
getFindEntityQuery: () => getFindEntityQuery,
|
|
68
|
+
getFragmentSpreads: () => getFragmentSpreads,
|
|
69
|
+
getFragmentTypeName: () => getFragmentTypeName,
|
|
70
|
+
getInlineFragments: () => getInlineFragments,
|
|
71
|
+
getJoins: () => getJoins,
|
|
72
|
+
getLabel: () => getLabel,
|
|
73
|
+
getManyToManyRelation: () => getManyToManyRelation,
|
|
74
|
+
getManyToManyRelations: () => getManyToManyRelations,
|
|
75
|
+
getManyToManyRelationsQuery: () => getManyToManyRelationsQuery,
|
|
76
|
+
getModelLabel: () => getModelLabel,
|
|
77
|
+
getModelLabelPlural: () => getModelLabelPlural,
|
|
78
|
+
getModelPlural: () => getModelPlural,
|
|
79
|
+
getModelPluralField: () => getModelPluralField,
|
|
80
|
+
getModelSlug: () => getModelSlug,
|
|
81
|
+
getModels: () => getModels,
|
|
82
|
+
getMutationQuery: () => getMutationQuery,
|
|
83
|
+
getNameOrAlias: () => getNameOrAlias,
|
|
84
|
+
getPermissionStack: () => getPermissionStack,
|
|
85
|
+
getResolverNode: () => getResolverNode,
|
|
86
|
+
getResolvers: () => getResolvers,
|
|
87
|
+
getRootFieldNode: () => getRootFieldNode,
|
|
88
|
+
getSimpleFields: () => getSimpleFields,
|
|
89
|
+
getString: () => getString,
|
|
90
|
+
getType: () => getType,
|
|
91
|
+
getTypeName: () => getTypeName,
|
|
92
|
+
getUpdateEntityQuery: () => getUpdateEntityQuery,
|
|
93
|
+
gql: () => gql,
|
|
94
|
+
hash: () => hash,
|
|
95
|
+
hydrate: () => hydrate,
|
|
96
|
+
iface: () => iface,
|
|
97
|
+
input: () => input,
|
|
98
|
+
inputValue: () => inputValue,
|
|
99
|
+
inputValues: () => inputValues,
|
|
100
|
+
isCreatable: () => isCreatable,
|
|
101
|
+
isCreatableBy: () => isCreatableBy,
|
|
102
|
+
isEnumList: () => isEnumList,
|
|
103
|
+
isEnumModel: () => isEnumModel,
|
|
104
|
+
isFieldNode: () => isFieldNode,
|
|
105
|
+
isFragmentSpreadNode: () => isFragmentSpreadNode,
|
|
106
|
+
isInlineFragmentNode: () => isInlineFragmentNode,
|
|
107
|
+
isJsonObjectModel: () => isJsonObjectModel,
|
|
108
|
+
isListType: () => isListType,
|
|
109
|
+
isObjectModel: () => isObjectModel,
|
|
110
|
+
isQueriableBy: () => isQueriableBy,
|
|
111
|
+
isQueriableField: () => isQueriableField,
|
|
112
|
+
isRaw: () => isRaw,
|
|
113
|
+
isRawEnumModel: () => isRawEnumModel,
|
|
114
|
+
isRawObjectModel: () => isRawObjectModel,
|
|
115
|
+
isRelation: () => isRelation,
|
|
116
|
+
isScalarModel: () => isScalarModel,
|
|
117
|
+
isSimpleField: () => isSimpleField,
|
|
118
|
+
isToOneRelation: () => isToOneRelation,
|
|
119
|
+
isUpdatable: () => isUpdatable,
|
|
120
|
+
isUpdatableBy: () => isUpdatableBy,
|
|
121
|
+
isVisible: () => isVisible,
|
|
122
|
+
isVisibleRelation: () => isVisibleRelation,
|
|
123
|
+
it: () => it,
|
|
124
|
+
list: () => list,
|
|
125
|
+
merge: () => merge,
|
|
126
|
+
mutationResolver: () => mutationResolver,
|
|
127
|
+
name: () => name,
|
|
128
|
+
namedType: () => namedType,
|
|
129
|
+
nonNull: () => nonNull,
|
|
130
|
+
normalizeArguments: () => normalizeArguments,
|
|
131
|
+
normalizeValue: () => normalizeValue,
|
|
132
|
+
normalizeValueByTypeDefinition: () => normalizeValueByTypeDefinition,
|
|
133
|
+
not: () => not,
|
|
134
|
+
object: () => object,
|
|
135
|
+
ors: () => ors,
|
|
136
|
+
printSchema: () => printSchema,
|
|
137
|
+
printSchemaFromDocument: () => printSchemaFromDocument,
|
|
138
|
+
printSchemaFromModels: () => printSchemaFromModels,
|
|
139
|
+
queryRelations: () => queryRelations,
|
|
140
|
+
queryResolver: () => queryResolver,
|
|
141
|
+
resolve: () => resolve,
|
|
142
|
+
retry: () => retry,
|
|
143
|
+
scalar: () => scalar,
|
|
144
|
+
summon: () => summon,
|
|
145
|
+
summonByKey: () => summonByKey,
|
|
146
|
+
summonByName: () => summonByName,
|
|
147
|
+
typeToField: () => typeToField,
|
|
148
|
+
value: () => value
|
|
149
|
+
});
|
|
150
|
+
module.exports = __toCommonJS(src_exports);
|
|
151
|
+
|
|
152
|
+
// src/client/gql.ts
|
|
153
|
+
var gql = (chunks, ...variables) => {
|
|
154
|
+
return chunks.reduce(
|
|
155
|
+
(accumulator, chunk, index) => `${accumulator}${chunk}${index in variables ? variables[index] : ""}`,
|
|
156
|
+
""
|
|
157
|
+
);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// src/client/queries.ts
|
|
161
|
+
var import_upperFirst = __toESM(require("lodash/upperFirst"), 1);
|
|
162
|
+
|
|
163
|
+
// src/models.ts
|
|
164
|
+
var isObjectModel = (model) => model.type === "object";
|
|
165
|
+
var isEnumModel = (model) => model.type === "enum";
|
|
166
|
+
var isRawEnumModel = (model) => model.type === "raw-enum";
|
|
167
|
+
var isScalarModel = (model) => model.type === "scalar";
|
|
168
|
+
var isRawObjectModel = (model) => model.type === "raw-object";
|
|
169
|
+
var isJsonObjectModel = (model) => model.type === "json-object";
|
|
170
|
+
var isEnumList = (models, field) => field?.list === true && models.find(({ name: name2 }) => name2 === field.type)?.type === "enum";
|
|
171
|
+
var and = (...predicates) => (field) => predicates.every((predicate) => predicate(field));
|
|
172
|
+
var not = (predicate) => (field) => !predicate(field);
|
|
173
|
+
var isRelation = ({ relation }) => !!relation;
|
|
174
|
+
var isVisibleRelation = (visibleRelationsByRole, modelName, role) => {
|
|
175
|
+
const whitelist = visibleRelationsByRole[role]?.[modelName];
|
|
176
|
+
return ({ name: name2 }) => whitelist ? whitelist.includes(name2) : true;
|
|
177
|
+
};
|
|
178
|
+
var isToOneRelation = ({ toOne }) => !!toOne;
|
|
179
|
+
var isQueriableField = ({ queriable }) => queriable !== false;
|
|
180
|
+
var isRaw = ({ raw }) => !!raw;
|
|
181
|
+
var isVisible = ({ hidden }) => hidden !== true;
|
|
182
|
+
var isSimpleField = and(not(isRelation), not(isRaw));
|
|
183
|
+
var isUpdatable = ({ updatable }) => !!updatable;
|
|
184
|
+
var isCreatable = ({ creatable }) => !!creatable;
|
|
185
|
+
var isQueriableBy = (role) => (field) => isQueriableField(field) && (!field.queriableBy || field.queriableBy.includes(role));
|
|
186
|
+
var isUpdatableBy = (role) => (field) => isUpdatable(field) && (!field.updatableBy || field.updatableBy.includes(role));
|
|
187
|
+
var isCreatableBy = (role) => (field) => isCreatable(field) && (!field.creatableBy || field.creatableBy.includes(role));
|
|
188
|
+
var actionableRelations = (model, action) => model.fields.filter(
|
|
189
|
+
({ relation, ...field }) => relation && field[`${action === "filter" ? action : action.slice(0, -1)}able`]
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
// src/utils.ts
|
|
193
|
+
var import_assert = __toESM(require("assert"), 1);
|
|
194
|
+
var import_inflection = require("inflection");
|
|
195
|
+
var import_camelCase = __toESM(require("lodash/camelCase"), 1);
|
|
196
|
+
var import_get = __toESM(require("lodash/get"), 1);
|
|
197
|
+
var import_kebabCase = __toESM(require("lodash/kebabCase"), 1);
|
|
198
|
+
var import_startCase = __toESM(require("lodash/startCase"), 1);
|
|
199
|
+
var isNotFalsy = (v) => typeof v !== "undefined" && v !== null && v !== false;
|
|
200
|
+
var merge = (objects) => (objects || []).filter(isNotFalsy).reduce((i, acc) => ({ ...acc, ...i }), {});
|
|
201
|
+
var typeToField = (type) => type.substr(0, 1).toLowerCase() + type.substr(1);
|
|
202
|
+
var getModelPlural = (model) => model.plural || (0, import_inflection.pluralize)(model.name);
|
|
203
|
+
var getModelPluralField = (model) => typeToField(getModelPlural(model));
|
|
204
|
+
var getModelSlug = (model) => (0, import_kebabCase.default)(getModelPlural(model));
|
|
205
|
+
var getModelLabelPlural = (model) => getLabel(getModelPlural(model));
|
|
206
|
+
var getModelLabel = (model) => getLabel(model.name);
|
|
207
|
+
var getLabel = (s) => (0, import_startCase.default)((0, import_camelCase.default)(s));
|
|
208
|
+
var getModels = (rawModels) => {
|
|
209
|
+
const models = rawModels.filter(isObjectModel).map((model) => {
|
|
210
|
+
const objectModel = {
|
|
211
|
+
...model,
|
|
212
|
+
fieldsByName: {},
|
|
213
|
+
relations: [],
|
|
214
|
+
relationsByName: {},
|
|
215
|
+
reverseRelations: [],
|
|
216
|
+
reverseRelationsByName: {},
|
|
217
|
+
fields: [
|
|
218
|
+
{ name: "id", type: "ID", nonNull: true, unique: true, primary: true, generated: true },
|
|
219
|
+
...model.fields,
|
|
220
|
+
...model.creatable ? [
|
|
221
|
+
{ name: "createdAt", type: "DateTime", nonNull: !model.nonStrict, orderable: true, generated: true },
|
|
222
|
+
{
|
|
223
|
+
name: "createdBy",
|
|
224
|
+
type: "User",
|
|
225
|
+
relation: true,
|
|
226
|
+
nonNull: !model.nonStrict,
|
|
227
|
+
reverse: `created${getModelPlural(model)}`,
|
|
228
|
+
generated: true
|
|
229
|
+
}
|
|
230
|
+
] : [],
|
|
231
|
+
...model.updatable ? [
|
|
232
|
+
{ name: "updatedAt", type: "DateTime", nonNull: !model.nonStrict, orderable: true, generated: true },
|
|
233
|
+
{
|
|
234
|
+
name: "updatedBy",
|
|
235
|
+
type: "User",
|
|
236
|
+
relation: true,
|
|
237
|
+
nonNull: !model.nonStrict,
|
|
238
|
+
reverse: `updated${getModelPlural(model)}`,
|
|
239
|
+
generated: true
|
|
240
|
+
}
|
|
241
|
+
] : [],
|
|
242
|
+
...model.deletable ? [
|
|
243
|
+
{
|
|
244
|
+
name: "deleted",
|
|
245
|
+
type: "Boolean",
|
|
246
|
+
nonNull: true,
|
|
247
|
+
default: false,
|
|
248
|
+
filterable: true,
|
|
249
|
+
defaultFilter: false,
|
|
250
|
+
generated: true
|
|
251
|
+
},
|
|
252
|
+
{ name: "deletedAt", type: "DateTime", orderable: true, generated: true },
|
|
253
|
+
{
|
|
254
|
+
name: "deletedBy",
|
|
255
|
+
type: "User",
|
|
256
|
+
relation: true,
|
|
257
|
+
reverse: `deleted${getModelPlural(model)}`,
|
|
258
|
+
generated: true
|
|
259
|
+
}
|
|
260
|
+
] : []
|
|
261
|
+
].map(({ foreignKey, ...field }) => ({
|
|
262
|
+
...field,
|
|
263
|
+
...field.relation && {
|
|
264
|
+
foreignKey: foreignKey || `${field.name}Id`
|
|
265
|
+
}
|
|
266
|
+
}))
|
|
267
|
+
};
|
|
268
|
+
for (const field of objectModel.fields) {
|
|
269
|
+
objectModel.fieldsByName[field.name] = field;
|
|
270
|
+
}
|
|
271
|
+
return objectModel;
|
|
272
|
+
});
|
|
273
|
+
for (const model of models) {
|
|
274
|
+
for (const field of model.fields.filter(({ relation }) => relation)) {
|
|
275
|
+
const fieldModel = summonByName(models, field.type);
|
|
276
|
+
const reverseRelation = {
|
|
277
|
+
name: field.reverse || (field.toOne ? typeToField(model.name) : getModelPluralField(model)),
|
|
278
|
+
foreignKey: get(field, "foreignKey"),
|
|
279
|
+
type: model.name,
|
|
280
|
+
toOne: !!field.toOne,
|
|
281
|
+
fieldModel,
|
|
282
|
+
field,
|
|
283
|
+
model
|
|
284
|
+
};
|
|
285
|
+
const relation = {
|
|
286
|
+
field,
|
|
287
|
+
model: fieldModel,
|
|
288
|
+
reverseRelation
|
|
289
|
+
};
|
|
290
|
+
model.relations.push(relation);
|
|
291
|
+
model.relationsByName[relation.field.name] = relation;
|
|
292
|
+
fieldModel.reverseRelations.push(reverseRelation);
|
|
293
|
+
fieldModel.reverseRelationsByName[reverseRelation.name] = reverseRelation;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return models;
|
|
297
|
+
};
|
|
298
|
+
var summonByName = (array, value2) => summonByKey(array, "name", value2);
|
|
299
|
+
var summonByKey = (array, key, value2) => summon(array, (element) => (0, import_get.default)(element, key) === value2, `No element found with ${key} ${value2}`);
|
|
300
|
+
var summon = (array, cb, errorMessage) => {
|
|
301
|
+
if (array === void 0) {
|
|
302
|
+
throw new Error("Base array is not defined.");
|
|
303
|
+
}
|
|
304
|
+
const result = array.find(cb);
|
|
305
|
+
if (result === void 0) {
|
|
306
|
+
console.trace();
|
|
307
|
+
throw new Error(errorMessage || "Element not found.");
|
|
308
|
+
}
|
|
309
|
+
return result;
|
|
310
|
+
};
|
|
311
|
+
var it = (object2) => {
|
|
312
|
+
if (object2 === void 0 || object2 === null) {
|
|
313
|
+
console.trace();
|
|
314
|
+
throw new Error("Base object is not defined.");
|
|
315
|
+
}
|
|
316
|
+
return object2;
|
|
317
|
+
};
|
|
318
|
+
var get = (object2, key) => {
|
|
319
|
+
const value2 = it(object2)[key];
|
|
320
|
+
if (value2 === void 0 || value2 === null) {
|
|
321
|
+
console.trace();
|
|
322
|
+
throw new Error(`Object doesn't have ${String(key)}`);
|
|
323
|
+
}
|
|
324
|
+
return value2;
|
|
325
|
+
};
|
|
326
|
+
var getString = (v) => {
|
|
327
|
+
(0, import_assert.default)(typeof v === "string");
|
|
328
|
+
return v;
|
|
329
|
+
};
|
|
330
|
+
var retry = async (cb, condition) => {
|
|
331
|
+
try {
|
|
332
|
+
return await cb();
|
|
333
|
+
} catch (e) {
|
|
334
|
+
if (condition(e)) {
|
|
335
|
+
return await cb();
|
|
336
|
+
} else {
|
|
337
|
+
throw e;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
// src/client/queries.ts
|
|
343
|
+
var getUpdateEntityQuery = (model, role, fields2, additionalFields = "") => `query Update${model.name}Fields ($id: ID!) {
|
|
344
|
+
data: ${typeToField(model.name)}(where: { id: $id }) {
|
|
345
|
+
id
|
|
346
|
+
${model.fields.filter(({ name: name2 }) => !fields2 || fields2.includes(name2)).filter(not(isRelation)).filter(isUpdatableBy(role)).map(({ name: name2 }) => name2).join(" ")}
|
|
347
|
+
${actionableRelations(model, "update").filter(({ name: name2 }) => !fields2 || fields2.includes(name2)).map(({ name: name2 }) => `${name2} { id }`)}
|
|
348
|
+
${additionalFields}
|
|
349
|
+
}
|
|
350
|
+
}`;
|
|
351
|
+
var getEditEntityRelationsQuery = (models, model, action, fields2, ignoreFields, additionalFields = {}) => {
|
|
352
|
+
const relations = actionableRelations(model, action).filter(
|
|
353
|
+
({ name: name2 }) => (!fields2 || fields2.includes(name2)) && (!ignoreFields || !ignoreFields.includes(name2))
|
|
354
|
+
);
|
|
355
|
+
return !!relations.length && `query ${(0, import_upperFirst.default)(action)}${model.name}Relations {
|
|
356
|
+
${relations.map(({ name: name2, type }) => {
|
|
357
|
+
const model2 = summonByName(models, type);
|
|
358
|
+
return `${name2}: ${getModelPluralField(model2)} {
|
|
359
|
+
id
|
|
360
|
+
display: ${model2.displayField || ""}
|
|
361
|
+
${additionalFields[name2] || ""}
|
|
362
|
+
}`;
|
|
363
|
+
}).join(" ")}
|
|
364
|
+
}`;
|
|
365
|
+
};
|
|
366
|
+
var getManyToManyRelations = (model, fields2, ignoreFields) => {
|
|
367
|
+
const manyToManyRelations = [];
|
|
368
|
+
for (const field of model.reverseRelations) {
|
|
369
|
+
if (fields2 && !fields2.includes(field.name) || ignoreFields && ignoreFields.includes(field.name)) {
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
const relation = field.model.relations.find(
|
|
373
|
+
(relation2) => !relation2.field.generated && relation2.field.name !== field.field.name
|
|
374
|
+
);
|
|
375
|
+
if (!relation) {
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
const inapplicableFields = field.model.fields.filter(
|
|
379
|
+
(otherField) => !otherField.generated && ![field.field.name, relation.field.name].includes(otherField.name)
|
|
380
|
+
);
|
|
381
|
+
if (inapplicableFields.length) {
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
manyToManyRelations.push([field, relation]);
|
|
385
|
+
}
|
|
386
|
+
return manyToManyRelations;
|
|
387
|
+
};
|
|
388
|
+
var getManyToManyRelation = (model, name2) => getManyToManyRelations(model, [name2])[0];
|
|
389
|
+
var getManyToManyRelationsQuery = (model, action, manyToManyRelations) => !!manyToManyRelations.length && (action === "update" ? `query Update${model.name}ManyToManyRelations($id: ID!) {
|
|
390
|
+
${typeToField(model.name)}(where: { id: $id }) {
|
|
391
|
+
${manyToManyRelations.map(([reverseRelation, { field }]) => {
|
|
392
|
+
return `${reverseRelation.name} {
|
|
393
|
+
id
|
|
394
|
+
${field.name} {
|
|
395
|
+
id
|
|
396
|
+
}
|
|
397
|
+
}`;
|
|
398
|
+
}).join(" ")}
|
|
399
|
+
}
|
|
400
|
+
${manyToManyRelations.map(([reverseRelation, { model: model2 }]) => {
|
|
401
|
+
return `${reverseRelation.name}: ${getModelPluralField(model2)} {
|
|
402
|
+
id
|
|
403
|
+
${model2.displayField || ""}
|
|
404
|
+
}`;
|
|
405
|
+
}).join(" ")}
|
|
406
|
+
}` : `query Create${model.name}ManyToManyRelations {
|
|
407
|
+
${manyToManyRelations.map(([reverseRelation, { model: model2 }]) => {
|
|
408
|
+
return `${reverseRelation.name}: ${getModelPluralField(model2)} {
|
|
409
|
+
id
|
|
410
|
+
${model2.displayField || ""}
|
|
411
|
+
}`;
|
|
412
|
+
}).join(" ")}
|
|
413
|
+
}`);
|
|
414
|
+
var getMutationQuery = (model, action) => action === "create" ? `
|
|
415
|
+
mutation Create${model.name} ($data: Create${model.name}!) {
|
|
416
|
+
mutated: create${model.name}(data: $data) {
|
|
417
|
+
id
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
` : action === "update" ? `
|
|
421
|
+
mutation Update${model.name} ($id: ID!, $data: Update${model.name}!) {
|
|
422
|
+
mutated: update${model.name}(where: { id: $id } data: $data) {
|
|
423
|
+
id
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
` : `
|
|
427
|
+
mutation Delete${model.name} ($id: ID!) {
|
|
428
|
+
mutated: delete${model.name}(where: { id: $id }) {
|
|
429
|
+
id
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
`;
|
|
433
|
+
var displayField = (model) => `
|
|
434
|
+
${model.displayField ? `display: ${model.displayField}` : ""}
|
|
435
|
+
`;
|
|
436
|
+
var getEntityListQuery = (model, role, additionalFields = "", root) => `query ${getModelPlural(model)}List(
|
|
437
|
+
${root ? "$id: ID!," : ""}
|
|
438
|
+
$limit: Int!,
|
|
439
|
+
$where: ${model.name}Where!,
|
|
440
|
+
${model.fields.some(({ searchable }) => searchable) ? "$search: String," : ""}
|
|
441
|
+
) {
|
|
442
|
+
${root ? `root: ${typeToField(root.model.name)}(where: { id: $id }) {` : ""}
|
|
443
|
+
data: ${root ? root.reverseRelationName : getModelPluralField(model)}(limit: $limit, where: $where, ${model.fields.some(({ searchable }) => searchable) ? ", search: $search" : ""}) {
|
|
444
|
+
${displayField(model)}
|
|
445
|
+
${model.fields.filter(and(isSimpleField, isQueriableBy(role))).map(({ name: name2 }) => name2)}
|
|
446
|
+
${additionalFields}
|
|
447
|
+
}
|
|
448
|
+
${root ? "}" : ""}
|
|
449
|
+
}`;
|
|
450
|
+
var getEntityQuery = (models, model, role, visibleRelationsByRole, typesWithSubRelations) => `query Admin${model.name} ($id: ID!) {
|
|
451
|
+
data: ${typeToField(model.name)}(where: { id: $id }) {
|
|
452
|
+
${displayField(model)}
|
|
453
|
+
${model.fields.filter(and(isSimpleField, isQueriableBy(role))).map(({ name: name2 }) => name2)}
|
|
454
|
+
${queryRelations(
|
|
455
|
+
models,
|
|
456
|
+
model.fields.filter(and(isRelation, isVisibleRelation(visibleRelationsByRole, model.name, role))),
|
|
457
|
+
role,
|
|
458
|
+
typesWithSubRelations
|
|
459
|
+
)}
|
|
460
|
+
${queryRelations(
|
|
461
|
+
models,
|
|
462
|
+
model.reverseRelations.filter(and(isToOneRelation, isVisibleRelation(visibleRelationsByRole, model.name, role))),
|
|
463
|
+
role,
|
|
464
|
+
typesWithSubRelations
|
|
465
|
+
)}
|
|
466
|
+
}
|
|
467
|
+
}`;
|
|
468
|
+
var getFindEntityQuery = (model, role) => `query Find${model.name}($where: ${model.name}Where!, $orderBy: [${model.name}OrderBy!]) {
|
|
469
|
+
data: ${getModelPluralField(model)}(limit: 1, where: $where, orderBy: $orderBy) {
|
|
470
|
+
${model.fields.filter(and(isSimpleField, isQueriableBy(role))).map(({ name: name2 }) => name2)}
|
|
471
|
+
}
|
|
472
|
+
}`;
|
|
473
|
+
var queryRelations = (models, relations, role, typesWithSubRelations) => relations.map(({ name: name2, type }) => {
|
|
474
|
+
const relatedModel = summonByName(models, type);
|
|
475
|
+
const subRelations = typesWithSubRelations.includes(type) ? relatedModel.fields.filter(isRelation) : [];
|
|
476
|
+
return `${name2} {
|
|
477
|
+
id
|
|
478
|
+
${displayField(relatedModel)}
|
|
479
|
+
${subRelations.length > 0 ? queryRelations(models, subRelations, role, typesWithSubRelations) : ""}
|
|
480
|
+
}`;
|
|
481
|
+
}).join("\n");
|
|
482
|
+
|
|
483
|
+
// src/generate/generate.ts
|
|
484
|
+
var import_graphql = require("graphql");
|
|
485
|
+
var import_flatMap = __toESM(require("lodash/flatMap"), 1);
|
|
486
|
+
|
|
487
|
+
// src/generate/utils.ts
|
|
488
|
+
var import_luxon = require("luxon");
|
|
489
|
+
|
|
490
|
+
// src/values.ts
|
|
491
|
+
var Enum = class {
|
|
492
|
+
constructor(value2) {
|
|
493
|
+
this.value = value2;
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
// src/generate/utils.ts
|
|
498
|
+
var document = (definitions) => ({
|
|
499
|
+
kind: "Document",
|
|
500
|
+
definitions
|
|
501
|
+
});
|
|
502
|
+
var directive = (nme, locations, fields2) => ({
|
|
503
|
+
name: name(nme),
|
|
504
|
+
repeatable: false,
|
|
505
|
+
kind: "DirectiveDefinition",
|
|
506
|
+
arguments: fields2 && inputValues(fields2),
|
|
507
|
+
locations: locations.map(name)
|
|
508
|
+
});
|
|
509
|
+
var scalar = (nme) => ({
|
|
510
|
+
name: name(nme),
|
|
511
|
+
kind: "ScalarTypeDefinition"
|
|
512
|
+
});
|
|
513
|
+
var input = (nme, fields2, dvs) => ({
|
|
514
|
+
name: name(nme),
|
|
515
|
+
fields: inputValues(fields2),
|
|
516
|
+
kind: "InputObjectTypeDefinition",
|
|
517
|
+
directives: directives(dvs)
|
|
518
|
+
});
|
|
519
|
+
var object = (nme, fds, interfaces, dvs) => ({
|
|
520
|
+
name: name(nme),
|
|
521
|
+
fields: fields(fds),
|
|
522
|
+
kind: "ObjectTypeDefinition",
|
|
523
|
+
interfaces: interfaces && interfaces.map((i) => namedType(i)),
|
|
524
|
+
directives: directives(dvs)
|
|
525
|
+
});
|
|
526
|
+
var iface = (nme, fds, dvs) => ({
|
|
527
|
+
name: name(nme),
|
|
528
|
+
fields: fields(fds),
|
|
529
|
+
kind: "InterfaceTypeDefinition",
|
|
530
|
+
directives: directives(dvs)
|
|
531
|
+
});
|
|
532
|
+
var inputValues = (fields2) => fields2.map(
|
|
533
|
+
(field) => ({
|
|
534
|
+
kind: "InputValueDefinition",
|
|
535
|
+
name: name(field.name),
|
|
536
|
+
type: fieldType(field),
|
|
537
|
+
defaultValue: field.default === void 0 ? void 0 : value(field.default),
|
|
538
|
+
directives: directives(field.directives)
|
|
539
|
+
})
|
|
540
|
+
);
|
|
541
|
+
var fields = (fields2) => fields2.map(
|
|
542
|
+
(field) => ({
|
|
543
|
+
kind: "FieldDefinition",
|
|
544
|
+
name: name(field.name),
|
|
545
|
+
type: fieldType(field),
|
|
546
|
+
arguments: field.args?.map((arg) => ({
|
|
547
|
+
kind: "InputValueDefinition",
|
|
548
|
+
name: name(arg.name),
|
|
549
|
+
type: fieldType(arg),
|
|
550
|
+
defaultValue: arg.default === void 0 ? void 0 : value(arg.default)
|
|
551
|
+
})),
|
|
552
|
+
directives: directives(field.directives)
|
|
553
|
+
})
|
|
554
|
+
);
|
|
555
|
+
var inputValue = (nme, type, dvs, description, defaultValue) => ({
|
|
556
|
+
name: name(nme),
|
|
557
|
+
type,
|
|
558
|
+
kind: "InputValueDefinition",
|
|
559
|
+
directives: directives(dvs),
|
|
560
|
+
defaultValue: defaultValue ? value(defaultValue) : void 0,
|
|
561
|
+
description: description ? value(description) : void 0
|
|
562
|
+
});
|
|
563
|
+
var directives = (directives2) => directives2?.map(
|
|
564
|
+
(directive2) => ({
|
|
565
|
+
kind: "Directive",
|
|
566
|
+
name: name(directive2.name),
|
|
567
|
+
arguments: args(directive2.values)
|
|
568
|
+
})
|
|
569
|
+
);
|
|
570
|
+
var args = (ags) => ags?.map(
|
|
571
|
+
(argument) => ({
|
|
572
|
+
kind: "Argument",
|
|
573
|
+
name: name(argument.name),
|
|
574
|
+
value: value(argument.values)
|
|
575
|
+
})
|
|
576
|
+
);
|
|
577
|
+
var enm = (nme, values) => ({
|
|
578
|
+
name: name(nme),
|
|
579
|
+
kind: "EnumTypeDefinition",
|
|
580
|
+
values: values.map(
|
|
581
|
+
(v) => ({
|
|
582
|
+
kind: "EnumValueDefinition",
|
|
583
|
+
name: name(v)
|
|
584
|
+
})
|
|
585
|
+
)
|
|
586
|
+
});
|
|
587
|
+
var nonNull = (type) => ({
|
|
588
|
+
type,
|
|
589
|
+
kind: "NonNullType"
|
|
590
|
+
});
|
|
591
|
+
var namedType = (nme) => ({
|
|
592
|
+
kind: "NamedType",
|
|
593
|
+
name: name(nme)
|
|
594
|
+
});
|
|
595
|
+
var list = (type) => ({
|
|
596
|
+
type,
|
|
597
|
+
kind: "ListType"
|
|
598
|
+
});
|
|
599
|
+
var name = (name2) => ({
|
|
600
|
+
kind: "Name",
|
|
601
|
+
value: name2
|
|
602
|
+
});
|
|
603
|
+
var fieldType = (field) => {
|
|
604
|
+
let type = namedType(field.type);
|
|
605
|
+
if (field.list) {
|
|
606
|
+
type = list(nonNull(type));
|
|
607
|
+
}
|
|
608
|
+
if (field.nonNull) {
|
|
609
|
+
type = nonNull(type);
|
|
610
|
+
}
|
|
611
|
+
return type;
|
|
612
|
+
};
|
|
613
|
+
var value = (val = null) => val === null ? {
|
|
614
|
+
kind: "NullValue"
|
|
615
|
+
} : typeof val === "boolean" ? {
|
|
616
|
+
kind: "BooleanValue",
|
|
617
|
+
value: val
|
|
618
|
+
} : typeof val === "number" ? {
|
|
619
|
+
kind: "IntValue",
|
|
620
|
+
value: `${val}`
|
|
621
|
+
} : typeof val === "string" ? {
|
|
622
|
+
kind: "StringValue",
|
|
623
|
+
value: val
|
|
624
|
+
} : val instanceof Enum ? {
|
|
625
|
+
kind: "EnumValue",
|
|
626
|
+
value: val.value
|
|
627
|
+
} : Array.isArray(val) ? {
|
|
628
|
+
kind: "ListValue",
|
|
629
|
+
values: val.map(value)
|
|
630
|
+
} : val instanceof import_luxon.DateTime ? {
|
|
631
|
+
kind: "StringValue",
|
|
632
|
+
value: val.toString()
|
|
633
|
+
} : {
|
|
634
|
+
kind: "ObjectValue",
|
|
635
|
+
fields: Object.keys(val).map(
|
|
636
|
+
(nme) => ({
|
|
637
|
+
kind: "ObjectField",
|
|
638
|
+
name: name(nme),
|
|
639
|
+
value: value(val[nme])
|
|
640
|
+
})
|
|
641
|
+
)
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
// src/generate/generate.ts
|
|
645
|
+
var generateDefinitions = (rawModels) => {
|
|
646
|
+
const models = getModels(rawModels);
|
|
647
|
+
return [
|
|
648
|
+
// Predefined types
|
|
649
|
+
enm("Order", ["ASC", "DESC"]),
|
|
650
|
+
scalar("DateTime"),
|
|
651
|
+
scalar("Upload"),
|
|
652
|
+
...rawModels.filter(isEnumModel).map((model) => enm(model.name, model.values)),
|
|
653
|
+
...rawModels.filter(isRawEnumModel).map((model) => enm(model.name, model.values)),
|
|
654
|
+
...rawModels.filter(isScalarModel).map((model) => scalar(model.name)),
|
|
655
|
+
...rawModels.filter(isRawObjectModel).map((model) => object(model.name, model.fields)),
|
|
656
|
+
...rawModels.filter(isJsonObjectModel).map((model) => object(model.name, model.fields)),
|
|
657
|
+
...rawModels.filter(isRawObjectModel).filter(({ rawFilters }) => rawFilters).map(
|
|
658
|
+
(model) => input(
|
|
659
|
+
`${model.name}Where`,
|
|
660
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- array gets filtered above to only include models with rawFilters
|
|
661
|
+
model.rawFilters.map(({ name: name2, type, list: list2 = false, nonNull: nonNull2 = false }) => ({ name: name2, type, list: list2, nonNull: nonNull2 }))
|
|
662
|
+
)
|
|
663
|
+
),
|
|
664
|
+
...(0, import_flatMap.default)(
|
|
665
|
+
models.map((model) => {
|
|
666
|
+
const types = [
|
|
667
|
+
object(
|
|
668
|
+
model.name,
|
|
669
|
+
[
|
|
670
|
+
...model.fields.filter(isQueriableField).map((field) => ({
|
|
671
|
+
...field,
|
|
672
|
+
args: [
|
|
673
|
+
...field.args || [],
|
|
674
|
+
...hasRawFilters(rawModels, field.type) ? [{ name: "where", type: `${field.type}Where` }] : []
|
|
675
|
+
],
|
|
676
|
+
directives: field.directives
|
|
677
|
+
})),
|
|
678
|
+
...model.reverseRelations.map(({ name: name2, field, model: model2 }) => ({
|
|
679
|
+
name: name2,
|
|
680
|
+
type: model2.name,
|
|
681
|
+
list: !field.toOne,
|
|
682
|
+
nonNull: !field.toOne,
|
|
683
|
+
args: [
|
|
684
|
+
{ name: "where", type: `${model2.name}Where` },
|
|
685
|
+
...model2.fields.some(({ searchable }) => searchable) ? [{ name: "search", type: "String" }] : [],
|
|
686
|
+
...model2.fields.some(({ orderable }) => orderable) ? [{ name: "orderBy", type: `${model2.name}OrderBy`, list: true }] : [],
|
|
687
|
+
{ name: "limit", type: "Int" },
|
|
688
|
+
{ name: "offset", type: "Int" }
|
|
689
|
+
]
|
|
690
|
+
}))
|
|
691
|
+
],
|
|
692
|
+
model.interfaces
|
|
693
|
+
),
|
|
694
|
+
input(`${model.name}Where`, [
|
|
695
|
+
...model.fields.filter(({ unique, filterable, relation }) => (unique || filterable) && !relation).map(({ name: name2, type, defaultFilter }) => ({ name: name2, type, list: true, default: defaultFilter })),
|
|
696
|
+
...(0, import_flatMap.default)(
|
|
697
|
+
model.fields.filter(({ comparable }) => comparable),
|
|
698
|
+
({ name: name2, type }) => [
|
|
699
|
+
{ name: `${name2}_GT`, type },
|
|
700
|
+
{ name: `${name2}_GTE`, type },
|
|
701
|
+
{ name: `${name2}_LT`, type },
|
|
702
|
+
{ name: `${name2}_LTE`, type }
|
|
703
|
+
]
|
|
704
|
+
),
|
|
705
|
+
...model.fields.filter(({ filterable, relation }) => filterable && relation).map(({ name: name2, type }) => ({
|
|
706
|
+
name: name2,
|
|
707
|
+
type: `${type}Where`
|
|
708
|
+
}))
|
|
709
|
+
]),
|
|
710
|
+
input(
|
|
711
|
+
`${model.name}WhereUnique`,
|
|
712
|
+
model.fields.filter(({ unique }) => unique).map(({ name: name2, type }) => ({ name: name2, type }))
|
|
713
|
+
),
|
|
714
|
+
...model.fields.some(({ orderable }) => orderable) ? [
|
|
715
|
+
input(
|
|
716
|
+
`${model.name}OrderBy`,
|
|
717
|
+
model.fields.filter(({ orderable }) => orderable).map(({ name: name2 }) => ({ name: name2, type: "Order" }))
|
|
718
|
+
)
|
|
719
|
+
] : []
|
|
720
|
+
];
|
|
721
|
+
if (model.creatable) {
|
|
722
|
+
types.push(
|
|
723
|
+
input(
|
|
724
|
+
`Create${model.name}`,
|
|
725
|
+
model.fields.filter(({ creatable }) => creatable).map(
|
|
726
|
+
({ name: name2, relation, type, nonNull: nonNull2, list: list2, default: defaultValue }) => relation ? { name: `${name2}Id`, type: "ID", nonNull: nonNull2 } : { name: name2, type, list: list2, nonNull: nonNull2 && defaultValue === void 0 }
|
|
727
|
+
)
|
|
728
|
+
)
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
if (model.updatable) {
|
|
732
|
+
types.push(
|
|
733
|
+
input(
|
|
734
|
+
`Update${model.name}`,
|
|
735
|
+
model.fields.filter(({ updatable }) => updatable).map(
|
|
736
|
+
({ name: name2, relation, type, list: list2 }) => relation ? { name: `${name2}Id`, type: "ID" } : { name: name2, type, list: list2 }
|
|
737
|
+
)
|
|
738
|
+
)
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
return types;
|
|
742
|
+
})
|
|
743
|
+
),
|
|
744
|
+
object("Query", [
|
|
745
|
+
{
|
|
746
|
+
name: "me",
|
|
747
|
+
type: "User"
|
|
748
|
+
},
|
|
749
|
+
...models.filter(({ queriable }) => queriable).map(({ name: name2 }) => ({
|
|
750
|
+
name: typeToField(name2),
|
|
751
|
+
type: name2,
|
|
752
|
+
nonNull: true,
|
|
753
|
+
args: [
|
|
754
|
+
{
|
|
755
|
+
name: "where",
|
|
756
|
+
type: `${name2}WhereUnique`,
|
|
757
|
+
nonNull: true
|
|
758
|
+
}
|
|
759
|
+
]
|
|
760
|
+
})),
|
|
761
|
+
...models.filter(({ listQueriable }) => listQueriable).map((model) => ({
|
|
762
|
+
name: getModelPluralField(model),
|
|
763
|
+
type: model.name,
|
|
764
|
+
list: true,
|
|
765
|
+
nonNull: true,
|
|
766
|
+
args: [
|
|
767
|
+
{ name: "where", type: `${model.name}Where` },
|
|
768
|
+
...model.fields.some(({ searchable }) => searchable) ? [{ name: "search", type: "String" }] : [],
|
|
769
|
+
...model.fields.some(({ orderable }) => orderable) ? [{ name: "orderBy", type: `${model.name}OrderBy`, list: true }] : [],
|
|
770
|
+
{ name: "limit", type: "Int" },
|
|
771
|
+
{ name: "offset", type: "Int" }
|
|
772
|
+
]
|
|
773
|
+
}))
|
|
774
|
+
]),
|
|
775
|
+
object("Mutation", [
|
|
776
|
+
...(0, import_flatMap.default)(
|
|
777
|
+
models.map((model) => {
|
|
778
|
+
const mutations = [];
|
|
779
|
+
if (model.creatable) {
|
|
780
|
+
mutations.push({
|
|
781
|
+
name: `create${model.name}`,
|
|
782
|
+
type: model.name,
|
|
783
|
+
nonNull: true,
|
|
784
|
+
args: [
|
|
785
|
+
{
|
|
786
|
+
name: "data",
|
|
787
|
+
type: `Create${model.name}`,
|
|
788
|
+
nonNull: true
|
|
789
|
+
}
|
|
790
|
+
]
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
if (model.updatable) {
|
|
794
|
+
mutations.push({
|
|
795
|
+
name: `update${model.name}`,
|
|
796
|
+
type: model.name,
|
|
797
|
+
nonNull: true,
|
|
798
|
+
args: [
|
|
799
|
+
{
|
|
800
|
+
name: "where",
|
|
801
|
+
type: `${model.name}WhereUnique`,
|
|
802
|
+
nonNull: true
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
name: "data",
|
|
806
|
+
type: `Update${model.name}`,
|
|
807
|
+
nonNull: true
|
|
808
|
+
}
|
|
809
|
+
]
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
if (model.deletable) {
|
|
813
|
+
mutations.push({
|
|
814
|
+
name: `delete${model.name}`,
|
|
815
|
+
type: "ID",
|
|
816
|
+
nonNull: true,
|
|
817
|
+
args: [
|
|
818
|
+
{
|
|
819
|
+
name: "where",
|
|
820
|
+
type: `${model.name}WhereUnique`,
|
|
821
|
+
nonNull: true
|
|
822
|
+
},
|
|
823
|
+
{
|
|
824
|
+
name: "dryRun",
|
|
825
|
+
type: "Boolean"
|
|
826
|
+
}
|
|
827
|
+
]
|
|
828
|
+
});
|
|
829
|
+
mutations.push({
|
|
830
|
+
name: `restore${model.name}`,
|
|
831
|
+
type: "ID",
|
|
832
|
+
nonNull: true,
|
|
833
|
+
args: [
|
|
834
|
+
{
|
|
835
|
+
name: "where",
|
|
836
|
+
type: `${model.name}WhereUnique`,
|
|
837
|
+
nonNull: true
|
|
838
|
+
}
|
|
839
|
+
]
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
return mutations;
|
|
843
|
+
})
|
|
844
|
+
)
|
|
845
|
+
])
|
|
846
|
+
];
|
|
847
|
+
};
|
|
848
|
+
var generate = (rawModels) => document(generateDefinitions(rawModels));
|
|
849
|
+
var printSchema = (schema) => [
|
|
850
|
+
...schema.getDirectives().map((d) => d.astNode && (0, import_graphql.print)(d.astNode)),
|
|
851
|
+
...Object.values(schema.getTypeMap()).filter((t) => !t.name.match(/^__/)).sort((a, b) => a.name > b.name ? 1 : -1).map((t) => t.astNode && (0, import_graphql.print)(t.astNode))
|
|
852
|
+
].filter(Boolean).map((s) => `${s}
|
|
853
|
+
`).join("\n");
|
|
854
|
+
var hasRawFilters = (models, type) => models.filter(isRawObjectModel).some(({ name: name2, rawFilters }) => name2 === type && !!rawFilters);
|
|
855
|
+
var printSchemaFromDocument = (document2) => printSchema((0, import_graphql.buildASTSchema)(document2));
|
|
856
|
+
var printSchemaFromModels = (models) => printSchema((0, import_graphql.buildASTSchema)(generate(models)));
|
|
857
|
+
|
|
858
|
+
// src/generate/mutations.ts
|
|
859
|
+
var import_upperCase = __toESM(require("lodash/upperCase"), 1);
|
|
860
|
+
var constantCase = (str) => (0, import_upperCase.default)(str).replace(/ /g, "_");
|
|
861
|
+
var generateMutations = (models) => {
|
|
862
|
+
const parts = [];
|
|
863
|
+
for (const { name: name2, creatable, updatable, deletable } of models) {
|
|
864
|
+
if (creatable) {
|
|
865
|
+
parts.push(
|
|
866
|
+
`export const CREATE_${constantCase(
|
|
867
|
+
name2
|
|
868
|
+
)} = gql\`
|
|
869
|
+
mutation Create${name2}Mutation($data: Create${name2}!) {
|
|
870
|
+
create${name2}(data: $data) { id }
|
|
871
|
+
}
|
|
872
|
+
\`;`
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
if (updatable) {
|
|
876
|
+
parts.push(
|
|
877
|
+
`export const UPDATE_${constantCase(
|
|
878
|
+
name2
|
|
879
|
+
)} = gql\`
|
|
880
|
+
mutation Update${name2}Mutation($id: ID!, $data: Update${name2}!) {
|
|
881
|
+
update${name2}(where: { id: $id }, data: $data) { id }
|
|
882
|
+
}
|
|
883
|
+
\`;`
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
if (deletable) {
|
|
887
|
+
parts.push(
|
|
888
|
+
`export const DELETE_${constantCase(
|
|
889
|
+
name2
|
|
890
|
+
)} = gql\`
|
|
891
|
+
mutation Delete${name2}Mutation($id: ID!) {
|
|
892
|
+
delete${name2}(where: { id: $id })
|
|
893
|
+
}
|
|
894
|
+
\`;`
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
return `import { gql } from "@smartive-private/graphql-magic";
|
|
899
|
+
|
|
900
|
+
${parts.join("\n\n")}`;
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
// src/migrations/generate.ts
|
|
904
|
+
var import_code_block_writer = __toESM(require("code-block-writer"), 1);
|
|
905
|
+
var import_knex_schema_inspector = require("knex-schema-inspector");
|
|
906
|
+
var import_lowerFirst = __toESM(require("lodash/lowerFirst"), 1);
|
|
907
|
+
var MigrationGenerator = class {
|
|
908
|
+
constructor(knex, rawModels) {
|
|
909
|
+
this.rawModels = rawModels;
|
|
910
|
+
this.schema = (0, import_knex_schema_inspector.SchemaInspector)(knex);
|
|
911
|
+
this.models = getModels(rawModels);
|
|
912
|
+
}
|
|
913
|
+
writer = new (import_code_block_writer.default["default"] || import_code_block_writer.default)({
|
|
914
|
+
useSingleQuote: true,
|
|
915
|
+
indentNumberOfSpaces: 2
|
|
916
|
+
});
|
|
917
|
+
schema;
|
|
918
|
+
columns = {};
|
|
919
|
+
uuidUsed;
|
|
920
|
+
nowUsed;
|
|
921
|
+
models;
|
|
922
|
+
async generate() {
|
|
923
|
+
const { writer, schema, rawModels, models } = this;
|
|
924
|
+
const enums = (await schema.knex("pg_type").where({ typtype: "e" }).select("typname")).map(({ typname }) => typname);
|
|
925
|
+
const tables = await schema.tables();
|
|
926
|
+
for (const table of tables) {
|
|
927
|
+
this.columns[table] = await schema.columnInfo(table);
|
|
928
|
+
}
|
|
929
|
+
const up = [];
|
|
930
|
+
const down = [];
|
|
931
|
+
this.createEnums(
|
|
932
|
+
rawModels.filter(isEnumModel).filter((enm2) => !enums.includes((0, import_lowerFirst.default)(enm2.name))),
|
|
933
|
+
up,
|
|
934
|
+
down
|
|
935
|
+
);
|
|
936
|
+
for (const model of models) {
|
|
937
|
+
if (model.deleted) {
|
|
938
|
+
up.push(() => {
|
|
939
|
+
this.dropTable(model.name);
|
|
940
|
+
});
|
|
941
|
+
down.push(() => {
|
|
942
|
+
this.createTable(model.name, () => {
|
|
943
|
+
for (const field of model.fields) {
|
|
944
|
+
this.column(field);
|
|
945
|
+
}
|
|
946
|
+
});
|
|
947
|
+
});
|
|
948
|
+
if (model.updatable) {
|
|
949
|
+
up.push(() => {
|
|
950
|
+
this.dropTable(`${model.name}Revision`);
|
|
951
|
+
});
|
|
952
|
+
down.push(() => {
|
|
953
|
+
this.createRevisionTable(model);
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
if (model.oldName) {
|
|
958
|
+
up.push(() => {
|
|
959
|
+
this.renameTable(model.oldName, model.name);
|
|
960
|
+
});
|
|
961
|
+
down.push(() => {
|
|
962
|
+
this.renameTable(model.name, model.oldName);
|
|
963
|
+
});
|
|
964
|
+
tables[tables.indexOf(model.oldName)] = model.name;
|
|
965
|
+
this.columns[model.name] = this.columns[model.oldName];
|
|
966
|
+
delete this.columns[model.oldName];
|
|
967
|
+
if (model.updatable) {
|
|
968
|
+
up.push(() => {
|
|
969
|
+
this.renameTable(`${model.oldName}Revision`, `${model.name}Revision`);
|
|
970
|
+
this.alterTable(`${model.name}Revision`, () => {
|
|
971
|
+
this.renameColumn(`${typeToField(get(model, "oldName"))}Id`, `${typeToField(model.name)}Id`);
|
|
972
|
+
});
|
|
973
|
+
});
|
|
974
|
+
down.push(() => {
|
|
975
|
+
this.renameTable(`${model.name}Revision`, `${model.oldName}Revision`);
|
|
976
|
+
this.alterTable(`${model.oldName}Revision`, () => {
|
|
977
|
+
this.renameColumn(`${typeToField(model.name)}Id`, `${typeToField(get(model, "oldName"))}Id`);
|
|
978
|
+
});
|
|
979
|
+
});
|
|
980
|
+
tables[tables.indexOf(`${model.oldName}Revision`)] = `${model.name}Revision`;
|
|
981
|
+
this.columns[`${model.name}Revision`] = this.columns[`${model.oldName}Revision`];
|
|
982
|
+
delete this.columns[`${model.oldName}Revision`];
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
if (!tables.includes(model.name)) {
|
|
986
|
+
up.push(() => {
|
|
987
|
+
this.createTable(model.name, () => {
|
|
988
|
+
for (const field of model.fields) {
|
|
989
|
+
this.column(field);
|
|
990
|
+
}
|
|
991
|
+
});
|
|
992
|
+
});
|
|
993
|
+
down.push(() => {
|
|
994
|
+
this.dropTable(model.name);
|
|
995
|
+
});
|
|
996
|
+
} else {
|
|
997
|
+
this.renameFields(
|
|
998
|
+
model,
|
|
999
|
+
model.fields.filter(({ oldName }) => oldName),
|
|
1000
|
+
up,
|
|
1001
|
+
down
|
|
1002
|
+
);
|
|
1003
|
+
this.createFields(
|
|
1004
|
+
model,
|
|
1005
|
+
model.fields.filter(
|
|
1006
|
+
({ name: name2, relation, raw, foreignKey }) => !raw && !this.columns[model.name].some((col) => col.name === (foreignKey || (relation ? `${name2}Id` : name2)))
|
|
1007
|
+
),
|
|
1008
|
+
up,
|
|
1009
|
+
down
|
|
1010
|
+
);
|
|
1011
|
+
const existingFields = model.fields.filter(({ name: name2, relation, nonNull: nonNull2 }) => {
|
|
1012
|
+
const col = this.columns[model.name].find((col2) => col2.name === (relation ? `${name2}Id` : name2));
|
|
1013
|
+
if (!col) {
|
|
1014
|
+
return false;
|
|
1015
|
+
}
|
|
1016
|
+
return !model.nonStrict && !nonNull2 && !col.is_nullable;
|
|
1017
|
+
});
|
|
1018
|
+
this.updateFields(model, existingFields, up, down);
|
|
1019
|
+
}
|
|
1020
|
+
if (model.updatable) {
|
|
1021
|
+
if (!tables.includes(`${model.name}Revision`)) {
|
|
1022
|
+
up.push(() => {
|
|
1023
|
+
this.createRevisionTable(model);
|
|
1024
|
+
});
|
|
1025
|
+
if (tables.includes(model.name)) {
|
|
1026
|
+
up.push(() => {
|
|
1027
|
+
writer.block(() => {
|
|
1028
|
+
writer.writeLine(`const data = await knex('${model.name}');`);
|
|
1029
|
+
writer.write(`if (data.length)`).block(() => {
|
|
1030
|
+
writer.write(`await knex.batchInsert('${model.name}Revision', data.map((row) => (`).inlineBlock(() => {
|
|
1031
|
+
writer.writeLine(`id: uuid(),`);
|
|
1032
|
+
writer.writeLine(`${typeToField(model.name)}Id: row.id,`);
|
|
1033
|
+
this.nowUsed = true;
|
|
1034
|
+
writer.writeLine(`createdAt: row.updatedAt || row.createdAt || now,`);
|
|
1035
|
+
writer.writeLine(`createdById: row.updatedById || row.createdById,`);
|
|
1036
|
+
if (model.deletable) {
|
|
1037
|
+
writer.writeLine(`deleted: row.deleted,`);
|
|
1038
|
+
}
|
|
1039
|
+
for (const { name: name2, relation } of model.fields.filter(({ updatable }) => updatable)) {
|
|
1040
|
+
const col = relation ? `${name2}Id` : name2;
|
|
1041
|
+
writer.writeLine(`${col}: row.${col},`);
|
|
1042
|
+
}
|
|
1043
|
+
}).write(")));").newLine();
|
|
1044
|
+
});
|
|
1045
|
+
}).blankLine();
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
down.push(() => {
|
|
1049
|
+
this.dropTable(`${model.name}Revision`);
|
|
1050
|
+
});
|
|
1051
|
+
} else {
|
|
1052
|
+
const revisionTable = `${model.name}Revision`;
|
|
1053
|
+
const missingRevisionFields = model.fields.filter(
|
|
1054
|
+
({ name: name2, relation, raw, foreignKey, updatable }) => !raw && updatable && !this.columns[revisionTable].some((col) => col.name === (foreignKey || (relation ? `${name2}Id` : name2)))
|
|
1055
|
+
);
|
|
1056
|
+
this.createRevisionFields(model, missingRevisionFields, up, down);
|
|
1057
|
+
const revisionFieldsToRemove = model.fields.filter(
|
|
1058
|
+
({ name: name2, updatable, foreignKey, relation, raw, generated }) => !generated && !raw && !updatable && foreignKey !== "id" && this.columns[revisionTable].some((col) => col.name === (foreignKey || (relation ? `${name2}Id` : name2)))
|
|
1059
|
+
);
|
|
1060
|
+
this.createRevisionFields(model, revisionFieldsToRemove, down, up);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
for (const model of getModels(rawModels)) {
|
|
1065
|
+
if (tables.includes(model.name)) {
|
|
1066
|
+
this.createFields(
|
|
1067
|
+
model,
|
|
1068
|
+
model.fields.filter(({ name: name2, deleted }) => deleted && this.columns[model.name].some((col) => col.name === name2)),
|
|
1069
|
+
down,
|
|
1070
|
+
up
|
|
1071
|
+
);
|
|
1072
|
+
if (model.updatable) {
|
|
1073
|
+
this.createRevisionFields(
|
|
1074
|
+
model,
|
|
1075
|
+
model.fields.filter(({ deleted, updatable }) => updatable && deleted),
|
|
1076
|
+
down,
|
|
1077
|
+
up
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
this.createEnums(
|
|
1083
|
+
rawModels.filter(isEnumModel).filter((enm2) => enm2.deleted),
|
|
1084
|
+
down,
|
|
1085
|
+
up
|
|
1086
|
+
);
|
|
1087
|
+
writer.writeLine(`import { Knex } from 'knex';`);
|
|
1088
|
+
if (this.uuidUsed) {
|
|
1089
|
+
writer.writeLine(`import { v4 as uuid } from 'uuid';`);
|
|
1090
|
+
}
|
|
1091
|
+
if (this.nowUsed) {
|
|
1092
|
+
writer.writeLine(`import { date } from '../src/utils/dates';`);
|
|
1093
|
+
}
|
|
1094
|
+
writer.blankLine();
|
|
1095
|
+
if (this.nowUsed) {
|
|
1096
|
+
writer.writeLine(`const now = date();`).blankLine();
|
|
1097
|
+
}
|
|
1098
|
+
this.migration("up", up);
|
|
1099
|
+
this.migration("down", down.reverse());
|
|
1100
|
+
return writer.toString();
|
|
1101
|
+
}
|
|
1102
|
+
renameFields(model, fields2, up, down) {
|
|
1103
|
+
if (!fields2.length) {
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
up.push(() => {
|
|
1107
|
+
for (const field of fields2) {
|
|
1108
|
+
this.alterTable(model.name, () => {
|
|
1109
|
+
this.renameColumn(
|
|
1110
|
+
field.relation ? `${field.oldName}Id` : get(field, "oldName"),
|
|
1111
|
+
field.relation ? `${field.name}Id` : field.name
|
|
1112
|
+
);
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
});
|
|
1116
|
+
down.push(() => {
|
|
1117
|
+
for (const field of fields2) {
|
|
1118
|
+
this.alterTable(model.name, () => {
|
|
1119
|
+
this.renameColumn(
|
|
1120
|
+
field.relation ? `${field.name}Id` : field.name,
|
|
1121
|
+
field.relation ? `${field.oldName}Id` : get(field, "oldName")
|
|
1122
|
+
);
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
});
|
|
1126
|
+
for (const field of fields2) {
|
|
1127
|
+
summonByName(this.columns[model.name], field.relation ? `${field.oldName}Id` : field.oldName).name = field.relation ? `${field.name}Id` : field.name;
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
createFields(model, fields2, up, down) {
|
|
1131
|
+
if (!fields2.length) {
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
up.push(() => {
|
|
1135
|
+
const alter = [];
|
|
1136
|
+
const updates = [];
|
|
1137
|
+
const postAlter = [];
|
|
1138
|
+
for (const field of fields2) {
|
|
1139
|
+
alter.push(() => this.column(field, { setNonNull: field.default !== void 0 }));
|
|
1140
|
+
if (field.nonNull && field.default === void 0) {
|
|
1141
|
+
updates.push(() => this.writer.write(`${field.name}: 'TODO',`).newLine());
|
|
1142
|
+
postAlter.push(() => this.column(field, { alter: true, foreign: false }));
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
if (alter.length) {
|
|
1146
|
+
this.alterTable(model.name, () => {
|
|
1147
|
+
alter.map((cb) => cb());
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
if (updates.length) {
|
|
1151
|
+
this.writer.write(`await knex('${model.name}').update(`).inlineBlock(() => {
|
|
1152
|
+
updates.map((cb) => cb());
|
|
1153
|
+
}).write(");").newLine().blankLine();
|
|
1154
|
+
}
|
|
1155
|
+
if (postAlter.length) {
|
|
1156
|
+
this.alterTable(model.name, () => {
|
|
1157
|
+
postAlter.map((cb) => cb());
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
});
|
|
1161
|
+
down.push(() => {
|
|
1162
|
+
this.alterTable(model.name, () => {
|
|
1163
|
+
for (const { relation, name: name2 } of fields2) {
|
|
1164
|
+
this.dropColumn(relation ? `${name2}Id` : name2);
|
|
1165
|
+
}
|
|
1166
|
+
});
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
updateFields(model, fields2, up, down) {
|
|
1170
|
+
if (!fields2.length) {
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
up.push(() => {
|
|
1174
|
+
this.alterTable(model.name, () => {
|
|
1175
|
+
for (const field of fields2) {
|
|
1176
|
+
this.column(field, { alter: true });
|
|
1177
|
+
}
|
|
1178
|
+
});
|
|
1179
|
+
});
|
|
1180
|
+
down.push(() => {
|
|
1181
|
+
this.alterTable(model.name, () => {
|
|
1182
|
+
for (const field of fields2) {
|
|
1183
|
+
this.column(
|
|
1184
|
+
field,
|
|
1185
|
+
{ alter: true },
|
|
1186
|
+
summonByName(this.columns[model.name], field.relation ? `${field.name}Id` : field.name)
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
});
|
|
1190
|
+
});
|
|
1191
|
+
if (model.updatable) {
|
|
1192
|
+
const updatableFields = fields2.filter(({ updatable }) => updatable);
|
|
1193
|
+
if (!updatableFields.length) {
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
up.push(() => {
|
|
1197
|
+
this.alterTable(`${model.name}Revision`, () => {
|
|
1198
|
+
for (const field of updatableFields) {
|
|
1199
|
+
this.column(field, { alter: true });
|
|
1200
|
+
}
|
|
1201
|
+
});
|
|
1202
|
+
});
|
|
1203
|
+
down.push(() => {
|
|
1204
|
+
this.alterTable(`${model.name}Revision`, () => {
|
|
1205
|
+
for (const field of updatableFields) {
|
|
1206
|
+
this.column(
|
|
1207
|
+
field,
|
|
1208
|
+
{ alter: true },
|
|
1209
|
+
summonByName(this.columns[model.name], field.relation ? `${field.name}Id` : field.name)
|
|
1210
|
+
);
|
|
1211
|
+
}
|
|
1212
|
+
});
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
createRevisionTable(model) {
|
|
1217
|
+
const writer = this.writer;
|
|
1218
|
+
this.createTable(`${model.name}Revision`, () => {
|
|
1219
|
+
writer.writeLine(`table.uuid('id').notNullable().primary();`);
|
|
1220
|
+
writer.writeLine(`table.uuid('${typeToField(model.name)}Id').notNullable();`);
|
|
1221
|
+
writer.write(`table.uuid('createdById')`);
|
|
1222
|
+
if (!model.nonStrict) {
|
|
1223
|
+
writer.write(".notNullable()");
|
|
1224
|
+
}
|
|
1225
|
+
writer.write(";").newLine();
|
|
1226
|
+
writer.writeLine(`table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now(0));`);
|
|
1227
|
+
if (model.deletable) {
|
|
1228
|
+
writer.writeLine(`table.boolean('deleted').notNullable();`);
|
|
1229
|
+
}
|
|
1230
|
+
for (const field of model.fields.filter((field2) => field2.updatable)) {
|
|
1231
|
+
this.column(field, { setUnique: false, setDefault: false });
|
|
1232
|
+
}
|
|
1233
|
+
});
|
|
1234
|
+
}
|
|
1235
|
+
createRevisionFields(model, missingRevisionFields, up, down) {
|
|
1236
|
+
const revisionTable = `${model.name}Revision`;
|
|
1237
|
+
if (missingRevisionFields.length) {
|
|
1238
|
+
up.push(() => {
|
|
1239
|
+
this.alterTable(revisionTable, () => {
|
|
1240
|
+
for (const field of missingRevisionFields) {
|
|
1241
|
+
this.column(field, { setUnique: false, setNonNull: false, setDefault: false });
|
|
1242
|
+
}
|
|
1243
|
+
});
|
|
1244
|
+
this.writer.write(`await knex('${model.name}Revision').update(`).inlineBlock(() => {
|
|
1245
|
+
for (const { name: name2, relation } of missingRevisionFields) {
|
|
1246
|
+
const col = relation ? `${name2}Id` : name2;
|
|
1247
|
+
this.writer.write(
|
|
1248
|
+
`${col}: knex.raw('(select "${col}" from "${model.name}" where "${model.name}".id = "${model.name}Revision"."${typeToField(model.name)}Id")'),`
|
|
1249
|
+
).newLine();
|
|
1250
|
+
}
|
|
1251
|
+
}).write(");").newLine().blankLine();
|
|
1252
|
+
const nonNullableMissingRevisionFields = missingRevisionFields.filter(({ nonNull: nonNull2 }) => nonNull2);
|
|
1253
|
+
if (nonNullableMissingRevisionFields.length) {
|
|
1254
|
+
this.alterTable(revisionTable, () => {
|
|
1255
|
+
for (const field of nonNullableMissingRevisionFields) {
|
|
1256
|
+
this.column(field, { setUnique: false, setDefault: false, alter: true });
|
|
1257
|
+
}
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
});
|
|
1261
|
+
down.push(() => {
|
|
1262
|
+
this.alterTable(revisionTable, () => {
|
|
1263
|
+
for (const field of missingRevisionFields) {
|
|
1264
|
+
this.dropColumn(field.relation ? `${field.name}Id` : field.name);
|
|
1265
|
+
}
|
|
1266
|
+
});
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
createEnums(enums, up, down) {
|
|
1271
|
+
for (const enm2 of enums) {
|
|
1272
|
+
const name2 = (0, import_lowerFirst.default)(enm2.name);
|
|
1273
|
+
up.push(
|
|
1274
|
+
() => this.writer.writeLine(
|
|
1275
|
+
`await knex.raw(\`CREATE TYPE "${name2}" AS ENUM (${enm2.values.map((value2) => `'${value2}'`).join(",")})\`);`
|
|
1276
|
+
).newLine()
|
|
1277
|
+
);
|
|
1278
|
+
down.push(() => this.writer.writeLine(`await knex.raw('DROP TYPE "${name2}"')`));
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
migration(name2, cbs) {
|
|
1282
|
+
return this.writer.write(`export const ${name2} = async (knex: Knex) => `).inlineBlock(() => {
|
|
1283
|
+
cbs.map((cb) => cb());
|
|
1284
|
+
}).write(";").newLine().blankLine();
|
|
1285
|
+
}
|
|
1286
|
+
createTable(table, block) {
|
|
1287
|
+
return this.writer.write(`await knex.schema.createTable('${table}', (table) => `).inlineBlock(block).write(");").newLine().blankLine();
|
|
1288
|
+
}
|
|
1289
|
+
alterTable(table, block) {
|
|
1290
|
+
return this.writer.write(`await knex.schema.alterTable('${table}', (table) => `).inlineBlock(block).write(");").newLine().blankLine();
|
|
1291
|
+
}
|
|
1292
|
+
dropColumn(col) {
|
|
1293
|
+
return this.writer.writeLine(`table.dropColumn('${col}');`);
|
|
1294
|
+
}
|
|
1295
|
+
dropTable(table) {
|
|
1296
|
+
return this.writer.writeLine(`await knex.schema.dropTable('${table}');`).blankLine();
|
|
1297
|
+
}
|
|
1298
|
+
renameTable(from, to) {
|
|
1299
|
+
return this.writer.writeLine(`await knex.schema.renameTable('${from}', '${to}');`).blankLine();
|
|
1300
|
+
}
|
|
1301
|
+
renameColumn(from, to) {
|
|
1302
|
+
this.writer.writeLine(`table.renameColumn('${from}', '${to}')`);
|
|
1303
|
+
}
|
|
1304
|
+
value(value2) {
|
|
1305
|
+
if (typeof value2 === "string") {
|
|
1306
|
+
return `'${value2}'`;
|
|
1307
|
+
}
|
|
1308
|
+
return value2;
|
|
1309
|
+
}
|
|
1310
|
+
column({ name: name2, relation, type, primary, list: list2, ...field }, { setUnique = true, setNonNull = true, alter = false, foreign = true, setDefault = true } = {}, toColumn) {
|
|
1311
|
+
const col = (what) => {
|
|
1312
|
+
if (what) {
|
|
1313
|
+
this.writer.write(what);
|
|
1314
|
+
}
|
|
1315
|
+
if (setNonNull) {
|
|
1316
|
+
if (toColumn) {
|
|
1317
|
+
if (toColumn.is_nullable) {
|
|
1318
|
+
this.writer.write(`.nullable()`);
|
|
1319
|
+
} else {
|
|
1320
|
+
this.writer.write(".notNullable()");
|
|
1321
|
+
}
|
|
1322
|
+
} else {
|
|
1323
|
+
if (field.nonNull) {
|
|
1324
|
+
this.writer.write(`.notNullable()`);
|
|
1325
|
+
} else {
|
|
1326
|
+
this.writer.write(".nullable()");
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
if (setDefault && field.default !== void 0) {
|
|
1331
|
+
this.writer.write(`.defaultTo(${this.value(field.default)})`);
|
|
1332
|
+
}
|
|
1333
|
+
if (primary) {
|
|
1334
|
+
this.writer.write(".primary()");
|
|
1335
|
+
} else if (setUnique && field.unique) {
|
|
1336
|
+
this.writer.write(".unique()");
|
|
1337
|
+
}
|
|
1338
|
+
if (alter) {
|
|
1339
|
+
this.writer.write(".alter()");
|
|
1340
|
+
}
|
|
1341
|
+
this.writer.write(";").newLine();
|
|
1342
|
+
};
|
|
1343
|
+
if (relation) {
|
|
1344
|
+
col(`table.uuid('${name2}Id')`);
|
|
1345
|
+
if (foreign && !alter) {
|
|
1346
|
+
this.writer.writeLine(`table.foreign('${name2}Id').references('id').inTable('${type}');`);
|
|
1347
|
+
}
|
|
1348
|
+
} else if (this.rawModels.some((m) => m.name === type && m.type === "enum")) {
|
|
1349
|
+
list2 ? this.writer.write(`table.specificType('${name2}', '"${typeToField(type)}"[]');`) : this.writer.write(`table.enum('${name2}', null as any, `).inlineBlock(() => {
|
|
1350
|
+
this.writer.writeLine(`useNative: true,`);
|
|
1351
|
+
this.writer.writeLine(`existingType: true,`);
|
|
1352
|
+
this.writer.writeLine(`enumName: '${typeToField(type)}',`);
|
|
1353
|
+
}).write(")");
|
|
1354
|
+
col();
|
|
1355
|
+
} else {
|
|
1356
|
+
switch (type) {
|
|
1357
|
+
case "Boolean":
|
|
1358
|
+
col(`table.boolean('${name2}')`);
|
|
1359
|
+
break;
|
|
1360
|
+
case "Int":
|
|
1361
|
+
col(`table.integer('${name2}')`);
|
|
1362
|
+
break;
|
|
1363
|
+
case "Float":
|
|
1364
|
+
if (field.double) {
|
|
1365
|
+
col(`table.double('${name2}')`);
|
|
1366
|
+
} else {
|
|
1367
|
+
col(`table.decimal('${name2}', ${get(field, "precision")}, ${get(field, "scale")})`);
|
|
1368
|
+
}
|
|
1369
|
+
break;
|
|
1370
|
+
case "String":
|
|
1371
|
+
if (field.large) {
|
|
1372
|
+
col(`table.text('${name2}')`);
|
|
1373
|
+
} else {
|
|
1374
|
+
col(`table.string('${name2}', ${field.maxLength})`);
|
|
1375
|
+
}
|
|
1376
|
+
break;
|
|
1377
|
+
case "DateTime":
|
|
1378
|
+
col(`table.timestamp('${name2}')`);
|
|
1379
|
+
break;
|
|
1380
|
+
case "ID":
|
|
1381
|
+
if (field.maxLength) {
|
|
1382
|
+
col(`table.string('${name2}', ${get(field, "maxLength")})`);
|
|
1383
|
+
} else {
|
|
1384
|
+
col(`table.uuid('${name2}')`);
|
|
1385
|
+
}
|
|
1386
|
+
break;
|
|
1387
|
+
case "Upload":
|
|
1388
|
+
break;
|
|
1389
|
+
default:
|
|
1390
|
+
throw new Error(`Unknown field type ${type}`);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
};
|
|
1395
|
+
|
|
1396
|
+
// src/errors.ts
|
|
1397
|
+
var import_graphql2 = require("graphql");
|
|
1398
|
+
var GraphQLError = class extends import_graphql2.GraphQLError {
|
|
1399
|
+
constructor(message, extensions) {
|
|
1400
|
+
super(message, void 0, void 0, void 0, void 0, void 0, extensions);
|
|
1401
|
+
}
|
|
1402
|
+
};
|
|
1403
|
+
var ForbiddenError = class extends GraphQLError {
|
|
1404
|
+
constructor(what) {
|
|
1405
|
+
super(what, { code: "FORBIDDEN" });
|
|
1406
|
+
}
|
|
1407
|
+
};
|
|
1408
|
+
var NotFoundError = class extends GraphQLError {
|
|
1409
|
+
constructor(what) {
|
|
1410
|
+
super(what, { code: "NOT_FOUND" });
|
|
1411
|
+
}
|
|
1412
|
+
};
|
|
1413
|
+
var UserInputError = class extends GraphQLError {
|
|
1414
|
+
constructor(what) {
|
|
1415
|
+
super(what, { code: "BAD_USER_INPUT" });
|
|
1416
|
+
}
|
|
1417
|
+
};
|
|
1418
|
+
var PermissionError = class extends ForbiddenError {
|
|
1419
|
+
constructor(action, what) {
|
|
1420
|
+
super(`You do not have sufficient permissions to ${action.toLowerCase()} ${what}.`);
|
|
1421
|
+
}
|
|
1422
|
+
};
|
|
1423
|
+
|
|
1424
|
+
// src/resolvers/utils.ts
|
|
1425
|
+
var import_crypto = require("crypto");
|
|
1426
|
+
var import_graphql3 = require("graphql");
|
|
1427
|
+
var ID_ALIAS = "ID";
|
|
1428
|
+
var getTypeName = (t) => {
|
|
1429
|
+
switch (t.kind) {
|
|
1430
|
+
case "ListType":
|
|
1431
|
+
case "NonNullType":
|
|
1432
|
+
return getTypeName(t.type);
|
|
1433
|
+
default:
|
|
1434
|
+
return t.name.value;
|
|
1435
|
+
}
|
|
1436
|
+
};
|
|
1437
|
+
var isListType = (type) => {
|
|
1438
|
+
switch (type.kind) {
|
|
1439
|
+
case "ListType":
|
|
1440
|
+
return true;
|
|
1441
|
+
case "NonNullType":
|
|
1442
|
+
return isListType(type.type);
|
|
1443
|
+
default:
|
|
1444
|
+
return false;
|
|
1445
|
+
}
|
|
1446
|
+
};
|
|
1447
|
+
var isFieldNode = (n) => n.kind === import_graphql3.Kind.FIELD;
|
|
1448
|
+
var isInlineFragmentNode = (n) => n.kind === import_graphql3.Kind.INLINE_FRAGMENT;
|
|
1449
|
+
var isFragmentSpreadNode = (n) => n.kind === import_graphql3.Kind.FRAGMENT_SPREAD;
|
|
1450
|
+
var getType = (schema, name2) => get(schema.getType(name2), "astNode");
|
|
1451
|
+
var getFragmentTypeName = (node) => get(get(node.typeCondition, "name"), "value");
|
|
1452
|
+
function hydrate(node, raw) {
|
|
1453
|
+
const tableAlias = node.tableAlias;
|
|
1454
|
+
const res = raw.map((entry) => {
|
|
1455
|
+
const res2 = {};
|
|
1456
|
+
outer:
|
|
1457
|
+
for (const [column, value2] of Object.entries(entry)) {
|
|
1458
|
+
let current = res2;
|
|
1459
|
+
const shortParts = column.split("__");
|
|
1460
|
+
const fieldName = shortParts.pop();
|
|
1461
|
+
const columnWithoutField = shortParts.join("__");
|
|
1462
|
+
const longColumn = node.ctx.aliases.getLong(columnWithoutField);
|
|
1463
|
+
const longColumnWithoutRoot = longColumn.replace(new RegExp(`^${tableAlias}(__)?`), "");
|
|
1464
|
+
const allParts = [tableAlias, ...longColumnWithoutRoot ? longColumnWithoutRoot.split("__") : [], fieldName];
|
|
1465
|
+
for (let i = 0; i < allParts.length - 1; i++) {
|
|
1466
|
+
const part = allParts[i];
|
|
1467
|
+
if (!current[part]) {
|
|
1468
|
+
const idField = [node.ctx.aliases.getShort(allParts.slice(0, i + 1).join("__")), ID_ALIAS].join("__");
|
|
1469
|
+
if (!entry[idField]) {
|
|
1470
|
+
continue outer;
|
|
1471
|
+
}
|
|
1472
|
+
current[part] = {};
|
|
1473
|
+
}
|
|
1474
|
+
current = current[part];
|
|
1475
|
+
}
|
|
1476
|
+
current[it(fieldName)] = value2;
|
|
1477
|
+
}
|
|
1478
|
+
return res2[tableAlias];
|
|
1479
|
+
});
|
|
1480
|
+
return res;
|
|
1481
|
+
}
|
|
1482
|
+
var ors = (query, [first, ...rest]) => {
|
|
1483
|
+
if (!first) {
|
|
1484
|
+
return query;
|
|
1485
|
+
}
|
|
1486
|
+
return query.where((subQuery) => {
|
|
1487
|
+
subQuery.where((subSubQuery) => {
|
|
1488
|
+
first(subSubQuery);
|
|
1489
|
+
});
|
|
1490
|
+
for (const cb of rest) {
|
|
1491
|
+
subQuery.orWhere((subSubQuery) => {
|
|
1492
|
+
cb(subSubQuery);
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
});
|
|
1496
|
+
};
|
|
1497
|
+
var getNameOrAlias = (node) => {
|
|
1498
|
+
const name2 = node.alias ? node.alias.value : node.name.value;
|
|
1499
|
+
if ([ID_ALIAS].indexOf(name2) >= 0) {
|
|
1500
|
+
throw new UserInputError(`"${name2}" can not be used as alias since it's a reserved word`);
|
|
1501
|
+
}
|
|
1502
|
+
return name2;
|
|
1503
|
+
};
|
|
1504
|
+
var apply = (target, ops) => ops.reduce((target2, op) => op(target2), target);
|
|
1505
|
+
var applyJoins = (aliases, query, joins) => {
|
|
1506
|
+
for (const [tableName, { table1Alias, column1, column2 }] of Object.entries(joins)) {
|
|
1507
|
+
const [table, alias] = tableName.split(":");
|
|
1508
|
+
const table1ShortAlias = aliases.getShort(table1Alias);
|
|
1509
|
+
const table2ShortAlias = aliases.getShort(alias);
|
|
1510
|
+
query.leftJoin(`${table} as ${table2ShortAlias}`, `${table1ShortAlias}.${column1}`, `${table2ShortAlias}.${column2}`);
|
|
1511
|
+
}
|
|
1512
|
+
};
|
|
1513
|
+
var addJoin = (joins, table1Alias, table2, alias2, column1, column2) => {
|
|
1514
|
+
joins[`${table2}:${alias2}`] ||= { table1Alias, column1, column2 };
|
|
1515
|
+
};
|
|
1516
|
+
var AliasGenerator = class {
|
|
1517
|
+
reverse = {};
|
|
1518
|
+
getShort(long) {
|
|
1519
|
+
if (!long) {
|
|
1520
|
+
const short2 = `a${Object.keys(this.reverse).length}`;
|
|
1521
|
+
return this.reverse[short2] = short2;
|
|
1522
|
+
}
|
|
1523
|
+
const shortPrefix = long.split("__").map((part) => part[0] + part.slice(1).replaceAll(/[a-z]+/g, "")).join("__");
|
|
1524
|
+
let postfix = 0;
|
|
1525
|
+
let short = shortPrefix + (postfix || "");
|
|
1526
|
+
while (short in this.reverse && this.reverse[short] !== long) {
|
|
1527
|
+
short = shortPrefix + ++postfix;
|
|
1528
|
+
}
|
|
1529
|
+
this.reverse[short] = long;
|
|
1530
|
+
return short;
|
|
1531
|
+
}
|
|
1532
|
+
getLong(short) {
|
|
1533
|
+
return get(this.reverse, short);
|
|
1534
|
+
}
|
|
1535
|
+
};
|
|
1536
|
+
var hash = (s) => (0, import_crypto.createHash)("md5").update(JSON.stringify(s)).digest("hex");
|
|
1537
|
+
|
|
1538
|
+
// src/permissions/check.ts
|
|
1539
|
+
var getPermissionStack = (ctx, type, action) => {
|
|
1540
|
+
const rolePermissions = ctx.permissions[ctx.user.role];
|
|
1541
|
+
if (typeof rolePermissions === "boolean" || rolePermissions === void 0) {
|
|
1542
|
+
return !!rolePermissions;
|
|
1543
|
+
}
|
|
1544
|
+
const typePermissions = rolePermissions[type];
|
|
1545
|
+
if (typeof typePermissions === "boolean" || typePermissions === void 0) {
|
|
1546
|
+
return !!typePermissions;
|
|
1547
|
+
}
|
|
1548
|
+
const actionPermission = typePermissions[action];
|
|
1549
|
+
if (typeof actionPermission === "boolean" || actionPermission === void 0) {
|
|
1550
|
+
return !!actionPermission;
|
|
1551
|
+
}
|
|
1552
|
+
return actionPermission;
|
|
1553
|
+
};
|
|
1554
|
+
var applyPermissions = (ctx, type, tableAlias, query, action, verifiedPermissionStack) => {
|
|
1555
|
+
const permissionStack = getPermissionStack(ctx, type, action);
|
|
1556
|
+
if (permissionStack === true) {
|
|
1557
|
+
return permissionStack;
|
|
1558
|
+
}
|
|
1559
|
+
if (permissionStack === false) {
|
|
1560
|
+
query.where(false);
|
|
1561
|
+
return permissionStack;
|
|
1562
|
+
}
|
|
1563
|
+
if (verifiedPermissionStack?.every(
|
|
1564
|
+
(prefixChain) => permissionStack.some(
|
|
1565
|
+
(chain) => hash(prefixChain) === hash(chain.slice(0, -1)) && // TODO: this is stricter than it could be if we add these checks to the query
|
|
1566
|
+
!("where" in get(chain, chain.length - 1)) && !("me" in get(chain, chain.length - 1))
|
|
1567
|
+
)
|
|
1568
|
+
)) {
|
|
1569
|
+
return permissionStack;
|
|
1570
|
+
}
|
|
1571
|
+
ors(
|
|
1572
|
+
query,
|
|
1573
|
+
permissionStack.map(
|
|
1574
|
+
(links) => (query2) => query2.whereNull(`${tableAlias}.id`).orWhereExists((subQuery) => permissionLinkQuery(ctx, subQuery, links, ctx.knex.raw(`"${tableAlias}".id`)))
|
|
1575
|
+
)
|
|
1576
|
+
);
|
|
1577
|
+
return permissionStack;
|
|
1578
|
+
};
|
|
1579
|
+
var getEntityToMutate = async (ctx, model, where, action) => {
|
|
1580
|
+
const query = ctx.knex(model.name).where(where).first();
|
|
1581
|
+
let entity = await query.clone();
|
|
1582
|
+
if (!entity) {
|
|
1583
|
+
throw new NotFoundError("Entity to mutate");
|
|
1584
|
+
}
|
|
1585
|
+
applyPermissions(ctx, model.name, model.name, query, action);
|
|
1586
|
+
entity = await query;
|
|
1587
|
+
if (!entity) {
|
|
1588
|
+
throw new PermissionError(action, `this ${model.name}`);
|
|
1589
|
+
}
|
|
1590
|
+
return entity;
|
|
1591
|
+
};
|
|
1592
|
+
var checkCanWrite = async (ctx, model, data, action) => {
|
|
1593
|
+
const permissionStack = getPermissionStack(ctx, model.name, action);
|
|
1594
|
+
if (permissionStack === true) {
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
if (permissionStack === false) {
|
|
1598
|
+
throw new PermissionError(action, getModelPlural(model));
|
|
1599
|
+
}
|
|
1600
|
+
const query = ctx.knex.select(1).first();
|
|
1601
|
+
let linked = false;
|
|
1602
|
+
for (const field of model.fields.filter(({ relation }) => relation).filter((field2) => field2.generated || (action === "CREATE" ? field2.creatable : field2.updatable))) {
|
|
1603
|
+
const foreignKey = field.foreignKey || `${field.name}Id`;
|
|
1604
|
+
const foreignId = data[foreignKey];
|
|
1605
|
+
if (!foreignId) {
|
|
1606
|
+
continue;
|
|
1607
|
+
}
|
|
1608
|
+
const fieldPermissions = field[action === "CREATE" ? "creatableBy" : "updatableBy"];
|
|
1609
|
+
if (fieldPermissions && !fieldPermissions.includes(ctx.user.role)) {
|
|
1610
|
+
throw new PermissionError(action, `this ${model.name}'s ${field.name}`);
|
|
1611
|
+
}
|
|
1612
|
+
linked = true;
|
|
1613
|
+
const fieldPermissionStack = getPermissionStack(ctx, field.type, "LINK");
|
|
1614
|
+
if (fieldPermissionStack === true) {
|
|
1615
|
+
query.whereExists((subQuery) => subQuery.from(`${field.type} as a`).whereRaw(`a.id = ?`, foreignId));
|
|
1616
|
+
continue;
|
|
1617
|
+
}
|
|
1618
|
+
if (fieldPermissionStack === false || !fieldPermissionStack.length) {
|
|
1619
|
+
throw new PermissionError(action, `this ${model.name}'s ${field.name}`);
|
|
1620
|
+
}
|
|
1621
|
+
ors(
|
|
1622
|
+
query,
|
|
1623
|
+
fieldPermissionStack.map(
|
|
1624
|
+
(links) => (query2) => query2.whereExists((subQuery) => permissionLinkQuery(ctx, subQuery, links, foreignId))
|
|
1625
|
+
)
|
|
1626
|
+
);
|
|
1627
|
+
}
|
|
1628
|
+
if (linked) {
|
|
1629
|
+
const canMutate = await query;
|
|
1630
|
+
if (!canMutate) {
|
|
1631
|
+
throw new PermissionError(action, `this ${model.name} because there are no entities you can link it to`);
|
|
1632
|
+
}
|
|
1633
|
+
} else if (action === "CREATE") {
|
|
1634
|
+
throw new PermissionError(action, `this ${model.name} because there are no entity types you can link it to`);
|
|
1635
|
+
}
|
|
1636
|
+
};
|
|
1637
|
+
var permissionLinkQuery = (ctx, subQuery, links, id) => {
|
|
1638
|
+
const aliases = new AliasGenerator();
|
|
1639
|
+
let alias = aliases.getShort();
|
|
1640
|
+
const { type, me, where } = links[0];
|
|
1641
|
+
subQuery.from(`${type} as ${alias}`);
|
|
1642
|
+
if (me) {
|
|
1643
|
+
subQuery.where({ [`${alias}.id`]: ctx.user.id });
|
|
1644
|
+
}
|
|
1645
|
+
if (where) {
|
|
1646
|
+
applyWhere(summonByName(ctx.models, type), subQuery, alias, where, aliases);
|
|
1647
|
+
}
|
|
1648
|
+
for (const { type: type2, foreignKey, reverse, where: where2 } of links) {
|
|
1649
|
+
const model = summonByName(ctx.models, type2);
|
|
1650
|
+
const subAlias = aliases.getShort();
|
|
1651
|
+
if (reverse) {
|
|
1652
|
+
subQuery.leftJoin(`${type2} as ${subAlias}`, `${alias}.${foreignKey || "id"}`, `${subAlias}.id`);
|
|
1653
|
+
} else {
|
|
1654
|
+
subQuery.rightJoin(`${type2} as ${subAlias}`, `${alias}.id`, `${subAlias}.${foreignKey || "id"}`);
|
|
1655
|
+
}
|
|
1656
|
+
subQuery.where({ [`${subAlias}.deleted`]: false });
|
|
1657
|
+
if (where2) {
|
|
1658
|
+
applyWhere(model, subQuery, subAlias, where2, aliases);
|
|
1659
|
+
}
|
|
1660
|
+
alias = subAlias;
|
|
1661
|
+
}
|
|
1662
|
+
subQuery.whereRaw(`"${alias}".id = ?`, id);
|
|
1663
|
+
};
|
|
1664
|
+
var applyWhere = (model, query, alias, where, aliases) => {
|
|
1665
|
+
for (const [key, value2] of Object.entries(where)) {
|
|
1666
|
+
const relation = model.relationsByName[key];
|
|
1667
|
+
if (relation) {
|
|
1668
|
+
const subAlias = aliases.getShort();
|
|
1669
|
+
query.leftJoin(
|
|
1670
|
+
`${relation.model.name} as ${subAlias}`,
|
|
1671
|
+
`${alias}.${relation.field.foreignKey || `${relation.field.name}Id`}`,
|
|
1672
|
+
`${subAlias}.id`
|
|
1673
|
+
);
|
|
1674
|
+
applyWhere(relation.model, query, subAlias, value2, aliases);
|
|
1675
|
+
} else if (Array.isArray(value2)) {
|
|
1676
|
+
query.whereIn(`${alias}.${key}`, value2);
|
|
1677
|
+
} else {
|
|
1678
|
+
query.where({ [`${alias}.${key}`]: value2 });
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
};
|
|
1682
|
+
|
|
1683
|
+
// src/permissions/generate.ts
|
|
1684
|
+
var ACTIONS = ["READ", "CREATE", "UPDATE", "DELETE", "RESTORE", "LINK"];
|
|
1685
|
+
var generatePermissions = (models, config) => {
|
|
1686
|
+
const permissions = {};
|
|
1687
|
+
for (const [role, roleConfig] of Object.entries(config)) {
|
|
1688
|
+
if (roleConfig === true) {
|
|
1689
|
+
permissions[role] = true;
|
|
1690
|
+
continue;
|
|
1691
|
+
}
|
|
1692
|
+
const rolePermissions = {};
|
|
1693
|
+
for (const [key, block] of Object.entries(roleConfig)) {
|
|
1694
|
+
const type = key === "me" ? "User" : key;
|
|
1695
|
+
if (key !== "me" && !("WHERE" in block)) {
|
|
1696
|
+
rolePermissions[type] = {};
|
|
1697
|
+
for (const action of ACTIONS) {
|
|
1698
|
+
if (action === "READ" || action in block) {
|
|
1699
|
+
rolePermissions[type][action] = true;
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
addPermissions(
|
|
1704
|
+
models,
|
|
1705
|
+
rolePermissions,
|
|
1706
|
+
[
|
|
1707
|
+
{
|
|
1708
|
+
type,
|
|
1709
|
+
...key === "me" && { me: true },
|
|
1710
|
+
..."WHERE" in block && { where: block.WHERE }
|
|
1711
|
+
}
|
|
1712
|
+
],
|
|
1713
|
+
block
|
|
1714
|
+
);
|
|
1715
|
+
}
|
|
1716
|
+
permissions[role] = rolePermissions;
|
|
1717
|
+
}
|
|
1718
|
+
return permissions;
|
|
1719
|
+
};
|
|
1720
|
+
var addPermissions = (models, permissions, links, block) => {
|
|
1721
|
+
const { type } = links[links.length - 1];
|
|
1722
|
+
const model = summonByName(models, type);
|
|
1723
|
+
for (const action of ACTIONS) {
|
|
1724
|
+
if (action === "READ" || action in block) {
|
|
1725
|
+
if (!permissions[type]) {
|
|
1726
|
+
permissions[type] = {};
|
|
1727
|
+
}
|
|
1728
|
+
if (!permissions[type][action]) {
|
|
1729
|
+
permissions[type][action] = [];
|
|
1730
|
+
}
|
|
1731
|
+
if (permissions[type][action] !== true) {
|
|
1732
|
+
permissions[type][action].push(links);
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
if (block.RELATIONS) {
|
|
1737
|
+
for (const [relation, subBlock] of Object.entries(block.RELATIONS)) {
|
|
1738
|
+
const field = model.fields.find((field2) => field2.relation && field2.name === relation);
|
|
1739
|
+
let link;
|
|
1740
|
+
if (field) {
|
|
1741
|
+
link = {
|
|
1742
|
+
type: field.type,
|
|
1743
|
+
foreignKey: field.foreignKey || `${field.name}Id`,
|
|
1744
|
+
reverse: true
|
|
1745
|
+
};
|
|
1746
|
+
} else {
|
|
1747
|
+
const field2 = model.reverseRelationsByName[relation];
|
|
1748
|
+
if (!field2) {
|
|
1749
|
+
throw new Error(`Relation ${relation} in model ${model.name} does not exist.`);
|
|
1750
|
+
}
|
|
1751
|
+
link = {
|
|
1752
|
+
type: field2.model.name,
|
|
1753
|
+
foreignKey: field2.foreignKey
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1756
|
+
if (subBlock.WHERE) {
|
|
1757
|
+
link.where = subBlock.WHERE;
|
|
1758
|
+
}
|
|
1759
|
+
addPermissions(models, permissions, [...links, link], subBlock);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
};
|
|
1763
|
+
|
|
1764
|
+
// src/resolvers/arguments.ts
|
|
1765
|
+
var import_graphql4 = require("graphql");
|
|
1766
|
+
function getRawValue(value2, values) {
|
|
1767
|
+
switch (value2.kind) {
|
|
1768
|
+
case import_graphql4.Kind.LIST:
|
|
1769
|
+
if (!values) {
|
|
1770
|
+
return;
|
|
1771
|
+
}
|
|
1772
|
+
return value2.values.map((value3) => getRawValue(value3, values));
|
|
1773
|
+
case import_graphql4.Kind.VARIABLE:
|
|
1774
|
+
return values?.[value2.name.value];
|
|
1775
|
+
case import_graphql4.Kind.INT:
|
|
1776
|
+
return parseInt(value2.value, 10);
|
|
1777
|
+
case import_graphql4.Kind.NULL:
|
|
1778
|
+
return null;
|
|
1779
|
+
case import_graphql4.Kind.FLOAT:
|
|
1780
|
+
return parseFloat(value2.value);
|
|
1781
|
+
case import_graphql4.Kind.STRING:
|
|
1782
|
+
case import_graphql4.Kind.BOOLEAN:
|
|
1783
|
+
case import_graphql4.Kind.ENUM:
|
|
1784
|
+
return value2.value;
|
|
1785
|
+
case import_graphql4.Kind.OBJECT: {
|
|
1786
|
+
if (!value2.fields.length) {
|
|
1787
|
+
return;
|
|
1788
|
+
}
|
|
1789
|
+
const res = {};
|
|
1790
|
+
for (const field of value2.fields) {
|
|
1791
|
+
res[field.name.value] = getRawValue(field.value, values);
|
|
1792
|
+
}
|
|
1793
|
+
return res;
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
var normalizeArguments = (node) => {
|
|
1798
|
+
const normalizedArguments = {};
|
|
1799
|
+
if (node.field.arguments) {
|
|
1800
|
+
for (const argument of node.field.arguments) {
|
|
1801
|
+
const rawValue = getRawValue(argument.value, node.ctx.info.variableValues);
|
|
1802
|
+
const normalizedValue = normalizeValue(
|
|
1803
|
+
rawValue,
|
|
1804
|
+
summonByKey(node.fieldDefinition.arguments || [], "name.value", argument.name.value).type,
|
|
1805
|
+
node.ctx.info.schema
|
|
1806
|
+
);
|
|
1807
|
+
if (normalizedValue === void 0) {
|
|
1808
|
+
continue;
|
|
1809
|
+
}
|
|
1810
|
+
normalizedArguments[argument.name.value] = normalizedValue;
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
return normalizedArguments;
|
|
1814
|
+
};
|
|
1815
|
+
function normalizeValue(value2, type, schema) {
|
|
1816
|
+
switch (type.kind) {
|
|
1817
|
+
case import_graphql4.Kind.LIST_TYPE: {
|
|
1818
|
+
if (Array.isArray(value2)) {
|
|
1819
|
+
const res = [];
|
|
1820
|
+
for (const v of value2) {
|
|
1821
|
+
res.push(normalizeValue(v, type.type, schema));
|
|
1822
|
+
}
|
|
1823
|
+
return res;
|
|
1824
|
+
}
|
|
1825
|
+
const normalizedValue = normalizeValue(value2, type.type, schema);
|
|
1826
|
+
if (normalizedValue === void 0) {
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1829
|
+
return [normalizedValue];
|
|
1830
|
+
}
|
|
1831
|
+
case import_graphql4.Kind.NON_NULL_TYPE:
|
|
1832
|
+
return normalizeValue(value2, type.type, schema);
|
|
1833
|
+
case import_graphql4.Kind.NAMED_TYPE:
|
|
1834
|
+
return normalizeValueByTypeDefinition(
|
|
1835
|
+
value2,
|
|
1836
|
+
schema.getType(type.name.value)?.astNode,
|
|
1837
|
+
schema
|
|
1838
|
+
);
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
var normalizeValueByTypeDefinition = (value2, type, schema) => {
|
|
1842
|
+
if (!type || type.kind !== import_graphql4.Kind.INPUT_OBJECT_TYPE_DEFINITION) {
|
|
1843
|
+
return value2;
|
|
1844
|
+
}
|
|
1845
|
+
if (!value2) {
|
|
1846
|
+
return;
|
|
1847
|
+
}
|
|
1848
|
+
const res = {};
|
|
1849
|
+
for (const key of Object.keys(value2)) {
|
|
1850
|
+
const field = summonByKey(type.fields, "name.value", key);
|
|
1851
|
+
const normalizedValue = normalizeValue(value2[key], field.type, schema);
|
|
1852
|
+
if (normalizedValue === void 0) {
|
|
1853
|
+
continue;
|
|
1854
|
+
}
|
|
1855
|
+
res[key] = normalizedValue;
|
|
1856
|
+
}
|
|
1857
|
+
return res;
|
|
1858
|
+
};
|
|
1859
|
+
|
|
1860
|
+
// src/resolvers/filters.ts
|
|
1861
|
+
var SPECIAL_FILTERS = {
|
|
1862
|
+
GT: "?? > ?",
|
|
1863
|
+
GTE: "?? >= ?",
|
|
1864
|
+
LT: "?? < ?",
|
|
1865
|
+
LTE: "?? <= ?"
|
|
1866
|
+
};
|
|
1867
|
+
var applyFilters = (node, query, joins) => {
|
|
1868
|
+
const normalizedArguments = normalizeArguments(node);
|
|
1869
|
+
if (!normalizedArguments.orderBy) {
|
|
1870
|
+
if (node.model.defaultOrderBy) {
|
|
1871
|
+
normalizedArguments.orderBy = node.model.defaultOrderBy;
|
|
1872
|
+
} else if (node.model.creatable) {
|
|
1873
|
+
normalizedArguments.orderBy = [{ createdAt: "DESC" }];
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
if (node.model.deletable) {
|
|
1877
|
+
if (!normalizedArguments.where) {
|
|
1878
|
+
normalizedArguments.where = {};
|
|
1879
|
+
}
|
|
1880
|
+
if (normalizedArguments.where.deleted && (!Array.isArray(normalizedArguments.where.deleted) || normalizedArguments.where.deleted.some((v) => v))) {
|
|
1881
|
+
if (node.ctx.user.role !== "ADMIN") {
|
|
1882
|
+
throw new ForbiddenError("You cannot access deleted entries.");
|
|
1883
|
+
}
|
|
1884
|
+
} else {
|
|
1885
|
+
normalizedArguments.where.deleted = false;
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
const { limit, offset, orderBy, where, search } = normalizedArguments;
|
|
1889
|
+
if (limit) {
|
|
1890
|
+
query.limit(limit);
|
|
1891
|
+
}
|
|
1892
|
+
if (offset) {
|
|
1893
|
+
query.offset(offset);
|
|
1894
|
+
}
|
|
1895
|
+
if (orderBy) {
|
|
1896
|
+
applyOrderBy(node, orderBy, query);
|
|
1897
|
+
}
|
|
1898
|
+
if (where) {
|
|
1899
|
+
const ops = [];
|
|
1900
|
+
applyWhere2(node, where, ops, joins);
|
|
1901
|
+
apply(query, ops);
|
|
1902
|
+
}
|
|
1903
|
+
if (search) {
|
|
1904
|
+
applySearch(node, search, query);
|
|
1905
|
+
}
|
|
1906
|
+
};
|
|
1907
|
+
var applyWhere2 = (node, where, ops, joins) => {
|
|
1908
|
+
for (const key of Object.keys(where)) {
|
|
1909
|
+
const value2 = where[key];
|
|
1910
|
+
const specialFilter = key.match(/^(\w+)_(\w+)$/);
|
|
1911
|
+
if (specialFilter) {
|
|
1912
|
+
const [, actualKey, filter] = specialFilter;
|
|
1913
|
+
if (!SPECIAL_FILTERS[filter]) {
|
|
1914
|
+
throw new Error(`Invalid filter ${key}.`);
|
|
1915
|
+
}
|
|
1916
|
+
ops.push(
|
|
1917
|
+
(query) => query.whereRaw(SPECIAL_FILTERS[filter], [`${node.shortTableAlias}.${actualKey}`, value2])
|
|
1918
|
+
);
|
|
1919
|
+
continue;
|
|
1920
|
+
}
|
|
1921
|
+
const field = summonByName(node.model.fields, key);
|
|
1922
|
+
const fullKey = `${node.shortTableAlias}.${key}`;
|
|
1923
|
+
if (field.relation) {
|
|
1924
|
+
const relation = get(node.model.relationsByName, field.name);
|
|
1925
|
+
const tableAlias = `${node.model.name}__W__${key}`;
|
|
1926
|
+
const subNode = {
|
|
1927
|
+
ctx: node.ctx,
|
|
1928
|
+
model: relation.model,
|
|
1929
|
+
tableName: relation.model.name,
|
|
1930
|
+
tableAlias,
|
|
1931
|
+
shortTableAlias: node.ctx.aliases.getShort(tableAlias),
|
|
1932
|
+
foreignKey: relation.field.foreignKey
|
|
1933
|
+
};
|
|
1934
|
+
addJoin(joins, node.tableAlias, subNode.tableName, subNode.tableAlias, get(subNode, "foreignKey"), "id");
|
|
1935
|
+
applyWhere2(subNode, value2, ops, joins);
|
|
1936
|
+
continue;
|
|
1937
|
+
}
|
|
1938
|
+
if (Array.isArray(value2)) {
|
|
1939
|
+
if (field && field.list) {
|
|
1940
|
+
ops.push(
|
|
1941
|
+
(query) => ors(
|
|
1942
|
+
query,
|
|
1943
|
+
value2.map((v) => (subQuery) => subQuery.whereRaw("? = ANY(??)", [v, fullKey]))
|
|
1944
|
+
)
|
|
1945
|
+
);
|
|
1946
|
+
continue;
|
|
1947
|
+
}
|
|
1948
|
+
if (value2.some((v) => v === null)) {
|
|
1949
|
+
if (value2.some((v) => v !== null)) {
|
|
1950
|
+
ops.push(
|
|
1951
|
+
(query) => ors(query, [
|
|
1952
|
+
(subQuery) => subQuery.whereIn(fullKey, value2.filter((v) => v !== null)),
|
|
1953
|
+
(subQuery) => subQuery.whereNull(fullKey)
|
|
1954
|
+
])
|
|
1955
|
+
);
|
|
1956
|
+
continue;
|
|
1957
|
+
}
|
|
1958
|
+
ops.push((query) => query.whereNull(fullKey));
|
|
1959
|
+
continue;
|
|
1960
|
+
}
|
|
1961
|
+
ops.push((query) => query.whereIn(fullKey, value2));
|
|
1962
|
+
continue;
|
|
1963
|
+
}
|
|
1964
|
+
ops.push((query) => query.where({ [fullKey]: value2 }));
|
|
1965
|
+
}
|
|
1966
|
+
};
|
|
1967
|
+
var applySearch = (node, search, query) => ors(
|
|
1968
|
+
query,
|
|
1969
|
+
node.model.fields.filter(({ searchable }) => searchable).map(
|
|
1970
|
+
({ name: name2 }) => (query2) => query2.whereILike(`${node.shortTableAlias}.${name2}`, `%${search}%`)
|
|
1971
|
+
)
|
|
1972
|
+
);
|
|
1973
|
+
var applyOrderBy = (node, orderBy, query) => {
|
|
1974
|
+
for (const vals of orderBy) {
|
|
1975
|
+
const keys = Object.keys(vals);
|
|
1976
|
+
if (keys.length !== 1) {
|
|
1977
|
+
throw new UserInputError(`You need to specify exactly 1 value to order by for each orderBy entry.`);
|
|
1978
|
+
}
|
|
1979
|
+
const key = keys[0];
|
|
1980
|
+
const value2 = vals[key];
|
|
1981
|
+
query.orderBy(`${node.shortTableAlias}.${key}`, value2);
|
|
1982
|
+
}
|
|
1983
|
+
};
|
|
1984
|
+
|
|
1985
|
+
// src/resolvers/mutations.ts
|
|
1986
|
+
var import_uuid = require("uuid");
|
|
1987
|
+
|
|
1988
|
+
// src/resolvers/resolver.ts
|
|
1989
|
+
var import_cloneDeep = __toESM(require("lodash/cloneDeep"), 1);
|
|
1990
|
+
var import_flatMap2 = __toESM(require("lodash/flatMap"), 1);
|
|
1991
|
+
|
|
1992
|
+
// src/resolvers/node.ts
|
|
1993
|
+
var getResolverNode = ({
|
|
1994
|
+
ctx,
|
|
1995
|
+
node,
|
|
1996
|
+
tableAlias,
|
|
1997
|
+
baseTypeDefinition,
|
|
1998
|
+
typeName
|
|
1999
|
+
}) => ({
|
|
2000
|
+
ctx,
|
|
2001
|
+
tableName: typeName,
|
|
2002
|
+
tableAlias,
|
|
2003
|
+
shortTableAlias: ctx.aliases.getShort(tableAlias),
|
|
2004
|
+
baseTypeDefinition,
|
|
2005
|
+
baseModel: ctx.models.find((model) => model.name === baseTypeDefinition.name.value),
|
|
2006
|
+
typeDefinition: getType(ctx.info.schema, typeName),
|
|
2007
|
+
model: summonByName(ctx.models, typeName),
|
|
2008
|
+
selectionSet: get(node.selectionSet, "selections")
|
|
2009
|
+
});
|
|
2010
|
+
var getRootFieldNode = ({
|
|
2011
|
+
ctx,
|
|
2012
|
+
node,
|
|
2013
|
+
baseTypeDefinition
|
|
2014
|
+
}) => {
|
|
2015
|
+
const fieldName = node.name.value;
|
|
2016
|
+
const fieldDefinition = summonByKey(baseTypeDefinition.fields || [], "name.value", fieldName);
|
|
2017
|
+
const typeName = getTypeName(fieldDefinition.type);
|
|
2018
|
+
return {
|
|
2019
|
+
ctx,
|
|
2020
|
+
tableName: typeName,
|
|
2021
|
+
tableAlias: typeName,
|
|
2022
|
+
shortTableAlias: ctx.aliases.getShort(typeName),
|
|
2023
|
+
baseTypeDefinition,
|
|
2024
|
+
typeDefinition: getType(ctx.info.schema, typeName),
|
|
2025
|
+
model: summonByName(ctx.models, typeName),
|
|
2026
|
+
selectionSet: get(node.selectionSet, "selections"),
|
|
2027
|
+
field: node,
|
|
2028
|
+
fieldDefinition,
|
|
2029
|
+
isList: isListType(fieldDefinition.type)
|
|
2030
|
+
};
|
|
2031
|
+
};
|
|
2032
|
+
var getSimpleFields = (node) => {
|
|
2033
|
+
return node.selectionSet.filter(isFieldNode).filter((selection) => {
|
|
2034
|
+
if (!selection.selectionSet) {
|
|
2035
|
+
return true;
|
|
2036
|
+
}
|
|
2037
|
+
return node.model.fields.some(({ json, name: name2 }) => json && name2 === selection.name.value);
|
|
2038
|
+
});
|
|
2039
|
+
};
|
|
2040
|
+
var getInlineFragments = (node) => node.selectionSet.filter(isInlineFragmentNode).map(
|
|
2041
|
+
(subNode) => getResolverNode({
|
|
2042
|
+
ctx: node.ctx,
|
|
2043
|
+
node: subNode,
|
|
2044
|
+
tableAlias: node.tableAlias + "__" + getFragmentTypeName(subNode),
|
|
2045
|
+
baseTypeDefinition: node.baseTypeDefinition,
|
|
2046
|
+
typeName: getFragmentTypeName(subNode)
|
|
2047
|
+
})
|
|
2048
|
+
);
|
|
2049
|
+
var getFragmentSpreads = (node) => node.selectionSet.filter(isFragmentSpreadNode).map(
|
|
2050
|
+
(subNode) => getResolverNode({
|
|
2051
|
+
ctx: node.ctx,
|
|
2052
|
+
node: node.ctx.info.fragments[subNode.name.value],
|
|
2053
|
+
tableAlias: node.tableAlias,
|
|
2054
|
+
baseTypeDefinition: node.baseTypeDefinition,
|
|
2055
|
+
typeName: node.model.name
|
|
2056
|
+
})
|
|
2057
|
+
);
|
|
2058
|
+
var getJoins = (node, toMany) => {
|
|
2059
|
+
const nodes = [];
|
|
2060
|
+
for (const subNode of node.selectionSet.filter(isFieldNode).filter(({ selectionSet }) => selectionSet)) {
|
|
2061
|
+
const ctx = node.ctx;
|
|
2062
|
+
const baseTypeDefinition = node.typeDefinition;
|
|
2063
|
+
const fieldName = subNode.name.value;
|
|
2064
|
+
const fieldNameOrAlias = getNameOrAlias(subNode);
|
|
2065
|
+
const fieldDefinition = summonByKey(baseTypeDefinition.fields || [], "name.value", fieldName);
|
|
2066
|
+
const typeName = getTypeName(fieldDefinition.type);
|
|
2067
|
+
if (isJsonObjectModel(summonByName(ctx.rawModels, typeName))) {
|
|
2068
|
+
continue;
|
|
2069
|
+
}
|
|
2070
|
+
const baseModel = summonByName(ctx.models, baseTypeDefinition.name.value);
|
|
2071
|
+
let foreignKey;
|
|
2072
|
+
if (toMany) {
|
|
2073
|
+
const reverseRelation = baseModel.reverseRelationsByName[fieldName];
|
|
2074
|
+
if (!reverseRelation) {
|
|
2075
|
+
continue;
|
|
2076
|
+
}
|
|
2077
|
+
foreignKey = reverseRelation.foreignKey;
|
|
2078
|
+
} else {
|
|
2079
|
+
const modelField = baseModel.fieldsByName[fieldName];
|
|
2080
|
+
if (!modelField || modelField.raw) {
|
|
2081
|
+
continue;
|
|
2082
|
+
}
|
|
2083
|
+
foreignKey = modelField.foreignKey;
|
|
2084
|
+
}
|
|
2085
|
+
const tableAlias = node.tableAlias + "__" + fieldNameOrAlias;
|
|
2086
|
+
nodes.push({
|
|
2087
|
+
ctx,
|
|
2088
|
+
tableName: typeName,
|
|
2089
|
+
tableAlias,
|
|
2090
|
+
shortTableAlias: ctx.aliases.getShort(tableAlias),
|
|
2091
|
+
baseTypeDefinition,
|
|
2092
|
+
baseModel,
|
|
2093
|
+
typeDefinition: getType(ctx.info.schema, typeName),
|
|
2094
|
+
model: summonByName(ctx.models, typeName),
|
|
2095
|
+
selectionSet: get(subNode.selectionSet, "selections"),
|
|
2096
|
+
field: subNode,
|
|
2097
|
+
fieldDefinition,
|
|
2098
|
+
foreignKey,
|
|
2099
|
+
isList: isListType(fieldDefinition.type)
|
|
2100
|
+
});
|
|
2101
|
+
}
|
|
2102
|
+
return nodes;
|
|
2103
|
+
};
|
|
2104
|
+
|
|
2105
|
+
// src/resolvers/resolver.ts
|
|
2106
|
+
var queryResolver = (_parent, _args, ctx, info) => resolve({ ...ctx, info, aliases: new AliasGenerator() });
|
|
2107
|
+
var resolve = async (ctx, id) => {
|
|
2108
|
+
const fieldNode = summonByKey(ctx.info.fieldNodes, "name.value", ctx.info.fieldName);
|
|
2109
|
+
const baseTypeDefinition = get(
|
|
2110
|
+
ctx.info.operation.operation === "query" ? ctx.info.schema.getQueryType() : ctx.info.schema.getMutationType(),
|
|
2111
|
+
"astNode"
|
|
2112
|
+
);
|
|
2113
|
+
const node = getRootFieldNode({
|
|
2114
|
+
ctx,
|
|
2115
|
+
node: fieldNode,
|
|
2116
|
+
baseTypeDefinition
|
|
2117
|
+
});
|
|
2118
|
+
const { query, verifiedPermissionStacks } = await buildQuery(node);
|
|
2119
|
+
if (ctx.info.fieldName === "me") {
|
|
2120
|
+
query.where({ [`${node.shortTableAlias}.id`]: node.ctx.user.id });
|
|
2121
|
+
}
|
|
2122
|
+
if (!node.isList) {
|
|
2123
|
+
query.limit(1);
|
|
2124
|
+
}
|
|
2125
|
+
if (id) {
|
|
2126
|
+
query.where({ id });
|
|
2127
|
+
}
|
|
2128
|
+
const raw = await query;
|
|
2129
|
+
const res = hydrate(node, raw);
|
|
2130
|
+
await applySubQueries(node, res, verifiedPermissionStacks);
|
|
2131
|
+
if (node.isList) {
|
|
2132
|
+
return res;
|
|
2133
|
+
}
|
|
2134
|
+
if (!res[0]) {
|
|
2135
|
+
throw new NotFoundError("Entity not found");
|
|
2136
|
+
}
|
|
2137
|
+
return res[0];
|
|
2138
|
+
};
|
|
2139
|
+
var buildQuery = async (node, parentVerifiedPermissionStacks) => {
|
|
2140
|
+
const { tableAlias, shortTableAlias, tableName, model, ctx } = node;
|
|
2141
|
+
const query = ctx.knex.fromRaw(`"${tableName}" as "${shortTableAlias}"`);
|
|
2142
|
+
const joins = {};
|
|
2143
|
+
applyFilters(node, query, joins);
|
|
2144
|
+
applySelects(node, query, joins);
|
|
2145
|
+
applyJoins(node.ctx.aliases, query, joins);
|
|
2146
|
+
const tables = [
|
|
2147
|
+
[model.name, tableAlias],
|
|
2148
|
+
...Object.keys(joins).map((tableName2) => tableName2.split(":"))
|
|
2149
|
+
];
|
|
2150
|
+
const verifiedPermissionStacks = {};
|
|
2151
|
+
for (const [table, alias] of tables) {
|
|
2152
|
+
const verifiedPermissionStack = applyPermissions(
|
|
2153
|
+
ctx,
|
|
2154
|
+
table,
|
|
2155
|
+
node.ctx.aliases.getShort(alias),
|
|
2156
|
+
query,
|
|
2157
|
+
"READ",
|
|
2158
|
+
parentVerifiedPermissionStacks?.[alias.split("__").slice(0, -1).join("__")]
|
|
2159
|
+
);
|
|
2160
|
+
if (typeof verifiedPermissionStack !== "boolean") {
|
|
2161
|
+
verifiedPermissionStacks[alias] = verifiedPermissionStack;
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
return { query, verifiedPermissionStacks };
|
|
2165
|
+
};
|
|
2166
|
+
var applySelects = (node, query, joins) => {
|
|
2167
|
+
query.select(
|
|
2168
|
+
...[
|
|
2169
|
+
{ field: "id", alias: ID_ALIAS },
|
|
2170
|
+
...getSimpleFields(node).filter((n) => {
|
|
2171
|
+
const field = node.model.fields.find(({ name: name2 }) => name2 === n.name.value);
|
|
2172
|
+
if (!field || field.relation || field.raw) {
|
|
2173
|
+
return false;
|
|
2174
|
+
}
|
|
2175
|
+
if (field.queriableBy && !field.queriableBy.includes(node.ctx.user.role)) {
|
|
2176
|
+
throw new PermissionError("READ", `${node.model.name}'s field "${field.name}"`);
|
|
2177
|
+
}
|
|
2178
|
+
return true;
|
|
2179
|
+
}).map((n) => ({ field: n.name.value, alias: getNameOrAlias(n) }))
|
|
2180
|
+
].map(
|
|
2181
|
+
({ field, alias }) => `${node.shortTableAlias}.${field} as ${node.shortTableAlias}__${alias}`
|
|
2182
|
+
)
|
|
2183
|
+
);
|
|
2184
|
+
for (const subNode of getInlineFragments(node)) {
|
|
2185
|
+
applySelects(subNode, query, joins);
|
|
2186
|
+
}
|
|
2187
|
+
for (const subNode of getFragmentSpreads(node)) {
|
|
2188
|
+
applySelects(subNode, query, joins);
|
|
2189
|
+
}
|
|
2190
|
+
for (const subNode of getJoins(node, false)) {
|
|
2191
|
+
addJoin(joins, node.tableAlias, subNode.tableName, subNode.tableAlias, get(subNode, "foreignKey"), "id");
|
|
2192
|
+
applySelects(subNode, query, joins);
|
|
2193
|
+
}
|
|
2194
|
+
};
|
|
2195
|
+
var applySubQueries = async (node, entries, parentVerifiedPermissionStacks) => {
|
|
2196
|
+
if (!entries.length) {
|
|
2197
|
+
return;
|
|
2198
|
+
}
|
|
2199
|
+
const entriesById = {};
|
|
2200
|
+
for (const entry of entries) {
|
|
2201
|
+
if (!entriesById[entry[ID_ALIAS]]) {
|
|
2202
|
+
entriesById[entry[ID_ALIAS]] = [];
|
|
2203
|
+
}
|
|
2204
|
+
entriesById[entry[ID_ALIAS]].push(entry);
|
|
2205
|
+
}
|
|
2206
|
+
const ids = Object.keys(entriesById);
|
|
2207
|
+
for (const subNode of getJoins(node, true)) {
|
|
2208
|
+
const fieldName = getNameOrAlias(subNode.field);
|
|
2209
|
+
const isList = isListType(subNode.fieldDefinition.type);
|
|
2210
|
+
entries.forEach((entry) => entry[fieldName] = isList ? [] : null);
|
|
2211
|
+
const foreignKey = get(subNode, "foreignKey");
|
|
2212
|
+
const { query, verifiedPermissionStacks } = await buildQuery(subNode, parentVerifiedPermissionStacks);
|
|
2213
|
+
const queries = ids.map(
|
|
2214
|
+
(id) => query.clone().select(`${subNode.shortTableAlias}.${foreignKey} as ${subNode.shortTableAlias}__${foreignKey}`).where({ [`${subNode.shortTableAlias}.${foreignKey}`]: id })
|
|
2215
|
+
);
|
|
2216
|
+
const rawChildren = (await Promise.all(queries)).flat();
|
|
2217
|
+
const children = hydrate(subNode, rawChildren);
|
|
2218
|
+
for (const child of children) {
|
|
2219
|
+
for (const entry of entriesById[child[foreignKey]]) {
|
|
2220
|
+
if (isList) {
|
|
2221
|
+
entry[fieldName].push((0, import_cloneDeep.default)(child));
|
|
2222
|
+
} else {
|
|
2223
|
+
entry[fieldName] = (0, import_cloneDeep.default)(child);
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
await applySubQueries(
|
|
2228
|
+
subNode,
|
|
2229
|
+
(0, import_flatMap2.default)(
|
|
2230
|
+
entries.map((entry) => {
|
|
2231
|
+
const children2 = entry[fieldName];
|
|
2232
|
+
return isList ? children2 : children2 ? [children2] : [];
|
|
2233
|
+
})
|
|
2234
|
+
),
|
|
2235
|
+
verifiedPermissionStacks
|
|
2236
|
+
);
|
|
2237
|
+
}
|
|
2238
|
+
for (const subNode of getInlineFragments(node)) {
|
|
2239
|
+
await applySubQueries(subNode, entries, parentVerifiedPermissionStacks);
|
|
2240
|
+
}
|
|
2241
|
+
for (const subNode of getFragmentSpreads(node)) {
|
|
2242
|
+
await applySubQueries(subNode, entries, parentVerifiedPermissionStacks);
|
|
2243
|
+
}
|
|
2244
|
+
for (const subNode of getJoins(node, false)) {
|
|
2245
|
+
await applySubQueries(
|
|
2246
|
+
subNode,
|
|
2247
|
+
entries.map((item) => item[getNameOrAlias(subNode.field)]).filter(Boolean),
|
|
2248
|
+
parentVerifiedPermissionStacks
|
|
2249
|
+
);
|
|
2250
|
+
}
|
|
2251
|
+
};
|
|
2252
|
+
|
|
2253
|
+
// src/resolvers/mutations.ts
|
|
2254
|
+
var mutationResolver = async (_parent, args2, partialCtx, info) => {
|
|
2255
|
+
return await partialCtx.knex.transaction(async (knex) => {
|
|
2256
|
+
const [, mutation, modelName] = it(info.fieldName.match(/^(create|update|delete|restore)(.+)$/));
|
|
2257
|
+
const ctx = { ...partialCtx, knex, info, aliases: new AliasGenerator() };
|
|
2258
|
+
const model = summonByName(ctx.models, modelName);
|
|
2259
|
+
switch (mutation) {
|
|
2260
|
+
case "create":
|
|
2261
|
+
return await create(model, args2, ctx);
|
|
2262
|
+
case "update":
|
|
2263
|
+
return await update(model, args2, ctx);
|
|
2264
|
+
case "delete":
|
|
2265
|
+
return await del(model, args2, ctx);
|
|
2266
|
+
case "restore":
|
|
2267
|
+
return await restore(model, args2, ctx);
|
|
2268
|
+
}
|
|
2269
|
+
});
|
|
2270
|
+
};
|
|
2271
|
+
var create = async (model, { data: input2 }, ctx) => {
|
|
2272
|
+
const normalizedInput = { ...input2 };
|
|
2273
|
+
normalizedInput.id = (0, import_uuid.v4)();
|
|
2274
|
+
normalizedInput.createdAt = ctx.now;
|
|
2275
|
+
normalizedInput.createdById = ctx.user.id;
|
|
2276
|
+
sanitize(ctx, model, normalizedInput);
|
|
2277
|
+
await checkCanWrite(ctx, model, normalizedInput, "CREATE");
|
|
2278
|
+
await ctx.handleUploads?.(normalizedInput);
|
|
2279
|
+
const data = { prev: {}, input: input2, normalizedInput, next: normalizedInput };
|
|
2280
|
+
await ctx.mutationHook?.(model, "create", "before", data, ctx);
|
|
2281
|
+
await ctx.knex(model.name).insert(normalizedInput);
|
|
2282
|
+
await createRevision(model, normalizedInput, ctx);
|
|
2283
|
+
await ctx.mutationHook?.(model, "create", "after", data, ctx);
|
|
2284
|
+
return await resolve(ctx, normalizedInput.id);
|
|
2285
|
+
};
|
|
2286
|
+
var update = async (model, { where, data: input2 }, ctx) => {
|
|
2287
|
+
if (Object.keys(where).length === 0) {
|
|
2288
|
+
throw new Error(`No ${model.name} specified.`);
|
|
2289
|
+
}
|
|
2290
|
+
const normalizedInput = { ...input2 };
|
|
2291
|
+
sanitize(ctx, model, normalizedInput);
|
|
2292
|
+
const prev = await getEntityToMutate(ctx, model, where, "UPDATE");
|
|
2293
|
+
for (const key of Object.keys(normalizedInput)) {
|
|
2294
|
+
if (normalizedInput[key] === prev[key]) {
|
|
2295
|
+
delete normalizedInput[key];
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
if (Object.keys(normalizedInput).length > 0) {
|
|
2299
|
+
await checkCanWrite(ctx, model, normalizedInput, "UPDATE");
|
|
2300
|
+
await ctx.handleUploads?.(normalizedInput);
|
|
2301
|
+
const next = { ...prev, ...normalizedInput };
|
|
2302
|
+
const data = { prev, input: input2, normalizedInput, next };
|
|
2303
|
+
await ctx.mutationHook?.(model, "update", "before", data, ctx);
|
|
2304
|
+
await ctx.knex(model.name).where(where).update(normalizedInput);
|
|
2305
|
+
await createRevision(model, next, ctx);
|
|
2306
|
+
await ctx.mutationHook?.(model, "update", "after", data, ctx);
|
|
2307
|
+
}
|
|
2308
|
+
return await resolve(ctx);
|
|
2309
|
+
};
|
|
2310
|
+
var del = async (model, { where, dryRun }, ctx) => {
|
|
2311
|
+
if (Object.keys(where).length === 0) {
|
|
2312
|
+
throw new Error(`No ${model.name} specified.`);
|
|
2313
|
+
}
|
|
2314
|
+
const entity = await getEntityToMutate(ctx, model, where, "DELETE");
|
|
2315
|
+
if (entity.deleted) {
|
|
2316
|
+
throw new ForbiddenError("Entity is already deleted.");
|
|
2317
|
+
}
|
|
2318
|
+
const toDelete = {};
|
|
2319
|
+
const toUnlink = {};
|
|
2320
|
+
const beforeHooks = [];
|
|
2321
|
+
const mutations = [];
|
|
2322
|
+
const afterHooks = [];
|
|
2323
|
+
const deleteCascade = async (currentModel, entity2) => {
|
|
2324
|
+
if (entity2.deleted) {
|
|
2325
|
+
return;
|
|
2326
|
+
}
|
|
2327
|
+
if (dryRun) {
|
|
2328
|
+
if (!(currentModel.name in toDelete)) {
|
|
2329
|
+
toDelete[currentModel.name] = {};
|
|
2330
|
+
}
|
|
2331
|
+
toDelete[currentModel.name][entity2.id] = entity2[currentModel.displayField || "id"] || entity2.id;
|
|
2332
|
+
} else {
|
|
2333
|
+
const normalizedInput = { deleted: true, deletedAt: ctx.now, deletedById: ctx.user.id };
|
|
2334
|
+
const data = { prev: entity2, input: {}, normalizedInput, next: { ...entity2, ...normalizedInput } };
|
|
2335
|
+
if (ctx.mutationHook) {
|
|
2336
|
+
beforeHooks.push(async () => {
|
|
2337
|
+
await ctx.mutationHook(currentModel, "delete", "before", data, ctx);
|
|
2338
|
+
});
|
|
2339
|
+
}
|
|
2340
|
+
mutations.push(async () => {
|
|
2341
|
+
await ctx.knex(currentModel.name).where({ id: entity2.id }).update(normalizedInput);
|
|
2342
|
+
await createRevision(currentModel, { ...entity2, deleted: true }, ctx);
|
|
2343
|
+
});
|
|
2344
|
+
if (ctx.mutationHook) {
|
|
2345
|
+
afterHooks.push(async () => {
|
|
2346
|
+
await ctx.mutationHook(currentModel, "delete", "after", data, ctx);
|
|
2347
|
+
});
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
for (const {
|
|
2351
|
+
model: descendantModel,
|
|
2352
|
+
foreignKey,
|
|
2353
|
+
field: { name: name2, onDelete }
|
|
2354
|
+
} of currentModel.reverseRelations) {
|
|
2355
|
+
const query = ctx.knex(descendantModel.name).where({ [foreignKey]: entity2.id });
|
|
2356
|
+
switch (onDelete) {
|
|
2357
|
+
case "set-null": {
|
|
2358
|
+
const descendants = await query;
|
|
2359
|
+
for (const descendant of descendants) {
|
|
2360
|
+
if (dryRun) {
|
|
2361
|
+
if (!toUnlink[descendantModel.name]) {
|
|
2362
|
+
toUnlink[descendantModel.name] = {};
|
|
2363
|
+
}
|
|
2364
|
+
if (!toUnlink[descendantModel.name][descendant.id]) {
|
|
2365
|
+
toUnlink[descendantModel.name][descendant.id] = {
|
|
2366
|
+
display: descendant[descendantModel.displayField || "id"] || entity2.id,
|
|
2367
|
+
fields: []
|
|
2368
|
+
};
|
|
2369
|
+
}
|
|
2370
|
+
toUnlink[descendantModel.name][descendant.id].fields.push(name2);
|
|
2371
|
+
} else {
|
|
2372
|
+
mutations.push(async () => {
|
|
2373
|
+
await ctx.knex(descendantModel.name).where({ id: descendant.id }).update({
|
|
2374
|
+
[`${name2}Id`]: null
|
|
2375
|
+
});
|
|
2376
|
+
});
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
break;
|
|
2380
|
+
}
|
|
2381
|
+
case "cascade":
|
|
2382
|
+
default: {
|
|
2383
|
+
applyPermissions(ctx, descendantModel.name, descendantModel.name, query, "DELETE");
|
|
2384
|
+
const descendants = await query;
|
|
2385
|
+
if (descendants.length && !descendantModel.deletable) {
|
|
2386
|
+
throw new ForbiddenError(`This ${model.name} depends on a ${descendantModel.name} which cannot be deleted.`);
|
|
2387
|
+
}
|
|
2388
|
+
for (const descendant of descendants) {
|
|
2389
|
+
await deleteCascade(descendantModel, descendant);
|
|
2390
|
+
}
|
|
2391
|
+
break;
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
};
|
|
2396
|
+
await deleteCascade(model, entity);
|
|
2397
|
+
for (const callback of [...beforeHooks, ...mutations, ...afterHooks]) {
|
|
2398
|
+
await callback();
|
|
2399
|
+
}
|
|
2400
|
+
if (dryRun) {
|
|
2401
|
+
throw new GraphQLError(`Delete dry run:`, {
|
|
2402
|
+
code: "DELETE_DRY_RUN",
|
|
2403
|
+
toDelete,
|
|
2404
|
+
toUnlink
|
|
2405
|
+
});
|
|
2406
|
+
}
|
|
2407
|
+
return entity.id;
|
|
2408
|
+
};
|
|
2409
|
+
var restore = async (model, { where }, ctx) => {
|
|
2410
|
+
if (Object.keys(where).length === 0) {
|
|
2411
|
+
throw new Error(`No ${model.name} specified.`);
|
|
2412
|
+
}
|
|
2413
|
+
const entity = await getEntityToMutate(ctx, model, where, "RESTORE");
|
|
2414
|
+
if (!entity.deleted) {
|
|
2415
|
+
throw new ForbiddenError("Entity is not deleted.");
|
|
2416
|
+
}
|
|
2417
|
+
const beforeHooks = [];
|
|
2418
|
+
const mutations = [];
|
|
2419
|
+
const afterHooks = [];
|
|
2420
|
+
const restoreCascade = async (currentModel, relatedEntity) => {
|
|
2421
|
+
if (!relatedEntity.deleted || !relatedEntity.deletedAt || !relatedEntity.deletedAt.equals(entity.deletedAt)) {
|
|
2422
|
+
return;
|
|
2423
|
+
}
|
|
2424
|
+
const normalizedInput = { deleted: false, deletedAt: null, deletedById: null };
|
|
2425
|
+
const data = { prev: relatedEntity, input: {}, normalizedInput, next: { ...relatedEntity, ...normalizedInput } };
|
|
2426
|
+
if (ctx.mutationHook) {
|
|
2427
|
+
beforeHooks.push(async () => {
|
|
2428
|
+
await ctx.mutationHook(model, "restore", "before", data, ctx);
|
|
2429
|
+
});
|
|
2430
|
+
}
|
|
2431
|
+
mutations.push(async () => {
|
|
2432
|
+
await ctx.knex(currentModel.name).where({ id: relatedEntity.id }).update(normalizedInput);
|
|
2433
|
+
await createRevision(currentModel, { ...relatedEntity, deleted: false }, ctx);
|
|
2434
|
+
});
|
|
2435
|
+
if (ctx.mutationHook) {
|
|
2436
|
+
afterHooks.push(async () => {
|
|
2437
|
+
await ctx.mutationHook(model, "restore", "after", data, ctx);
|
|
2438
|
+
});
|
|
2439
|
+
}
|
|
2440
|
+
for (const { model: descendantModel, foreignKey } of currentModel.reverseRelations.filter(
|
|
2441
|
+
({ model: { deletable } }) => deletable
|
|
2442
|
+
)) {
|
|
2443
|
+
const query = ctx.knex(descendantModel.name).where({ [foreignKey]: relatedEntity.id });
|
|
2444
|
+
applyPermissions(ctx, descendantModel.name, descendantModel.name, query, "RESTORE");
|
|
2445
|
+
const descendants = await query;
|
|
2446
|
+
for (const descendant of descendants) {
|
|
2447
|
+
await restoreCascade(descendantModel, descendant);
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
};
|
|
2451
|
+
await restoreCascade(model, entity);
|
|
2452
|
+
for (const callback of [...beforeHooks, ...mutations, ...afterHooks]) {
|
|
2453
|
+
await callback();
|
|
2454
|
+
}
|
|
2455
|
+
return entity.id;
|
|
2456
|
+
};
|
|
2457
|
+
var createRevision = async (model, data, ctx) => {
|
|
2458
|
+
if (model.updatable) {
|
|
2459
|
+
const revisionData = {
|
|
2460
|
+
id: (0, import_uuid.v4)(),
|
|
2461
|
+
[`${typeToField(model.name)}Id`]: data.id,
|
|
2462
|
+
createdAt: ctx.now,
|
|
2463
|
+
createdById: ctx.user.id
|
|
2464
|
+
};
|
|
2465
|
+
if (model.deletable) {
|
|
2466
|
+
revisionData.deleted = data.deleted || false;
|
|
2467
|
+
}
|
|
2468
|
+
for (const { name: name2, relation, nonNull: nonNull2, ...field } of model.fields.filter(({ updatable }) => updatable)) {
|
|
2469
|
+
const col = relation ? `${name2}Id` : name2;
|
|
2470
|
+
if (nonNull2 && (!(col in data) || col === void 0 || col === null)) {
|
|
2471
|
+
revisionData[col] = get(field, "default");
|
|
2472
|
+
} else {
|
|
2473
|
+
revisionData[col] = data[col];
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
await ctx.knex(`${model.name}Revision`).insert(revisionData);
|
|
2477
|
+
}
|
|
2478
|
+
};
|
|
2479
|
+
var sanitize = (ctx, model, data) => {
|
|
2480
|
+
if (model.updatable) {
|
|
2481
|
+
data.updatedAt = ctx.now;
|
|
2482
|
+
data.updatedById = ctx.user.id;
|
|
2483
|
+
}
|
|
2484
|
+
for (const key of Object.keys(data)) {
|
|
2485
|
+
const field = model.fields.find(({ name: name2 }) => name2 === key);
|
|
2486
|
+
if (!field) {
|
|
2487
|
+
continue;
|
|
2488
|
+
}
|
|
2489
|
+
if (isEndOfDay(field) && data[key]) {
|
|
2490
|
+
data[key] = data[key].endOf("day");
|
|
2491
|
+
continue;
|
|
2492
|
+
}
|
|
2493
|
+
if (isEnumList(ctx.rawModels, field) && Array.isArray(data[key])) {
|
|
2494
|
+
data[key] = `{${data[key].join(",")}}`;
|
|
2495
|
+
continue;
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
};
|
|
2499
|
+
var isEndOfDay = (field) => field?.endOfDay === true && field?.dateTimeType === "date" && field?.type === "DateTime";
|
|
2500
|
+
|
|
2501
|
+
// src/resolvers/resolvers.ts
|
|
2502
|
+
var getResolvers = (models) => ({
|
|
2503
|
+
Query: merge([
|
|
2504
|
+
{
|
|
2505
|
+
me: queryResolver
|
|
2506
|
+
},
|
|
2507
|
+
...models.filter(({ queriable }) => queriable).map((model) => ({
|
|
2508
|
+
[typeToField(model.name)]: queryResolver
|
|
2509
|
+
})),
|
|
2510
|
+
...models.filter(({ listQueriable }) => listQueriable).map((model) => ({
|
|
2511
|
+
[getModelPluralField(model)]: queryResolver
|
|
2512
|
+
}))
|
|
2513
|
+
]),
|
|
2514
|
+
Mutation: merge([
|
|
2515
|
+
...models.filter(({ creatable }) => creatable).map((model) => ({
|
|
2516
|
+
[`create${model.name}`]: mutationResolver
|
|
2517
|
+
})),
|
|
2518
|
+
...models.filter(({ updatable }) => updatable).map((model) => ({
|
|
2519
|
+
[`update${model.name}`]: mutationResolver
|
|
2520
|
+
})),
|
|
2521
|
+
...models.filter(({ deletable }) => deletable).map((model) => ({
|
|
2522
|
+
[`delete${model.name}`]: mutationResolver,
|
|
2523
|
+
[`restore${model.name}`]: mutationResolver
|
|
2524
|
+
}))
|
|
2525
|
+
])
|
|
2526
|
+
});
|
|
2527
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2528
|
+
0 && (module.exports = {
|
|
2529
|
+
AliasGenerator,
|
|
2530
|
+
Enum,
|
|
2531
|
+
ForbiddenError,
|
|
2532
|
+
GraphQLError,
|
|
2533
|
+
ID_ALIAS,
|
|
2534
|
+
MigrationGenerator,
|
|
2535
|
+
NotFoundError,
|
|
2536
|
+
PermissionError,
|
|
2537
|
+
SPECIAL_FILTERS,
|
|
2538
|
+
UserInputError,
|
|
2539
|
+
actionableRelations,
|
|
2540
|
+
addJoin,
|
|
2541
|
+
and,
|
|
2542
|
+
apply,
|
|
2543
|
+
applyFilters,
|
|
2544
|
+
applyJoins,
|
|
2545
|
+
applyPermissions,
|
|
2546
|
+
args,
|
|
2547
|
+
checkCanWrite,
|
|
2548
|
+
directive,
|
|
2549
|
+
directives,
|
|
2550
|
+
displayField,
|
|
2551
|
+
document,
|
|
2552
|
+
enm,
|
|
2553
|
+
fieldType,
|
|
2554
|
+
fields,
|
|
2555
|
+
generate,
|
|
2556
|
+
generateDefinitions,
|
|
2557
|
+
generateMutations,
|
|
2558
|
+
generatePermissions,
|
|
2559
|
+
get,
|
|
2560
|
+
getEditEntityRelationsQuery,
|
|
2561
|
+
getEntityListQuery,
|
|
2562
|
+
getEntityQuery,
|
|
2563
|
+
getEntityToMutate,
|
|
2564
|
+
getFindEntityQuery,
|
|
2565
|
+
getFragmentSpreads,
|
|
2566
|
+
getFragmentTypeName,
|
|
2567
|
+
getInlineFragments,
|
|
2568
|
+
getJoins,
|
|
2569
|
+
getLabel,
|
|
2570
|
+
getManyToManyRelation,
|
|
2571
|
+
getManyToManyRelations,
|
|
2572
|
+
getManyToManyRelationsQuery,
|
|
2573
|
+
getModelLabel,
|
|
2574
|
+
getModelLabelPlural,
|
|
2575
|
+
getModelPlural,
|
|
2576
|
+
getModelPluralField,
|
|
2577
|
+
getModelSlug,
|
|
2578
|
+
getModels,
|
|
2579
|
+
getMutationQuery,
|
|
2580
|
+
getNameOrAlias,
|
|
2581
|
+
getPermissionStack,
|
|
2582
|
+
getResolverNode,
|
|
2583
|
+
getResolvers,
|
|
2584
|
+
getRootFieldNode,
|
|
2585
|
+
getSimpleFields,
|
|
2586
|
+
getString,
|
|
2587
|
+
getType,
|
|
2588
|
+
getTypeName,
|
|
2589
|
+
getUpdateEntityQuery,
|
|
2590
|
+
gql,
|
|
2591
|
+
hash,
|
|
2592
|
+
hydrate,
|
|
2593
|
+
iface,
|
|
2594
|
+
input,
|
|
2595
|
+
inputValue,
|
|
2596
|
+
inputValues,
|
|
2597
|
+
isCreatable,
|
|
2598
|
+
isCreatableBy,
|
|
2599
|
+
isEnumList,
|
|
2600
|
+
isEnumModel,
|
|
2601
|
+
isFieldNode,
|
|
2602
|
+
isFragmentSpreadNode,
|
|
2603
|
+
isInlineFragmentNode,
|
|
2604
|
+
isJsonObjectModel,
|
|
2605
|
+
isListType,
|
|
2606
|
+
isObjectModel,
|
|
2607
|
+
isQueriableBy,
|
|
2608
|
+
isQueriableField,
|
|
2609
|
+
isRaw,
|
|
2610
|
+
isRawEnumModel,
|
|
2611
|
+
isRawObjectModel,
|
|
2612
|
+
isRelation,
|
|
2613
|
+
isScalarModel,
|
|
2614
|
+
isSimpleField,
|
|
2615
|
+
isToOneRelation,
|
|
2616
|
+
isUpdatable,
|
|
2617
|
+
isUpdatableBy,
|
|
2618
|
+
isVisible,
|
|
2619
|
+
isVisibleRelation,
|
|
2620
|
+
it,
|
|
2621
|
+
list,
|
|
2622
|
+
merge,
|
|
2623
|
+
mutationResolver,
|
|
2624
|
+
name,
|
|
2625
|
+
namedType,
|
|
2626
|
+
nonNull,
|
|
2627
|
+
normalizeArguments,
|
|
2628
|
+
normalizeValue,
|
|
2629
|
+
normalizeValueByTypeDefinition,
|
|
2630
|
+
not,
|
|
2631
|
+
object,
|
|
2632
|
+
ors,
|
|
2633
|
+
printSchema,
|
|
2634
|
+
printSchemaFromDocument,
|
|
2635
|
+
printSchemaFromModels,
|
|
2636
|
+
queryRelations,
|
|
2637
|
+
queryResolver,
|
|
2638
|
+
resolve,
|
|
2639
|
+
retry,
|
|
2640
|
+
scalar,
|
|
2641
|
+
summon,
|
|
2642
|
+
summonByKey,
|
|
2643
|
+
summonByName,
|
|
2644
|
+
typeToField,
|
|
2645
|
+
value
|
|
2646
|
+
});
|