@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.
Files changed (120) hide show
  1. package/.eslintrc +2 -10
  2. package/.github/workflows/release.yml +1 -1
  3. package/.gqmrc.json +6 -0
  4. package/CHANGELOG.md +2 -2
  5. package/README.md +1 -1
  6. package/dist/bin/gqm.cjs +684 -330
  7. package/dist/cjs/index.cjs +998 -554
  8. package/dist/esm/api/execute.js +1 -1
  9. package/dist/esm/api/execute.js.map +1 -1
  10. package/dist/esm/client/mutations.d.ts +2 -2
  11. package/dist/esm/client/mutations.js +5 -4
  12. package/dist/esm/client/mutations.js.map +1 -1
  13. package/dist/esm/client/queries.d.ts +12 -17
  14. package/dist/esm/client/queries.js +30 -50
  15. package/dist/esm/client/queries.js.map +1 -1
  16. package/dist/esm/context.d.ts +1 -2
  17. package/dist/esm/db/generate.d.ts +3 -3
  18. package/dist/esm/db/generate.js +31 -29
  19. package/dist/esm/db/generate.js.map +1 -1
  20. package/dist/esm/migrations/generate.d.ts +3 -4
  21. package/dist/esm/migrations/generate.js +114 -107
  22. package/dist/esm/migrations/generate.js.map +1 -1
  23. package/dist/esm/models/index.d.ts +1 -0
  24. package/dist/esm/models/index.js +1 -0
  25. package/dist/esm/models/index.js.map +1 -1
  26. package/dist/esm/models/model-definitions.d.ts +189 -0
  27. package/dist/esm/models/model-definitions.js +2 -0
  28. package/dist/esm/models/model-definitions.js.map +1 -0
  29. package/dist/esm/models/models.d.ts +128 -174
  30. package/dist/esm/models/models.js +411 -1
  31. package/dist/esm/models/models.js.map +1 -1
  32. package/dist/esm/models/mutation-hook.d.ts +2 -2
  33. package/dist/esm/models/utils.d.ts +35 -497
  34. package/dist/esm/models/utils.js +21 -144
  35. package/dist/esm/models/utils.js.map +1 -1
  36. package/dist/esm/permissions/check.d.ts +3 -3
  37. package/dist/esm/permissions/check.js +14 -7
  38. package/dist/esm/permissions/check.js.map +1 -1
  39. package/dist/esm/permissions/generate.js +6 -6
  40. package/dist/esm/permissions/generate.js.map +1 -1
  41. package/dist/esm/resolvers/filters.d.ts +8 -0
  42. package/dist/esm/resolvers/filters.js +28 -25
  43. package/dist/esm/resolvers/filters.js.map +1 -1
  44. package/dist/esm/resolvers/index.d.ts +1 -0
  45. package/dist/esm/resolvers/index.js +1 -0
  46. package/dist/esm/resolvers/index.js.map +1 -1
  47. package/dist/esm/resolvers/mutations.js +85 -21
  48. package/dist/esm/resolvers/mutations.js.map +1 -1
  49. package/dist/esm/resolvers/node.d.ts +13 -15
  50. package/dist/esm/resolvers/node.js +41 -36
  51. package/dist/esm/resolvers/node.js.map +1 -1
  52. package/dist/esm/resolvers/resolver.js +19 -49
  53. package/dist/esm/resolvers/resolver.js.map +1 -1
  54. package/dist/esm/resolvers/resolvers.d.ts +1 -8
  55. package/dist/esm/resolvers/resolvers.js +15 -7
  56. package/dist/esm/resolvers/resolvers.js.map +1 -1
  57. package/dist/esm/resolvers/selects.d.ts +3 -0
  58. package/dist/esm/resolvers/selects.js +50 -0
  59. package/dist/esm/resolvers/selects.js.map +1 -0
  60. package/dist/esm/resolvers/utils.d.ts +12 -4
  61. package/dist/esm/resolvers/utils.js +30 -22
  62. package/dist/esm/resolvers/utils.js.map +1 -1
  63. package/dist/esm/schema/generate.d.ts +4 -4
  64. package/dist/esm/schema/generate.js +122 -131
  65. package/dist/esm/schema/generate.js.map +1 -1
  66. package/dist/esm/schema/utils.d.ts +1 -1
  67. package/dist/esm/schema/utils.js +2 -1
  68. package/dist/esm/schema/utils.js.map +1 -1
  69. package/knexfile.ts +31 -0
  70. package/migrations/20230912185644_setup.ts +127 -0
  71. package/package.json +16 -14
  72. package/src/api/execute.ts +1 -1
  73. package/src/bin/gqm/gqm.ts +25 -23
  74. package/src/bin/gqm/parse-models.ts +5 -5
  75. package/src/bin/gqm/settings.ts +13 -4
  76. package/src/bin/gqm/static-eval.ts +5 -0
  77. package/src/bin/gqm/templates.ts +23 -3
  78. package/src/client/mutations.ts +11 -5
  79. package/src/client/queries.ts +43 -80
  80. package/src/context.ts +1 -2
  81. package/src/db/generate.ts +41 -41
  82. package/src/migrations/generate.ts +165 -146
  83. package/src/models/index.ts +1 -0
  84. package/src/models/model-definitions.ts +168 -0
  85. package/src/models/models.ts +510 -166
  86. package/src/models/mutation-hook.ts +2 -2
  87. package/src/models/utils.ts +53 -187
  88. package/src/permissions/check.ts +19 -11
  89. package/src/permissions/generate.ts +6 -6
  90. package/src/resolvers/filters.ts +44 -28
  91. package/src/resolvers/index.ts +1 -0
  92. package/src/resolvers/mutations.ts +98 -36
  93. package/src/resolvers/node.ts +79 -51
  94. package/src/resolvers/resolver.ts +20 -74
  95. package/src/resolvers/resolvers.ts +18 -7
  96. package/src/resolvers/selects.ts +77 -0
  97. package/src/resolvers/utils.ts +41 -25
  98. package/src/schema/generate.ts +106 -127
  99. package/src/schema/utils.ts +2 -1
  100. package/tests/api/__snapshots__/inheritance.spec.ts.snap +83 -0
  101. package/tests/api/inheritance.spec.ts +130 -0
  102. package/tests/generated/api/index.ts +1174 -0
  103. package/tests/generated/client/index.ts +1163 -0
  104. package/tests/generated/client/mutations.ts +109 -0
  105. package/tests/generated/db/index.ts +291 -0
  106. package/tests/generated/db/knex.ts +14 -0
  107. package/tests/generated/models.json +675 -0
  108. package/tests/generated/schema.graphql +325 -0
  109. package/tests/unit/__snapshots__/resolve.spec.ts.snap +23 -0
  110. package/tests/unit/queries.spec.ts +5 -5
  111. package/tests/unit/resolve.spec.ts +8 -8
  112. package/tests/utils/database/knex.ts +5 -13
  113. package/tests/utils/database/seed.ts +57 -18
  114. package/tests/utils/models.ts +62 -7
  115. package/tests/utils/server.ts +5 -5
  116. package/tsconfig.eslint.json +1 -0
  117. package/tests/unit/__snapshots__/generate.spec.ts.snap +0 -128
  118. package/tests/unit/generate.spec.ts +0 -8
  119. package/tests/utils/database/schema.ts +0 -64
  120. package/tests/utils/generate-migration.ts +0 -24
