@smartive/graphql-magic 6.0.0 → 7.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/CHANGELOG.md +6 -1
- package/dist/cjs/index.cjs +150 -141
- package/dist/esm/client/index.d.ts +1 -0
- package/dist/esm/client/index.js +1 -0
- package/dist/esm/client/index.js.map +1 -1
- package/dist/esm/client/models.d.ts +1 -0
- package/dist/esm/client/models.js +2 -0
- package/dist/esm/client/models.js.map +1 -0
- package/dist/esm/client/queries.d.ts +2 -2
- package/dist/esm/client/queries.js +3 -3
- package/dist/esm/client/queries.js.map +1 -1
- package/dist/esm/context.d.ts +2 -1
- package/dist/esm/db/generate.js +17 -19
- package/dist/esm/db/generate.js.map +1 -1
- package/dist/esm/migrations/generate.js +69 -57
- 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/models.d.ts +65 -59
- package/dist/esm/models/mutation-hook.d.ts +17 -0
- package/dist/esm/models/mutation-hook.js +2 -0
- package/dist/esm/models/mutation-hook.js.map +1 -0
- package/dist/esm/models/utils.d.ts +398 -22
- package/dist/esm/models/utils.js +27 -21
- package/dist/esm/models/utils.js.map +1 -1
- package/dist/esm/permissions/check.js +2 -2
- package/dist/esm/permissions/check.js.map +1 -1
- package/dist/esm/resolvers/filters.js +1 -1
- package/dist/esm/resolvers/mutations.js +4 -4
- package/dist/esm/resolvers/mutations.js.map +1 -1
- package/dist/esm/resolvers/node.js +2 -2
- package/dist/esm/resolvers/node.js.map +1 -1
- package/dist/esm/resolvers/resolver.js +1 -1
- package/dist/esm/schema/generate.js +27 -37
- 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 -2
- package/dist/esm/schema/utils.js.map +1 -1
- package/package.json +5 -5
- package/src/client/index.ts +1 -0
- package/src/client/models.ts +1 -0
- package/src/client/queries.ts +5 -5
- package/src/context.ts +2 -1
- package/src/db/generate.ts +25 -27
- package/src/migrations/generate.ts +69 -57
- package/src/models/index.ts +1 -0
- package/src/models/models.ts +88 -97
- package/src/models/mutation-hook.ts +17 -0
- package/src/models/utils.ts +32 -22
- package/src/permissions/check.ts +2 -2
- package/src/resolvers/filters.ts +1 -1
- package/src/resolvers/mutations.ts +9 -8
- package/src/resolvers/node.ts +2 -2
- package/src/resolvers/resolver.ts +1 -1
- package/src/schema/generate.ts +31 -48
- package/src/schema/utils.ts +3 -3
- package/tests/unit/__snapshots__/generate.spec.ts.snap +2 -2
- package/tests/utils/models.ts +12 -12
|
@@ -131,9 +131,9 @@ export class MigrationGenerator {
|
|
|
131
131
|
model,
|
|
132
132
|
model.fields.filter(
|
|
133
133
|
({ name, ...field }) =>
|
|
134
|
-
field.
|
|
134
|
+
field.kind !== 'raw' &&
|
|
135
135
|
!this.columns[model.name].some(
|
|
136
|
-
(col) => col.name === (field.
|
|
136
|
+
(col) => col.name === (field.kind === 'relation' ? field.foreignKey || `${name}Id` : name)
|
|
137
137
|
)
|
|
138
138
|
),
|
|
139
139
|
up,
|
|
@@ -141,8 +141,8 @@ export class MigrationGenerator {
|
|
|
141
141
|
);
|
|
142
142
|
|
|
143
143
|
// Update fields
|
|
144
|
-
const existingFields = model.fields.filter(({ name,
|
|
145
|
-
const col = this.columns[model.name].find((col) => col.name === (
|
|
144
|
+
const existingFields = model.fields.filter(({ name, kind, nonNull }) => {
|
|
145
|
+
const col = this.columns[model.name].find((col) => col.name === (kind === 'relation' ? `${name}Id` : name));
|
|
146
146
|
if (!col) {
|
|
147
147
|
return false;
|
|
148
148
|
}
|
|
@@ -177,8 +177,8 @@ export class MigrationGenerator {
|
|
|
177
177
|
writer.writeLine(`deleted: row.deleted,`);
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
-
for (const { name,
|
|
181
|
-
const col =
|
|
180
|
+
for (const { name, kind } of model.fields.filter(({ updatable }) => updatable)) {
|
|
181
|
+
const col = kind === 'relation' ? `${name}Id` : name;
|
|
182
182
|
|
|
183
183
|
writer.writeLine(`${col}: row.${col},`);
|
|
184
184
|
}
|
|
@@ -198,10 +198,10 @@ export class MigrationGenerator {
|
|
|
198
198
|
const revisionTable = `${model.name}Revision`;
|
|
199
199
|
const missingRevisionFields = model.fields.filter(
|
|
200
200
|
({ name, updatable, ...field }) =>
|
|
201
|
-
field.
|
|
201
|
+
field.kind !== 'raw' &&
|
|
202
202
|
updatable &&
|
|
203
203
|
!this.columns[revisionTable].some(
|
|
204
|
-
(col) => col.name === (field.
|
|
204
|
+
(col) => col.name === (field.kind === 'relation' ? field.foreignKey || `${name}Id` : name)
|
|
205
205
|
)
|
|
206
206
|
);
|
|
207
207
|
|
|
@@ -210,11 +210,11 @@ export class MigrationGenerator {
|
|
|
210
210
|
const revisionFieldsToRemove = model.fields.filter(
|
|
211
211
|
({ name, updatable, generated, ...field }) =>
|
|
212
212
|
!generated &&
|
|
213
|
-
field.
|
|
213
|
+
field.kind !== 'raw' &&
|
|
214
214
|
!updatable &&
|
|
215
|
-
!(field.
|
|
215
|
+
!(field.kind === 'relation' && field.foreignKey === 'id') &&
|
|
216
216
|
this.columns[revisionTable].some(
|
|
217
|
-
(col) => col.name === (field.
|
|
217
|
+
(col) => col.name === (field.kind === 'relation' ? field.foreignKey || `${name}Id` : name)
|
|
218
218
|
)
|
|
219
219
|
);
|
|
220
220
|
this.createRevisionFields(model, revisionFieldsToRemove, down, up);
|
|
@@ -276,8 +276,8 @@ export class MigrationGenerator {
|
|
|
276
276
|
for (const field of fields) {
|
|
277
277
|
this.alterTable(model.name, () => {
|
|
278
278
|
this.renameColumn(
|
|
279
|
-
field.
|
|
280
|
-
field.
|
|
279
|
+
field.kind === 'relation' ? `${field.oldName}Id` : get(field, 'oldName'),
|
|
280
|
+
field.kind === 'relation' ? `${field.name}Id` : field.name
|
|
281
281
|
);
|
|
282
282
|
});
|
|
283
283
|
}
|
|
@@ -287,8 +287,8 @@ export class MigrationGenerator {
|
|
|
287
287
|
for (const field of fields) {
|
|
288
288
|
this.alterTable(model.name, () => {
|
|
289
289
|
this.renameColumn(
|
|
290
|
-
field.
|
|
291
|
-
field.
|
|
290
|
+
field.kind === 'relation' ? `${field.name}Id` : field.name,
|
|
291
|
+
field.kind === 'relation' ? `${field.oldName}Id` : get(field, 'oldName')
|
|
292
292
|
);
|
|
293
293
|
});
|
|
294
294
|
}
|
|
@@ -296,8 +296,8 @@ export class MigrationGenerator {
|
|
|
296
296
|
|
|
297
297
|
for (const field of fields) {
|
|
298
298
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
299
|
-
summonByName(this.columns[model.name]!, field.
|
|
300
|
-
field.
|
|
299
|
+
summonByName(this.columns[model.name]!, field.kind === 'relation' ? `${field.oldName!}Id` : field.oldName!).name =
|
|
300
|
+
field.kind === 'relation' ? `${field.name}Id` : field.name;
|
|
301
301
|
}
|
|
302
302
|
}
|
|
303
303
|
|
|
@@ -311,10 +311,10 @@ export class MigrationGenerator {
|
|
|
311
311
|
const updates: Callbacks = [];
|
|
312
312
|
const postAlter: Callbacks = [];
|
|
313
313
|
for (const field of fields) {
|
|
314
|
-
alter.push(() => this.column(field, { setNonNull: field.
|
|
314
|
+
alter.push(() => this.column(field, { setNonNull: field.defaultValue !== undefined }));
|
|
315
315
|
|
|
316
316
|
// If the field is not nullable but has no default, write placeholder code
|
|
317
|
-
if (field.nonNull && field.
|
|
317
|
+
if (field.nonNull && field.defaultValue === undefined) {
|
|
318
318
|
updates.push(() => this.writer.write(`${field.name}: 'TODO',`).newLine());
|
|
319
319
|
postAlter.push(() => this.column(field, { alter: true, foreign: false }));
|
|
320
320
|
}
|
|
@@ -343,8 +343,8 @@ export class MigrationGenerator {
|
|
|
343
343
|
|
|
344
344
|
down.push(() => {
|
|
345
345
|
this.alterTable(model.name, () => {
|
|
346
|
-
for (const {
|
|
347
|
-
this.dropColumn(
|
|
346
|
+
for (const { kind, name } of fields) {
|
|
347
|
+
this.dropColumn(kind === 'relation' ? `${name}Id` : name);
|
|
348
348
|
}
|
|
349
349
|
});
|
|
350
350
|
});
|
|
@@ -369,7 +369,7 @@ export class MigrationGenerator {
|
|
|
369
369
|
this.column(
|
|
370
370
|
field,
|
|
371
371
|
{ alter: true },
|
|
372
|
-
summonByName(this.columns[model.name], field.
|
|
372
|
+
summonByName(this.columns[model.name], field.kind === 'relation' ? `${field.name}Id` : field.name)
|
|
373
373
|
);
|
|
374
374
|
}
|
|
375
375
|
});
|
|
@@ -395,7 +395,7 @@ export class MigrationGenerator {
|
|
|
395
395
|
this.column(
|
|
396
396
|
field,
|
|
397
397
|
{ alter: true },
|
|
398
|
-
summonByName(this.columns[model.name], field.
|
|
398
|
+
summonByName(this.columns[model.name], field.kind === 'relation' ? `${field.name}Id` : field.name)
|
|
399
399
|
);
|
|
400
400
|
}
|
|
401
401
|
});
|
|
@@ -439,7 +439,7 @@ export class MigrationGenerator {
|
|
|
439
439
|
this.writer
|
|
440
440
|
.write(`await knex('${model.name}Revision').update(`)
|
|
441
441
|
.inlineBlock(() => {
|
|
442
|
-
for (const { name, type } of missingRevisionFields) {
|
|
442
|
+
for (const { name, kind: type } of missingRevisionFields) {
|
|
443
443
|
const col = type === 'relation' ? `${name}Id` : name;
|
|
444
444
|
this.writer
|
|
445
445
|
.write(
|
|
@@ -467,7 +467,7 @@ export class MigrationGenerator {
|
|
|
467
467
|
down.push(() => {
|
|
468
468
|
this.alterTable(revisionTable, () => {
|
|
469
469
|
for (const field of missingRevisionFields) {
|
|
470
|
-
this.dropColumn(field.
|
|
470
|
+
this.dropColumn(field.kind === 'relation' ? `${field.name}Id` : field.name);
|
|
471
471
|
}
|
|
472
472
|
});
|
|
473
473
|
});
|
|
@@ -565,8 +565,8 @@ export class MigrationGenerator {
|
|
|
565
565
|
}
|
|
566
566
|
}
|
|
567
567
|
}
|
|
568
|
-
if (setDefault && field.
|
|
569
|
-
this.writer.write(`.defaultTo(${this.value(field.
|
|
568
|
+
if (setDefault && field.defaultValue !== undefined) {
|
|
569
|
+
this.writer.write(`.defaultTo(${this.value(field.defaultValue)})`);
|
|
570
570
|
}
|
|
571
571
|
if (primary) {
|
|
572
572
|
this.writer.write('.primary()');
|
|
@@ -578,39 +578,44 @@ export class MigrationGenerator {
|
|
|
578
578
|
}
|
|
579
579
|
this.writer.write(';').newLine();
|
|
580
580
|
};
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
581
|
+
const kind = field.kind;
|
|
582
|
+
switch (kind) {
|
|
583
|
+
case 'primitive':
|
|
584
|
+
switch (field.type) {
|
|
585
|
+
case 'Boolean':
|
|
586
|
+
col(`table.boolean('${name}')`);
|
|
587
|
+
break;
|
|
588
|
+
case 'Int':
|
|
589
|
+
col(`table.integer('${name}')`);
|
|
590
|
+
break;
|
|
591
|
+
case 'Float':
|
|
592
|
+
if (field.double) {
|
|
593
|
+
col(`table.double('${name}')`);
|
|
594
|
+
} else {
|
|
595
|
+
col(`table.decimal('${name}', ${get(field, 'precision')}, ${get(field, 'scale')})`);
|
|
596
|
+
}
|
|
597
|
+
break;
|
|
598
|
+
case 'String':
|
|
599
|
+
if (field.large) {
|
|
600
|
+
col(`table.text('${name}')`);
|
|
601
|
+
} else {
|
|
602
|
+
col(`table.string('${name}', ${field.maxLength})`);
|
|
603
|
+
}
|
|
604
|
+
break;
|
|
605
|
+
case 'DateTime':
|
|
606
|
+
col(`table.timestamp('${name}')`);
|
|
607
|
+
break;
|
|
608
|
+
case 'ID':
|
|
609
|
+
col(`table.uuid('${name}')`);
|
|
610
|
+
break;
|
|
611
|
+
case 'Upload':
|
|
612
|
+
break;
|
|
600
613
|
}
|
|
601
614
|
break;
|
|
602
|
-
case 'DateTime':
|
|
603
|
-
col(`table.timestamp('${name}')`);
|
|
604
|
-
break;
|
|
605
|
-
case 'ID':
|
|
606
|
-
col(`table.uuid('${name}')`);
|
|
607
|
-
break;
|
|
608
|
-
case 'Upload':
|
|
609
|
-
break;
|
|
610
615
|
case 'relation':
|
|
611
616
|
col(`table.uuid('${name}Id')`);
|
|
612
617
|
if (foreign && !alter) {
|
|
613
|
-
this.writer.writeLine(`table.foreign('${name}Id').references('id').inTable('${field.
|
|
618
|
+
this.writer.writeLine(`table.foreign('${name}Id').references('id').inTable('${field.type}');`);
|
|
614
619
|
}
|
|
615
620
|
break;
|
|
616
621
|
case 'enum':
|
|
@@ -628,8 +633,15 @@ export class MigrationGenerator {
|
|
|
628
633
|
}
|
|
629
634
|
col();
|
|
630
635
|
break;
|
|
631
|
-
|
|
632
|
-
|
|
636
|
+
case 'json':
|
|
637
|
+
this.writer.write(`table.json('${typeToField(field.type)}')`);
|
|
638
|
+
break;
|
|
639
|
+
case 'raw':
|
|
640
|
+
throw new Error("Raw fields aren't stored in the database");
|
|
641
|
+
default: {
|
|
642
|
+
const exhaustiveCheck: never = kind;
|
|
643
|
+
throw new Error(exhaustiveCheck);
|
|
644
|
+
}
|
|
633
645
|
}
|
|
634
646
|
}
|
|
635
647
|
}
|
package/src/models/index.ts
CHANGED
package/src/models/models.ts
CHANGED
|
@@ -1,62 +1,48 @@
|
|
|
1
|
-
import { DateTime } from 'luxon';
|
|
2
1
|
import { Field } from '..';
|
|
3
|
-
import type { Context } from '../context';
|
|
4
2
|
import type { OrderBy } from '../resolvers/arguments';
|
|
5
3
|
import type { Value } from '../values';
|
|
6
4
|
|
|
7
5
|
export type RawModels = RawModel[];
|
|
8
6
|
|
|
9
|
-
export type RawModel =
|
|
10
|
-
|
|
11
|
-
type BaseModel = {
|
|
7
|
+
export type RawModel = {
|
|
12
8
|
name: string;
|
|
13
9
|
plural?: string;
|
|
14
10
|
description?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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: 'raw';
|
|
18
|
+
fields: RawObjectField[];
|
|
19
|
+
}
|
|
20
|
+
| {
|
|
21
|
+
kind: 'object';
|
|
22
|
+
interfaces?: string[];
|
|
23
|
+
queriable?: boolean;
|
|
24
|
+
listQueriable?: boolean;
|
|
25
|
+
creatable?: boolean | { createdBy?: Partial<RelationField>; createdAt?: Partial<DateTimeField> };
|
|
26
|
+
updatable?: boolean | { updatedBy?: Partial<RelationField>; updatedAt?: Partial<DateTimeField> };
|
|
27
|
+
deletable?:
|
|
28
|
+
| boolean
|
|
29
|
+
| { deleted?: Partial<BooleanField>; deletedBy?: Partial<RelationField>; deletedAt?: Partial<DateTimeField> };
|
|
30
|
+
displayField?: string;
|
|
31
|
+
defaultOrderBy?: OrderBy;
|
|
32
|
+
fields: ModelField[];
|
|
33
|
+
|
|
34
|
+
// temporary fields for the generation of migrations
|
|
35
|
+
deleted?: true;
|
|
36
|
+
oldName?: string;
|
|
37
|
+
}
|
|
38
|
+
);
|
|
29
39
|
|
|
30
|
-
export type
|
|
31
|
-
|
|
32
|
-
export type
|
|
33
|
-
|
|
34
|
-
export type
|
|
35
|
-
|
|
36
|
-
action: Action,
|
|
37
|
-
when: 'before' | 'after',
|
|
38
|
-
data: { prev: Entity; input: Entity; normalizedInput: Entity; next: Entity },
|
|
39
|
-
ctx: Context
|
|
40
|
-
) => Promise<void>;
|
|
41
|
-
|
|
42
|
-
export type ObjectModel = BaseModel & {
|
|
43
|
-
type: 'object';
|
|
44
|
-
interfaces?: string[];
|
|
45
|
-
queriable?: boolean;
|
|
46
|
-
listQueriable?: boolean;
|
|
47
|
-
creatable?: boolean | { createdBy?: Partial<RelationField>; createdAt?: Partial<DateTimeField> };
|
|
48
|
-
updatable?: boolean | { updatedBy?: Partial<RelationField>; updatedAt?: Partial<DateTimeField> };
|
|
49
|
-
deletable?:
|
|
50
|
-
| boolean
|
|
51
|
-
| { deleted?: Partial<BooleanField>; deletedBy?: Partial<RelationField>; deletedAt?: Partial<DateTimeField> };
|
|
52
|
-
displayField?: string;
|
|
53
|
-
defaultOrderBy?: OrderBy;
|
|
54
|
-
fields: ModelField[];
|
|
55
|
-
|
|
56
|
-
// temporary fields for the generation of migrations
|
|
57
|
-
deleted?: true;
|
|
58
|
-
oldName?: string;
|
|
59
|
-
};
|
|
40
|
+
export type ScalarModel = Extract<RawModel, { kind: 'scalar' }>;
|
|
41
|
+
export type EnumModel = Extract<RawModel, { kind: 'enum' }>;
|
|
42
|
+
export type RawEnumModel = Extract<RawModel, { kind: 'raw-enum' }>;
|
|
43
|
+
export type InterfaceModel = Extract<RawModel, { kind: 'interface' }>;
|
|
44
|
+
export type RawObjectModel = Extract<RawModel, { kind: 'raw' }>;
|
|
45
|
+
export type ObjectModel = Extract<RawModel, { kind: 'object' }>;
|
|
60
46
|
|
|
61
47
|
type BaseNumberType = {
|
|
62
48
|
unit?: 'million';
|
|
@@ -64,46 +50,48 @@ type BaseNumberType = {
|
|
|
64
50
|
max?: number;
|
|
65
51
|
};
|
|
66
52
|
|
|
67
|
-
type
|
|
53
|
+
type FieldBase = Omit<Field, 'type'>;
|
|
68
54
|
|
|
69
|
-
type
|
|
70
|
-
| {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
55
|
+
type FieldBase2 =
|
|
56
|
+
| ({ kind?: 'primitive' | undefined } & (
|
|
57
|
+
| { type: 'ID' }
|
|
58
|
+
| { type: 'Boolean' }
|
|
59
|
+
| {
|
|
60
|
+
type: 'String';
|
|
61
|
+
stringType?: 'email' | 'url' | 'phone';
|
|
62
|
+
large?: true;
|
|
63
|
+
maxLength?: number;
|
|
64
|
+
}
|
|
65
|
+
| {
|
|
66
|
+
type: 'DateTime';
|
|
67
|
+
dateTimeType?: 'year' | 'date' | 'datetime' | 'year_and_month';
|
|
68
|
+
endOfDay?: boolean;
|
|
69
|
+
}
|
|
70
|
+
| ({
|
|
71
|
+
type: 'Int';
|
|
72
|
+
intType?: 'currency';
|
|
73
|
+
} & BaseNumberType)
|
|
74
|
+
| ({
|
|
75
|
+
type: 'Float';
|
|
76
|
+
floatType?: 'currency' | 'percentage';
|
|
77
|
+
double?: boolean;
|
|
78
|
+
precision?: number;
|
|
79
|
+
scale?: number;
|
|
80
|
+
} & BaseNumberType)
|
|
81
|
+
| { type: 'Upload' }
|
|
82
|
+
))
|
|
83
|
+
| { kind: 'enum'; type: string; possibleValues?: Value[] }
|
|
84
|
+
| { kind: 'raw'; type: string };
|
|
85
|
+
|
|
86
|
+
export type RawObjectField = FieldBase & FieldBase2;
|
|
87
|
+
|
|
88
|
+
export type ModelField = FieldBase &
|
|
99
89
|
(
|
|
100
|
-
|
|
|
101
|
-
| {
|
|
102
|
-
| { type: 'enum'; typeName: string; possibleValues?: Value[] }
|
|
103
|
-
| { type: 'raw'; typeName: string }
|
|
90
|
+
| FieldBase2
|
|
91
|
+
| { kind: 'json'; type: string }
|
|
104
92
|
| {
|
|
105
|
-
|
|
106
|
-
|
|
93
|
+
kind: 'relation';
|
|
94
|
+
type: string;
|
|
107
95
|
toOne?: boolean;
|
|
108
96
|
reverse?: string;
|
|
109
97
|
foreignKey?: string;
|
|
@@ -138,7 +126,6 @@ export type ModelField = BaseField &
|
|
|
138
126
|
generated?: boolean;
|
|
139
127
|
// The tooltip is "hidden" behind an icon in the admin forms
|
|
140
128
|
tooltip?: string;
|
|
141
|
-
defaultValue?: string | number | ReadonlyArray<string> | undefined;
|
|
142
129
|
// If true the field must be filled within forms but can be null in the database
|
|
143
130
|
required?: boolean;
|
|
144
131
|
indent?: boolean;
|
|
@@ -148,18 +135,22 @@ export type ModelField = BaseField &
|
|
|
148
135
|
// temporary fields for the generation of migrations
|
|
149
136
|
deleted?: true;
|
|
150
137
|
oldName?: string;
|
|
138
|
+
|
|
139
|
+
meta?: Record<string, unknown>;
|
|
151
140
|
};
|
|
152
141
|
|
|
153
|
-
export type
|
|
154
|
-
export type
|
|
155
|
-
export type
|
|
156
|
-
export type
|
|
157
|
-
export type
|
|
158
|
-
export type
|
|
159
|
-
export type
|
|
160
|
-
export type
|
|
161
|
-
export type
|
|
162
|
-
export type
|
|
142
|
+
export type PrimitiveField = Extract<ModelField, { kind?: 'primitive' | undefined }>;
|
|
143
|
+
export type IDField = Extract<PrimitiveField, { type: 'ID' }>;
|
|
144
|
+
export type BooleanField = Extract<PrimitiveField, { type: 'Boolean' }>;
|
|
145
|
+
export type StringField = Extract<PrimitiveField, { type: 'String' }>;
|
|
146
|
+
export type DateTimeField = Extract<PrimitiveField, { type: 'DateTime' }>;
|
|
147
|
+
export type IntField = Extract<PrimitiveField, { type: 'Int' }>;
|
|
148
|
+
export type FloatField = Extract<PrimitiveField, { type: 'Float' }>;
|
|
149
|
+
export type UploadField = Extract<PrimitiveField, { type: 'Upload' }>;
|
|
150
|
+
export type JsonField = Extract<ModelField, { kind: 'json' }>;
|
|
151
|
+
export type EnumField = Extract<ModelField, { kind: 'enum' }>;
|
|
152
|
+
export type RawField = Extract<ModelField, { kind: 'raw' }>;
|
|
153
|
+
export type RelationField = Extract<ModelField, { kind: 'relation' }>;
|
|
163
154
|
|
|
164
155
|
export type Models = Model[];
|
|
165
156
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { DateTime } from 'luxon';
|
|
2
|
+
import { Model } from '.';
|
|
3
|
+
import { Context } from '..';
|
|
4
|
+
|
|
5
|
+
export type Entity = Record<string, unknown> & { createdAt?: DateTime; deletedAt?: DateTime };
|
|
6
|
+
|
|
7
|
+
export type FullEntity = Entity & { id: string };
|
|
8
|
+
|
|
9
|
+
export type Action = 'create' | 'update' | 'delete' | 'restore';
|
|
10
|
+
|
|
11
|
+
export type MutationHook = (
|
|
12
|
+
model: Model,
|
|
13
|
+
action: Action,
|
|
14
|
+
when: 'before' | 'after',
|
|
15
|
+
data: { prev: Entity; input: Entity; normalizedInput: Entity; next: FullEntity },
|
|
16
|
+
ctx: Context
|
|
17
|
+
) => Promise<void>;
|
package/src/models/utils.ts
CHANGED
|
@@ -7,11 +7,13 @@ import startCase from 'lodash/startCase';
|
|
|
7
7
|
import {
|
|
8
8
|
BooleanField,
|
|
9
9
|
DateTimeField,
|
|
10
|
+
EnumField,
|
|
10
11
|
EnumModel,
|
|
11
12
|
Model,
|
|
12
13
|
ModelField,
|
|
13
14
|
Models,
|
|
14
15
|
ObjectModel,
|
|
16
|
+
PrimitiveField,
|
|
15
17
|
RawEnumModel,
|
|
16
18
|
RawField,
|
|
17
19
|
RawModel,
|
|
@@ -43,18 +45,18 @@ export const getModelLabel = (model: Model) => getLabel(model.name);
|
|
|
43
45
|
|
|
44
46
|
export const getLabel = (s: string) => startCase(camelCase(s));
|
|
45
47
|
|
|
46
|
-
export const isObjectModel = (model: RawModel): model is ObjectModel => model.
|
|
48
|
+
export const isObjectModel = (model: RawModel): model is ObjectModel => model.kind === 'object';
|
|
47
49
|
|
|
48
|
-
export const isEnumModel = (model: RawModel): model is EnumModel => model.
|
|
50
|
+
export const isEnumModel = (model: RawModel): model is EnumModel => model.kind === 'enum';
|
|
49
51
|
|
|
50
|
-
export const isRawEnumModel = (model: RawModel): model is RawEnumModel => model.
|
|
52
|
+
export const isRawEnumModel = (model: RawModel): model is RawEnumModel => model.kind === 'raw-enum';
|
|
51
53
|
|
|
52
|
-
export const isScalarModel = (model: RawModel): model is ScalarModel => model.
|
|
54
|
+
export const isScalarModel = (model: RawModel): model is ScalarModel => model.kind === 'scalar';
|
|
53
55
|
|
|
54
|
-
export const isRawObjectModel = (model: RawModel): model is RawObjectModel => model.
|
|
56
|
+
export const isRawObjectModel = (model: RawModel): model is RawObjectModel => model.kind === 'raw';
|
|
55
57
|
|
|
56
58
|
export const isEnumList = (models: RawModels, field: ModelField) =>
|
|
57
|
-
field?.list === true && models.find(({ name }) => name === field.
|
|
59
|
+
field?.list === true && models.find(({ name }) => name === field.kind)?.kind === 'enum';
|
|
58
60
|
|
|
59
61
|
export const and =
|
|
60
62
|
(...predicates: ((field: ModelField) => boolean)[]) =>
|
|
@@ -63,13 +65,18 @@ export const and =
|
|
|
63
65
|
|
|
64
66
|
export const not = (predicate: (field: ModelField) => boolean) => (field: ModelField) => !predicate(field);
|
|
65
67
|
|
|
66
|
-
export const
|
|
68
|
+
export const isPrimitive = (field: ModelField): field is PrimitiveField =>
|
|
69
|
+
field.kind === undefined || field.kind === 'primitive';
|
|
70
|
+
|
|
71
|
+
export const isEnum = (field: ModelField): field is EnumField => field.kind === 'enum';
|
|
72
|
+
|
|
73
|
+
export const isRelation = (field: ModelField): field is RelationField => field.kind === 'relation';
|
|
67
74
|
|
|
68
75
|
export const isToOneRelation = (field: ModelField): field is RelationField => isRelation(field) && !!field.toOne;
|
|
69
76
|
|
|
70
77
|
export const isQueriableField = ({ queriable }: ModelField) => queriable !== false;
|
|
71
78
|
|
|
72
|
-
export const isRaw = (field: ModelField): field is RawField => field.
|
|
79
|
+
export const isRaw = (field: ModelField): field is RawField => field.kind === 'raw';
|
|
73
80
|
|
|
74
81
|
export const isVisible = ({ hidden }: ModelField) => hidden !== true;
|
|
75
82
|
|
|
@@ -80,7 +87,11 @@ export const isUpdatable = ({ updatable }: ModelField) => !!updatable;
|
|
|
80
87
|
export const isCreatable = ({ creatable }: ModelField) => !!creatable;
|
|
81
88
|
|
|
82
89
|
export const isQueriableBy = (role: string) => (field: ModelField) =>
|
|
83
|
-
field.queriable !== false &&
|
|
90
|
+
field.queriable !== false &&
|
|
91
|
+
(field.queriable === undefined ||
|
|
92
|
+
field.queriable === true ||
|
|
93
|
+
!field.queriable.roles ||
|
|
94
|
+
field.queriable.roles.includes(role));
|
|
84
95
|
|
|
85
96
|
export const isUpdatableBy = (role: string) => (field: ModelField) =>
|
|
86
97
|
field.updatable && (field.updatable === true || !field.updatable.roles || field.updatable.roles.includes(role));
|
|
@@ -114,7 +125,6 @@ export const getModels = (rawModels: RawModels): Models => {
|
|
|
114
125
|
{
|
|
115
126
|
name: 'createdAt',
|
|
116
127
|
type: 'DateTime',
|
|
117
|
-
|
|
118
128
|
nonNull: true,
|
|
119
129
|
orderable: true,
|
|
120
130
|
generated: true,
|
|
@@ -122,8 +132,8 @@ export const getModels = (rawModels: RawModels): Models => {
|
|
|
122
132
|
} satisfies DateTimeField,
|
|
123
133
|
{
|
|
124
134
|
name: 'createdBy',
|
|
125
|
-
|
|
126
|
-
|
|
135
|
+
kind: 'relation',
|
|
136
|
+
type: 'User',
|
|
127
137
|
nonNull: true,
|
|
128
138
|
reverse: `created${getModelPlural(model)}`,
|
|
129
139
|
generated: true,
|
|
@@ -143,8 +153,8 @@ export const getModels = (rawModels: RawModels): Models => {
|
|
|
143
153
|
} satisfies DateTimeField,
|
|
144
154
|
{
|
|
145
155
|
name: 'updatedBy',
|
|
146
|
-
|
|
147
|
-
|
|
156
|
+
kind: 'relation',
|
|
157
|
+
type: 'User',
|
|
148
158
|
nonNull: true,
|
|
149
159
|
reverse: `updated${getModelPlural(model)}`,
|
|
150
160
|
generated: true,
|
|
@@ -158,7 +168,7 @@ export const getModels = (rawModels: RawModels): Models => {
|
|
|
158
168
|
name: 'deleted',
|
|
159
169
|
type: 'Boolean',
|
|
160
170
|
nonNull: true,
|
|
161
|
-
|
|
171
|
+
defaultValue: false,
|
|
162
172
|
filterable: { default: false },
|
|
163
173
|
generated: true,
|
|
164
174
|
...(typeof model.deletable === 'object' && model.deletable.deleted),
|
|
@@ -172,8 +182,8 @@ export const getModels = (rawModels: RawModels): Models => {
|
|
|
172
182
|
} satisfies DateTimeField,
|
|
173
183
|
{
|
|
174
184
|
name: 'deletedBy',
|
|
175
|
-
|
|
176
|
-
|
|
185
|
+
kind: 'relation',
|
|
186
|
+
type: 'User',
|
|
177
187
|
reverse: `deleted${getModelPlural(model)}`,
|
|
178
188
|
generated: true,
|
|
179
189
|
...(typeof model.deletable === 'object' && model.deletable.deletedBy),
|
|
@@ -183,7 +193,7 @@ export const getModels = (rawModels: RawModels): Models => {
|
|
|
183
193
|
] satisfies ModelField[]
|
|
184
194
|
).map((field: ModelField) => ({
|
|
185
195
|
...field,
|
|
186
|
-
...(field.
|
|
196
|
+
...(field.kind === 'relation' && {
|
|
187
197
|
foreignKey: field.foreignKey || `${field.name}Id`,
|
|
188
198
|
}),
|
|
189
199
|
})),
|
|
@@ -198,17 +208,17 @@ export const getModels = (rawModels: RawModels): Models => {
|
|
|
198
208
|
|
|
199
209
|
for (const model of models) {
|
|
200
210
|
for (const field of model.fields) {
|
|
201
|
-
if (field.
|
|
211
|
+
if (field.kind !== 'relation') {
|
|
202
212
|
continue;
|
|
203
213
|
}
|
|
204
214
|
|
|
205
|
-
const fieldModel = summonByName(models, field.
|
|
215
|
+
const fieldModel = summonByName(models, field.type);
|
|
206
216
|
|
|
207
217
|
const reverseRelation: ReverseRelation = {
|
|
208
|
-
|
|
218
|
+
kind: 'relation',
|
|
209
219
|
name: field.reverse || (field.toOne ? typeToField(model.name) : getModelPluralField(model)),
|
|
210
220
|
foreignKey: get(field, 'foreignKey'),
|
|
211
|
-
|
|
221
|
+
type: model.name,
|
|
212
222
|
toOne: !!field.toOne,
|
|
213
223
|
fieldModel,
|
|
214
224
|
field,
|
package/src/permissions/check.ts
CHANGED
|
@@ -147,8 +147,8 @@ export const checkCanWrite = async (
|
|
|
147
147
|
continue;
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
const fieldPermissions = field[action === 'CREATE' ? '
|
|
151
|
-
if (fieldPermissions && !fieldPermissions.includes(ctx.user.role)) {
|
|
150
|
+
const fieldPermissions = field[action === 'CREATE' ? 'creatable' : 'updatable'];
|
|
151
|
+
if (fieldPermissions && typeof fieldPermissions === 'object' && !fieldPermissions.roles?.includes(ctx.user.role)) {
|
|
152
152
|
throw new PermissionError(action, `this ${model.name}'s ${field.name}`, 'field permission not available');
|
|
153
153
|
}
|
|
154
154
|
|