@smartive/graphql-magic 9.1.2 → 10.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc +2 -10
- package/.github/workflows/release.yml +1 -1
- package/.gqmrc.json +6 -0
- package/CHANGELOG.md +2 -2
- package/README.md +1 -1
- package/dist/bin/gqm.cjs +684 -330
- package/dist/cjs/index.cjs +998 -554
- package/dist/esm/api/execute.js +1 -1
- package/dist/esm/api/execute.js.map +1 -1
- package/dist/esm/client/mutations.d.ts +2 -2
- package/dist/esm/client/mutations.js +5 -4
- package/dist/esm/client/mutations.js.map +1 -1
- package/dist/esm/client/queries.d.ts +12 -17
- package/dist/esm/client/queries.js +30 -50
- package/dist/esm/client/queries.js.map +1 -1
- package/dist/esm/context.d.ts +1 -2
- package/dist/esm/db/generate.d.ts +3 -3
- package/dist/esm/db/generate.js +31 -29
- package/dist/esm/db/generate.js.map +1 -1
- package/dist/esm/migrations/generate.d.ts +3 -4
- package/dist/esm/migrations/generate.js +114 -107
- package/dist/esm/migrations/generate.js.map +1 -1
- package/dist/esm/models/index.d.ts +1 -0
- package/dist/esm/models/index.js +1 -0
- package/dist/esm/models/index.js.map +1 -1
- package/dist/esm/models/model-definitions.d.ts +189 -0
- package/dist/esm/models/model-definitions.js +2 -0
- package/dist/esm/models/model-definitions.js.map +1 -0
- package/dist/esm/models/models.d.ts +128 -174
- package/dist/esm/models/models.js +411 -1
- package/dist/esm/models/models.js.map +1 -1
- package/dist/esm/models/mutation-hook.d.ts +2 -2
- package/dist/esm/models/utils.d.ts +35 -497
- package/dist/esm/models/utils.js +21 -144
- package/dist/esm/models/utils.js.map +1 -1
- package/dist/esm/permissions/check.d.ts +3 -3
- package/dist/esm/permissions/check.js +14 -7
- package/dist/esm/permissions/check.js.map +1 -1
- package/dist/esm/permissions/generate.js +6 -6
- package/dist/esm/permissions/generate.js.map +1 -1
- package/dist/esm/resolvers/filters.d.ts +8 -0
- package/dist/esm/resolvers/filters.js +28 -25
- package/dist/esm/resolvers/filters.js.map +1 -1
- package/dist/esm/resolvers/index.d.ts +1 -0
- package/dist/esm/resolvers/index.js +1 -0
- package/dist/esm/resolvers/index.js.map +1 -1
- package/dist/esm/resolvers/mutations.js +85 -21
- package/dist/esm/resolvers/mutations.js.map +1 -1
- package/dist/esm/resolvers/node.d.ts +13 -15
- package/dist/esm/resolvers/node.js +41 -36
- package/dist/esm/resolvers/node.js.map +1 -1
- package/dist/esm/resolvers/resolver.js +19 -49
- package/dist/esm/resolvers/resolver.js.map +1 -1
- package/dist/esm/resolvers/resolvers.d.ts +1 -8
- package/dist/esm/resolvers/resolvers.js +15 -7
- package/dist/esm/resolvers/resolvers.js.map +1 -1
- package/dist/esm/resolvers/selects.d.ts +3 -0
- package/dist/esm/resolvers/selects.js +50 -0
- package/dist/esm/resolvers/selects.js.map +1 -0
- package/dist/esm/resolvers/utils.d.ts +12 -4
- package/dist/esm/resolvers/utils.js +30 -22
- package/dist/esm/resolvers/utils.js.map +1 -1
- package/dist/esm/schema/generate.d.ts +4 -4
- package/dist/esm/schema/generate.js +122 -131
- package/dist/esm/schema/generate.js.map +1 -1
- package/dist/esm/schema/utils.d.ts +1 -1
- package/dist/esm/schema/utils.js +2 -1
- package/dist/esm/schema/utils.js.map +1 -1
- package/knexfile.ts +31 -0
- package/migrations/20230912185644_setup.ts +127 -0
- package/package.json +16 -14
- package/src/api/execute.ts +1 -1
- package/src/bin/gqm/gqm.ts +25 -23
- package/src/bin/gqm/parse-models.ts +5 -5
- package/src/bin/gqm/settings.ts +13 -4
- package/src/bin/gqm/static-eval.ts +5 -0
- package/src/bin/gqm/templates.ts +23 -3
- package/src/client/mutations.ts +11 -5
- package/src/client/queries.ts +43 -80
- package/src/context.ts +1 -2
- package/src/db/generate.ts +41 -41
- package/src/migrations/generate.ts +165 -146
- package/src/models/index.ts +1 -0
- package/src/models/model-definitions.ts +168 -0
- package/src/models/models.ts +510 -166
- package/src/models/mutation-hook.ts +2 -2
- package/src/models/utils.ts +53 -187
- package/src/permissions/check.ts +19 -11
- package/src/permissions/generate.ts +6 -6
- package/src/resolvers/filters.ts +44 -28
- package/src/resolvers/index.ts +1 -0
- package/src/resolvers/mutations.ts +98 -36
- package/src/resolvers/node.ts +79 -51
- package/src/resolvers/resolver.ts +20 -74
- package/src/resolvers/resolvers.ts +18 -7
- package/src/resolvers/selects.ts +77 -0
- package/src/resolvers/utils.ts +41 -25
- package/src/schema/generate.ts +106 -127
- package/src/schema/utils.ts +2 -1
- package/tests/api/__snapshots__/inheritance.spec.ts.snap +83 -0
- package/tests/api/inheritance.spec.ts +130 -0
- package/tests/generated/api/index.ts +1174 -0
- package/tests/generated/client/index.ts +1163 -0
- package/tests/generated/client/mutations.ts +109 -0
- package/tests/generated/db/index.ts +291 -0
- package/tests/generated/db/knex.ts +14 -0
- package/tests/generated/models.json +675 -0
- package/tests/generated/schema.graphql +325 -0
- package/tests/unit/__snapshots__/resolve.spec.ts.snap +23 -0
- package/tests/unit/queries.spec.ts +5 -5
- package/tests/unit/resolve.spec.ts +8 -8
- package/tests/utils/database/knex.ts +5 -13
- package/tests/utils/database/seed.ts +57 -18
- package/tests/utils/models.ts +62 -7
- package/tests/utils/server.ts +5 -5
- package/tsconfig.eslint.json +1 -0
- package/tests/unit/__snapshots__/generate.spec.ts.snap +0 -128
- package/tests/unit/generate.spec.ts +0 -8
- package/tests/utils/database/schema.ts +0 -64
- package/tests/utils/generate-migration.ts +0 -24
package/src/models/models.ts
CHANGED
|
@@ -1,180 +1,524 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import
|
|
1
|
+
import { pluralize } from 'inflection';
|
|
2
|
+
import { cloneDeep, kebabCase, omit, pick } from 'lodash';
|
|
3
|
+
import { isRelation } from '.';
|
|
4
|
+
import {
|
|
5
|
+
CustomFieldDefinition,
|
|
6
|
+
EnumFieldDefinition,
|
|
7
|
+
FloatFieldDefinition,
|
|
8
|
+
IDFieldDefinition,
|
|
9
|
+
InputModelDefinition,
|
|
10
|
+
IntFieldDefinition,
|
|
11
|
+
InterfaceModelDefinition,
|
|
12
|
+
JsonFieldDefinition,
|
|
13
|
+
ObjectModelDefinition,
|
|
14
|
+
OrderBy,
|
|
15
|
+
PrimitiveFieldDefinition,
|
|
16
|
+
RawEnumModelDefinition,
|
|
17
|
+
StringFieldDefinition,
|
|
18
|
+
UploadFieldDefinition,
|
|
19
|
+
} from '..';
|
|
20
|
+
import {
|
|
21
|
+
BooleanFieldDefinition,
|
|
22
|
+
DateTimeFieldDefinition,
|
|
23
|
+
EntityFieldDefinition,
|
|
24
|
+
EntityModelDefinition,
|
|
25
|
+
EnumModelDefinition,
|
|
26
|
+
ModelDefinition,
|
|
27
|
+
ModelDefinitions,
|
|
28
|
+
ObjectFieldDefinition,
|
|
29
|
+
RelationFieldDefinition,
|
|
30
|
+
} from './model-definitions';
|
|
31
|
+
import { get, getLabel, summonByName, typeToField } from './utils';
|
|
4
32
|
|
|
5
|
-
|
|
33
|
+
// These might one day become classes
|
|
6
34
|
|
|
7
|
-
export type
|
|
35
|
+
export type ObjectField = ObjectFieldDefinition;
|
|
36
|
+
export type EntityField = EntityFieldDefinition;
|
|
37
|
+
export type PrimitiveField = PrimitiveFieldDefinition;
|
|
38
|
+
export type IDField = IDFieldDefinition;
|
|
39
|
+
export type BooleanField = BooleanFieldDefinition;
|
|
40
|
+
export type StringField = StringFieldDefinition;
|
|
41
|
+
export type DateTimeField = DateTimeFieldDefinition;
|
|
42
|
+
export type IntField = IntFieldDefinition;
|
|
43
|
+
export type FloatField = FloatFieldDefinition;
|
|
44
|
+
export type UploadField = UploadFieldDefinition;
|
|
45
|
+
export type JsonField = JsonFieldDefinition;
|
|
46
|
+
export type EnumField = EnumFieldDefinition;
|
|
47
|
+
export type CustomField = CustomFieldDefinition;
|
|
48
|
+
export type RelationField = Omit<RelationFieldDefinition, 'foreignKey'> & { foreignKey: string };
|
|
49
|
+
|
|
50
|
+
export class Models {
|
|
51
|
+
public models: Model[];
|
|
52
|
+
private modelsByName: Record<string, Model> = {};
|
|
53
|
+
public scalars: ScalarModel[];
|
|
54
|
+
public rawEnums: RawEnumModel[];
|
|
55
|
+
public enums: EnumModel[];
|
|
56
|
+
public inputs: InputModel[];
|
|
57
|
+
public interfaces: InterfaceModel[];
|
|
58
|
+
public objects: ObjectModel[];
|
|
59
|
+
public entities: EntityModel[];
|
|
60
|
+
public definitions: ModelDefinitions;
|
|
61
|
+
|
|
62
|
+
constructor(definitions: ModelDefinitions) {
|
|
63
|
+
this.definitions = cloneDeep(definitions);
|
|
64
|
+
this.definitions.push(
|
|
65
|
+
{
|
|
66
|
+
kind: 'scalar',
|
|
67
|
+
name: 'DateTime',
|
|
68
|
+
},
|
|
69
|
+
{ kind: 'scalar', name: 'Upload' },
|
|
70
|
+
{
|
|
71
|
+
kind: 'raw-enum',
|
|
72
|
+
name: 'Order',
|
|
73
|
+
values: ['ASC', 'DESC'],
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
const entities = this.definitions.filter(isEntityModelDefinition);
|
|
77
|
+
|
|
78
|
+
for (const entity of entities) {
|
|
79
|
+
if (entity.root) {
|
|
80
|
+
this.definitions.push({
|
|
81
|
+
kind: 'enum',
|
|
82
|
+
name: `${entity.name}Type`,
|
|
83
|
+
values: entities.filter((subModel) => subModel.parent === entity.name).map((subModel) => subModel.name),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
entity.fields = [
|
|
88
|
+
{
|
|
89
|
+
name: 'id',
|
|
90
|
+
type: 'ID',
|
|
91
|
+
nonNull: true,
|
|
92
|
+
unique: true,
|
|
93
|
+
primary: true,
|
|
94
|
+
generated: true,
|
|
95
|
+
},
|
|
96
|
+
...(entity.root
|
|
97
|
+
? [
|
|
98
|
+
{
|
|
99
|
+
name: 'type',
|
|
100
|
+
kind: 'enum',
|
|
101
|
+
type: `${entity.name}Type`,
|
|
102
|
+
nonNull: true,
|
|
103
|
+
generated: true,
|
|
104
|
+
} satisfies EntityFieldDefinition,
|
|
105
|
+
]
|
|
106
|
+
: []),
|
|
107
|
+
...entity.fields,
|
|
108
|
+
...(entity.creatable
|
|
109
|
+
? [
|
|
110
|
+
{
|
|
111
|
+
name: 'createdAt',
|
|
112
|
+
type: 'DateTime',
|
|
113
|
+
nonNull: true,
|
|
114
|
+
orderable: true,
|
|
115
|
+
generated: true,
|
|
116
|
+
...(typeof entity.creatable === 'object' && entity.creatable.createdAt),
|
|
117
|
+
} satisfies DateTimeFieldDefinition,
|
|
118
|
+
{
|
|
119
|
+
name: 'createdBy',
|
|
120
|
+
kind: 'relation',
|
|
121
|
+
type: 'User',
|
|
122
|
+
nonNull: true,
|
|
123
|
+
reverse: `created${getModelPlural(entity)}`,
|
|
124
|
+
generated: true,
|
|
125
|
+
...(typeof entity.creatable === 'object' && entity.creatable.createdBy),
|
|
126
|
+
} satisfies RelationFieldDefinition,
|
|
127
|
+
]
|
|
128
|
+
: []),
|
|
129
|
+
...(entity.updatable
|
|
130
|
+
? [
|
|
131
|
+
{
|
|
132
|
+
name: 'updatedAt',
|
|
133
|
+
type: 'DateTime',
|
|
134
|
+
nonNull: true,
|
|
135
|
+
orderable: true,
|
|
136
|
+
generated: true,
|
|
137
|
+
...(typeof entity.updatable === 'object' && entity.updatable.updatedAt),
|
|
138
|
+
} satisfies DateTimeFieldDefinition,
|
|
139
|
+
{
|
|
140
|
+
name: 'updatedBy',
|
|
141
|
+
kind: 'relation',
|
|
142
|
+
type: 'User',
|
|
143
|
+
nonNull: true,
|
|
144
|
+
reverse: `updated${getModelPlural(entity)}`,
|
|
145
|
+
generated: true,
|
|
146
|
+
...(typeof entity.updatable === 'object' && entity.updatable.updatedBy),
|
|
147
|
+
} satisfies RelationFieldDefinition,
|
|
148
|
+
]
|
|
149
|
+
: []),
|
|
150
|
+
...(entity.deletable
|
|
151
|
+
? [
|
|
152
|
+
{
|
|
153
|
+
name: 'deleted',
|
|
154
|
+
type: 'Boolean',
|
|
155
|
+
nonNull: true,
|
|
156
|
+
defaultValue: false,
|
|
157
|
+
filterable: { default: false },
|
|
158
|
+
generated: true,
|
|
159
|
+
...(typeof entity.deletable === 'object' && entity.deletable.deleted),
|
|
160
|
+
} satisfies BooleanFieldDefinition,
|
|
161
|
+
{
|
|
162
|
+
name: 'deletedAt',
|
|
163
|
+
type: 'DateTime',
|
|
164
|
+
orderable: true,
|
|
165
|
+
generated: true,
|
|
166
|
+
...(typeof entity.deletable === 'object' && entity.deletable.deletedAt),
|
|
167
|
+
} satisfies DateTimeFieldDefinition,
|
|
168
|
+
{
|
|
169
|
+
name: 'deletedBy',
|
|
170
|
+
kind: 'relation',
|
|
171
|
+
type: 'User',
|
|
172
|
+
reverse: `deleted${getModelPlural(entity)}`,
|
|
173
|
+
generated: true,
|
|
174
|
+
...(typeof entity.deletable === 'object' && entity.deletable.deletedBy),
|
|
175
|
+
} satisfies RelationFieldDefinition,
|
|
176
|
+
]
|
|
177
|
+
: []),
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
for (const field of entity.fields) {
|
|
181
|
+
if (field.kind === 'relation') {
|
|
182
|
+
field.foreignKey = field.foreignKey || `${field.name}Id`;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
for (const model of entities) {
|
|
188
|
+
if (model.parent) {
|
|
189
|
+
const parent = summonByName(entities, model.parent);
|
|
190
|
+
const INHERITED_FIELDS = ['queriable', 'listQueriable', 'creatable', 'updatable', 'deletable'];
|
|
191
|
+
Object.assign(model, pick(parent, INHERITED_FIELDS), pick(model, INHERITED_FIELDS));
|
|
192
|
+
|
|
193
|
+
model.fields = [
|
|
194
|
+
...parent.fields.map((field) => ({
|
|
195
|
+
...field,
|
|
196
|
+
...(field.kind === 'relation' &&
|
|
197
|
+
field.reverse && { reverse: field.reverse.replace(getModelPlural(parent), getModelPlural(model)) }),
|
|
198
|
+
...model.fields.find((childField) => childField.name === field.name),
|
|
199
|
+
inherited: true,
|
|
200
|
+
})),
|
|
201
|
+
...model.fields.filter((field) => !parent.fields.some((parentField) => parentField.name === field.name)),
|
|
202
|
+
];
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
this.models = this.definitions.map(
|
|
207
|
+
(definition) => new (MODEL_KIND_TO_CLASS_MAPPING[definition.kind] as any)(this, definition)
|
|
208
|
+
);
|
|
209
|
+
for (const model of this.models) {
|
|
210
|
+
this.modelsByName[model.name] = model;
|
|
211
|
+
}
|
|
212
|
+
this.entities = this.models.filter((model): model is EntityModel => model instanceof EntityModel);
|
|
213
|
+
this.enums = this.models.filter((model): model is EnumModel => model instanceof EnumModel);
|
|
214
|
+
this.inputs = this.models.filter((model): model is InputModel => model instanceof InputModel);
|
|
215
|
+
this.interfaces = this.models.filter((model): model is InterfaceModel => model instanceof InterfaceModel);
|
|
216
|
+
this.objects = this.models.filter((model): model is ObjectModel => model instanceof ObjectModel);
|
|
217
|
+
this.rawEnums = this.models.filter((model): model is RawEnumModel => model instanceof RawEnumModel);
|
|
218
|
+
this.scalars = this.models.filter((model): model is ScalarModel => model instanceof ScalarModel);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
public getModel<K extends keyof ModelKindToClassMapping>(name: string, kind?: K): ModelKindToClassMapping[K] {
|
|
222
|
+
const model = get(this.modelsByName, name);
|
|
223
|
+
if (!kind) {
|
|
224
|
+
return model as ModelKindToClassMapping[K];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const expectedType = MODEL_KIND_TO_CLASS_MAPPING[kind];
|
|
228
|
+
if (!(model instanceof expectedType)) {
|
|
229
|
+
throw new Error(`Model ${name} is not of kind ${kind}.`);
|
|
230
|
+
}
|
|
231
|
+
return model as ModelKindToClassMapping[K];
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export abstract class Model {
|
|
8
236
|
name: string;
|
|
9
|
-
plural
|
|
10
|
-
description
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
237
|
+
plural: string;
|
|
238
|
+
description: string;
|
|
239
|
+
|
|
240
|
+
constructor(public models: Models, definition: ModelDefinition) {
|
|
241
|
+
Object.assign(this, definition);
|
|
242
|
+
this.plural = definition.plural || pluralize(definition.name);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export class ScalarModel extends Model {
|
|
247
|
+
kind: 'scalar';
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export class EnumModel extends Model {
|
|
251
|
+
kind: 'enum';
|
|
252
|
+
values: string[];
|
|
253
|
+
deleted?: true;
|
|
254
|
+
|
|
255
|
+
constructor(models: Models, definition: EnumModelDefinition) {
|
|
256
|
+
super(models, definition);
|
|
257
|
+
Object.assign(this, omit(definition, 'name', 'plural', 'description'));
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export class RawEnumModel extends Model {
|
|
262
|
+
kind: 'raw-enum';
|
|
263
|
+
values: string[];
|
|
264
|
+
|
|
265
|
+
constructor(models: Models, definition: RawEnumModelDefinition) {
|
|
266
|
+
super(models, definition);
|
|
267
|
+
Object.assign(this, omit(definition, 'name', 'plural', 'description'));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export class InterfaceModel extends Model {
|
|
272
|
+
kind: 'interface';
|
|
273
|
+
fields: EntityField[];
|
|
274
|
+
|
|
275
|
+
constructor(models: Models, definition: InterfaceModelDefinition) {
|
|
276
|
+
super(models, definition);
|
|
277
|
+
Object.assign(this, omit(definition, 'name', 'plural', 'description'));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export class InputModel extends Model {
|
|
282
|
+
kind: 'model';
|
|
283
|
+
fields: ObjectField[];
|
|
284
|
+
|
|
285
|
+
constructor(models: Models, definition: InputModelDefinition) {
|
|
286
|
+
super(models, definition);
|
|
287
|
+
Object.assign(this, omit(definition, 'name', 'plural', 'description'));
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export class ObjectModel extends Model {
|
|
292
|
+
kind: 'object';
|
|
293
|
+
fields: ObjectField[];
|
|
294
|
+
|
|
295
|
+
constructor(models: Models, definition: ObjectModelDefinition) {
|
|
296
|
+
super(models, definition);
|
|
297
|
+
Object.assign(this, omit(definition, 'name', 'plural', 'description'));
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export class EntityModel extends Model {
|
|
302
|
+
kind: 'entity';
|
|
303
|
+
root?: boolean;
|
|
304
|
+
parent?: string;
|
|
305
|
+
interfaces?: string[];
|
|
306
|
+
queriable?: boolean;
|
|
307
|
+
listQueriable?: boolean;
|
|
308
|
+
creatable?: boolean | { createdBy?: Partial<RelationFieldDefinition>; createdAt?: Partial<DateTimeFieldDefinition> };
|
|
309
|
+
updatable?: boolean | { updatedBy?: Partial<RelationFieldDefinition>; updatedAt?: Partial<DateTimeFieldDefinition> };
|
|
310
|
+
deletable?:
|
|
311
|
+
| boolean
|
|
312
|
+
| {
|
|
313
|
+
deleted?: Partial<BooleanFieldDefinition>;
|
|
314
|
+
deletedBy?: Partial<RelationFieldDefinition>;
|
|
315
|
+
deletedAt?: Partial<DateTimeFieldDefinition>;
|
|
316
|
+
};
|
|
317
|
+
displayField?: string;
|
|
318
|
+
defaultOrderBy?: OrderBy;
|
|
319
|
+
fields: EntityField[];
|
|
320
|
+
|
|
321
|
+
// temporary fields for the generation of migrations
|
|
322
|
+
deleted?: true;
|
|
323
|
+
oldName?: string;
|
|
324
|
+
|
|
325
|
+
fieldsByName: Record<string, EntityField> = {};
|
|
326
|
+
fieldsByColumnName: Record<string, EntityField> = {};
|
|
327
|
+
private _relations: NormalRelation[];
|
|
328
|
+
private _relationsByName: Record<string, NormalRelation>;
|
|
329
|
+
private _reverseRelations: ReverseRelation[];
|
|
330
|
+
private _reverseRelationsByName: Record<string, ReverseRelation>;
|
|
331
|
+
private _manyToManyRelations: ManyToManyRelation[];
|
|
332
|
+
private _manyToManyRelationsByName: Record<string, ManyToManyRelation>;
|
|
333
|
+
public pluralField: string;
|
|
334
|
+
public slug: string;
|
|
335
|
+
public labelPlural: string;
|
|
336
|
+
public label: string;
|
|
337
|
+
private _parentModel: EntityModel;
|
|
338
|
+
|
|
339
|
+
constructor(models: Models, definition: EntityModelDefinition) {
|
|
340
|
+
super(models, definition);
|
|
341
|
+
Object.assign(this, omit(definition, 'name', 'plural', 'description'));
|
|
342
|
+
this.pluralField = typeToField(this.plural);
|
|
343
|
+
this.slug = kebabCase(this.plural);
|
|
344
|
+
this.labelPlural = getLabel(this.plural);
|
|
345
|
+
this.label = getLabel(definition.name);
|
|
346
|
+
for (const field of definition.fields) {
|
|
347
|
+
this.fieldsByName[field.name] = field;
|
|
19
348
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
public getField(name: string) {
|
|
352
|
+
return get(this.fieldsByName, name);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
public get relations() {
|
|
356
|
+
if (!this._relations) {
|
|
357
|
+
this._relations = this.fields
|
|
358
|
+
.filter(isRelation)
|
|
359
|
+
.map((relationField) => new NormalRelation(this, relationField, this.models.getModel(relationField.type, 'entity')));
|
|
23
360
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
| { deleted?: Partial<BooleanField>; deletedBy?: Partial<RelationField>; deletedAt?: Partial<DateTimeField> };
|
|
34
|
-
displayField?: string;
|
|
35
|
-
defaultOrderBy?: OrderBy;
|
|
36
|
-
fields: ModelField[];
|
|
37
|
-
|
|
38
|
-
// temporary fields for the generation of migrations
|
|
39
|
-
deleted?: true;
|
|
40
|
-
oldName?: string;
|
|
361
|
+
return this._relations;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
public get relationsByName() {
|
|
365
|
+
if (!this._relationsByName) {
|
|
366
|
+
this._relationsByName = {};
|
|
367
|
+
for (const relation of this.relations) {
|
|
368
|
+
this._relationsByName[relation.name] = relation;
|
|
369
|
+
}
|
|
41
370
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
371
|
+
return this._relationsByName;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
public getRelation(name: string) {
|
|
375
|
+
return get(this.relationsByName, name);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
public get reverseRelations() {
|
|
379
|
+
if (!this._reverseRelations) {
|
|
380
|
+
this._reverseRelations = this.models.entities.flatMap((model) =>
|
|
381
|
+
model.relations
|
|
382
|
+
.filter((relation) => relation.targetModel.name === this.name || relation.targetModel.name === this.rootModel.name)
|
|
383
|
+
.map((relation) => relation.reverse)
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
return this._reverseRelations;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
public get reverseRelationsByName() {
|
|
390
|
+
if (!this._reverseRelationsByName) {
|
|
391
|
+
this._reverseRelationsByName = {};
|
|
392
|
+
for (const reverseRelation of this.reverseRelations) {
|
|
393
|
+
this._reverseRelationsByName[reverseRelation.name] = reverseRelation;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return this._reverseRelationsByName;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
public getReverseRelation(name: string) {
|
|
400
|
+
return get(this.reverseRelationsByName, name);
|
|
401
|
+
}
|
|
57
402
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
large?: true;
|
|
68
|
-
maxLength?: number;
|
|
403
|
+
public get manyToManyRelations() {
|
|
404
|
+
if (!this._manyToManyRelations) {
|
|
405
|
+
this._manyToManyRelations = [];
|
|
406
|
+
for (const relationFromSource of this.reverseRelations) {
|
|
407
|
+
const relationToTarget = relationFromSource.targetModel.relations.find(
|
|
408
|
+
(relation) => !relation.field.generated && relation.field.name !== relationFromSource.field.name
|
|
409
|
+
);
|
|
410
|
+
if (!relationToTarget) {
|
|
411
|
+
continue;
|
|
69
412
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
413
|
+
|
|
414
|
+
const inapplicableFields = relationFromSource.targetModel.fields.filter(
|
|
415
|
+
(otherField) =>
|
|
416
|
+
!otherField.generated && ![relationFromSource.field.name, relationToTarget.field.name].includes(otherField.name)
|
|
417
|
+
);
|
|
418
|
+
if (inapplicableFields.length) {
|
|
419
|
+
continue;
|
|
74
420
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
intType?: 'currency';
|
|
78
|
-
} & BaseNumberType)
|
|
79
|
-
| ({
|
|
80
|
-
type: 'Float';
|
|
81
|
-
floatType?: 'currency' | 'percentage';
|
|
82
|
-
double?: boolean;
|
|
83
|
-
precision?: number;
|
|
84
|
-
scale?: number;
|
|
85
|
-
} & BaseNumberType)
|
|
86
|
-
| { type: 'Upload' }
|
|
87
|
-
))
|
|
88
|
-
| { kind: 'enum'; type: string; possibleValues?: Value[] }
|
|
89
|
-
| { kind: 'custom'; type: string };
|
|
90
|
-
|
|
91
|
-
export type ObjectField = FieldBase & FieldBase2;
|
|
92
|
-
|
|
93
|
-
export type ModelField = FieldBase &
|
|
94
|
-
(
|
|
95
|
-
| FieldBase2
|
|
96
|
-
| { kind: 'json'; type: string }
|
|
97
|
-
| {
|
|
98
|
-
kind: 'relation';
|
|
99
|
-
type: string;
|
|
100
|
-
toOne?: boolean;
|
|
101
|
-
reverse?: string;
|
|
102
|
-
foreignKey?: string;
|
|
103
|
-
onDelete?: 'cascade' | 'set-null';
|
|
421
|
+
|
|
422
|
+
this._manyToManyRelations.push(new ManyToManyRelation(relationFromSource, relationToTarget));
|
|
104
423
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
indent?: boolean;
|
|
137
|
-
// If true the field is hidden in the admin interface
|
|
138
|
-
hidden?: boolean;
|
|
139
|
-
|
|
140
|
-
// temporary fields for the generation of migrations
|
|
141
|
-
deleted?: true;
|
|
142
|
-
oldName?: string;
|
|
143
|
-
|
|
144
|
-
meta?: Record<string, unknown>;
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
export type PrimitiveField = Extract<ModelField, { kind?: 'primitive' | undefined }>;
|
|
148
|
-
export type IDField = Extract<PrimitiveField, { type: 'ID' }>;
|
|
149
|
-
export type BooleanField = Extract<PrimitiveField, { type: 'Boolean' }>;
|
|
150
|
-
export type StringField = Extract<PrimitiveField, { type: 'String' }>;
|
|
151
|
-
export type DateTimeField = Extract<PrimitiveField, { type: 'DateTime' }>;
|
|
152
|
-
export type IntField = Extract<PrimitiveField, { type: 'Int' }>;
|
|
153
|
-
export type FloatField = Extract<PrimitiveField, { type: 'Float' }>;
|
|
154
|
-
export type UploadField = Extract<PrimitiveField, { type: 'Upload' }>;
|
|
155
|
-
export type JsonField = Extract<ModelField, { kind: 'json' }>;
|
|
156
|
-
export type EnumField = Extract<ModelField, { kind: 'enum' }>;
|
|
157
|
-
export type CustomField = Extract<ModelField, { kind: 'custom' }>;
|
|
158
|
-
export type RelationField = Extract<ModelField, { kind: 'relation' }>;
|
|
159
|
-
|
|
160
|
-
export type Models = Model[];
|
|
161
|
-
|
|
162
|
-
export type Model = EntityModel & {
|
|
163
|
-
fieldsByName: Record<string, ModelField>;
|
|
164
|
-
relations: Relation[];
|
|
165
|
-
relationsByName: Record<string, Relation>;
|
|
166
|
-
reverseRelations: ReverseRelation[];
|
|
167
|
-
reverseRelationsByName: Record<string, ReverseRelation>;
|
|
168
|
-
};
|
|
424
|
+
}
|
|
425
|
+
return this._manyToManyRelations;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
public get manyToManyRelationsByName() {
|
|
429
|
+
if (!this._manyToManyRelationsByName) {
|
|
430
|
+
this._manyToManyRelationsByName = {};
|
|
431
|
+
for (const manyToManyRelation of this.manyToManyRelations) {
|
|
432
|
+
this._manyToManyRelationsByName[manyToManyRelation.name] = manyToManyRelation;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return this._manyToManyRelationsByName;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
public getManyToManyRelation(name: string) {
|
|
439
|
+
return get(this.manyToManyRelationsByName, name);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
public get parentModel() {
|
|
443
|
+
if (this.parent) {
|
|
444
|
+
if (!this._parentModel) {
|
|
445
|
+
this._parentModel = this.models.getModel(this.parent, 'entity');
|
|
446
|
+
}
|
|
447
|
+
return this._parentModel;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
public get rootModel() {
|
|
452
|
+
return this.parentModel || this;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
169
455
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
456
|
+
const MODEL_KIND_TO_CLASS_MAPPING = {
|
|
457
|
+
entity: EntityModel,
|
|
458
|
+
enum: EnumModel,
|
|
459
|
+
input: InputModel,
|
|
460
|
+
interface: InterfaceModel,
|
|
461
|
+
object: ObjectModel,
|
|
462
|
+
'raw-enum': RawEnumModel,
|
|
463
|
+
scalar: ScalarModel,
|
|
174
464
|
};
|
|
175
465
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
field: RelationField;
|
|
179
|
-
fieldModel: Model;
|
|
466
|
+
type ModelKindToClassMapping = {
|
|
467
|
+
[K in keyof typeof MODEL_KIND_TO_CLASS_MAPPING]: InstanceType<(typeof MODEL_KIND_TO_CLASS_MAPPING)[K]>;
|
|
180
468
|
};
|
|
469
|
+
|
|
470
|
+
export abstract class Relation {
|
|
471
|
+
constructor(
|
|
472
|
+
public name: string,
|
|
473
|
+
public sourceModel: EntityModel,
|
|
474
|
+
public field: RelationField,
|
|
475
|
+
public targetModel: EntityModel
|
|
476
|
+
) {}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
export class NormalRelation extends Relation {
|
|
480
|
+
public reverse: ReverseRelation;
|
|
481
|
+
|
|
482
|
+
constructor(sourceModel: EntityModel, public field: RelationField, targetModel: EntityModel) {
|
|
483
|
+
super(field.name, sourceModel, field, targetModel);
|
|
484
|
+
this.reverse = new ReverseRelation(this);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
export class ReverseRelation extends Relation {
|
|
489
|
+
constructor(public reverse: NormalRelation) {
|
|
490
|
+
super(
|
|
491
|
+
reverse.field.reverse ||
|
|
492
|
+
(reverse.field.toOne ? typeToField(reverse.sourceModel.name) : reverse.sourceModel.pluralField),
|
|
493
|
+
reverse.targetModel,
|
|
494
|
+
reverse.field,
|
|
495
|
+
reverse.sourceModel
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
export class ManyToManyRelation {
|
|
501
|
+
public name: string;
|
|
502
|
+
public sourceModel: EntityModel;
|
|
503
|
+
public relationFromSource: ReverseRelation;
|
|
504
|
+
public relationModel: EntityModel;
|
|
505
|
+
public relationToTarget: NormalRelation;
|
|
506
|
+
public targetModel: EntityModel;
|
|
507
|
+
|
|
508
|
+
constructor(relationFromSource: ReverseRelation, relationToTarget: NormalRelation) {
|
|
509
|
+
this.name = relationFromSource.name;
|
|
510
|
+
this.sourceModel = relationFromSource.sourceModel;
|
|
511
|
+
this.relationFromSource = relationFromSource;
|
|
512
|
+
this.relationModel = relationFromSource.targetModel;
|
|
513
|
+
if (this.relationModel !== relationToTarget.sourceModel) {
|
|
514
|
+
throw new Error(`Relation model is ambiguous.`);
|
|
515
|
+
}
|
|
516
|
+
this.relationToTarget = relationToTarget;
|
|
517
|
+
this.targetModel = relationToTarget.targetModel;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const isEntityModelDefinition = (definition: ModelDefinition): definition is EntityModelDefinition =>
|
|
522
|
+
definition.kind === 'entity';
|
|
523
|
+
|
|
524
|
+
const getModelPlural = (model: EntityModelDefinition) => model.plural || pluralize(model.name);
|