@@ -1,180 +1,524 @@
1
- import { Field } from '..';
2
- import type { OrderBy } from '../resolvers/arguments';
3
- import type { Value } from '../values';
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
- export type RawModels = RawModel[];
33
+ // These might one day become classes
6
34
 
7
- export type RawModel = {
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?: string;
10
- description?: string;
11
- } & (
12
- | { kind: 'scalar' }
13
- | { kind: 'enum'; values: string[]; deleted?: true }
14
- | { kind: 'raw-enum'; values: string[] }
15
- | { kind: 'interface'; fields: ModelField[] }
16
- | {
17
- kind: 'input';
18
- fields: ObjectField[];
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
- kind: 'object';
22
- fields: ObjectField[];
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
- kind: 'entity';
26
- interfaces?: string[];
27
- queriable?: boolean;
28
- listQueriable?: boolean;
29
- creatable?: boolean | { createdBy?: Partial<RelationField>; createdAt?: Partial<DateTimeField> };
30
- updatable?: boolean | { updatedBy?: Partial<RelationField>; updatedAt?: Partial<DateTimeField> };
31
- deletable?:
32
- | boolean
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
- export type ScalarModel = Extract<RawModel, { kind: 'scalar' }>;
45
- export type EnumModel = Extract<RawModel, { kind: 'enum' }>;
46
- export type RawEnumModel = Extract<RawModel, { kind: 'raw-enum' }>;
47
- export type InterfaceModel = Extract<RawModel, { kind: 'interface' }>;
48
- export type ObjectModel = Extract<RawModel, { kind: 'object' }>;
49
- export type InputModel = Extract<RawModel, { kind: 'input' }>;
50
- export type EntityModel = Extract<RawModel, { kind: 'entity' }>;
51
-
52
- type BaseNumberType = {
53
- unit?: 'million';
54
- min?: number;
55
- max?: number;
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
- type FieldBase = Omit<Field, 'type'>;
59
-
60
- type FieldBase2 =
61
- | ({ kind?: 'primitive' | undefined } & (
62
- | { type: 'ID' }
63
- | { type: 'Boolean' }
64
- | {
65
- type: 'String';
66
- stringType?: 'email' | 'url' | 'phone';
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
- type: 'DateTime';
72
- dateTimeType?: 'year' | 'date' | 'datetime' | 'year_and_month';
73
- endOfDay?: boolean;
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
- type: 'Int';
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
- primary?: boolean;
107
- unique?: boolean;
108
- filterable?:
109
- | boolean
110
- | {
111
- default?: Value;
112
- };
113
- searchable?: boolean;
114
- orderable?: boolean;
115
- comparable?: boolean;
116
- queriable?:
117
- | boolean
118
- | {
119
- roles?: string[];
120
- };
121
- creatable?:
122
- | boolean
123
- | {
124
- roles?: string[];
125
- };
126
- updatable?:
127
- | boolean
128
- | {
129
- roles?: string[];
130
- };
131
- generated?: boolean;
132
- // The tooltip is "hidden" behind an icon in the admin forms
133
- tooltip?: string;
134
- // If true the field must be filled within forms but can be null in the database
135
- required?: boolean;
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
- export type Relation = {
171
- field: RelationField;
172
- model: Model;
173
- reverseRelation: ReverseRelation;
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
- export type ReverseRelation = RelationField & {
177
- model: Model;
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);