@smartive/graphql-magic 5.1.2 → 7.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/CHANGELOG.md +1 -6
- package/dist/cjs/index.cjs +149 -140
- 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 +5 -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 +22 -20
- 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 +4 -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 +27 -21
- 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/unit/resolve.spec.ts +1 -0
- package/tests/utils/models.ts +12 -12
- package/tests/utils/server.ts +1 -0
|
@@ -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
|
|
|
@@ -114,7 +121,6 @@ export const getModels = (rawModels: RawModels): Models => {
|
|
|
114
121
|
{
|
|
115
122
|
name: 'createdAt',
|
|
116
123
|
type: 'DateTime',
|
|
117
|
-
|
|
118
124
|
nonNull: true,
|
|
119
125
|
orderable: true,
|
|
120
126
|
generated: true,
|
|
@@ -122,8 +128,8 @@ export const getModels = (rawModels: RawModels): Models => {
|
|
|
122
128
|
} satisfies DateTimeField,
|
|
123
129
|
{
|
|
124
130
|
name: 'createdBy',
|
|
125
|
-
|
|
126
|
-
|
|
131
|
+
kind: 'relation',
|
|
132
|
+
type: 'User',
|
|
127
133
|
nonNull: true,
|
|
128
134
|
reverse: `created${getModelPlural(model)}`,
|
|
129
135
|
generated: true,
|
|
@@ -143,8 +149,8 @@ export const getModels = (rawModels: RawModels): Models => {
|
|
|
143
149
|
} satisfies DateTimeField,
|
|
144
150
|
{
|
|
145
151
|
name: 'updatedBy',
|
|
146
|
-
|
|
147
|
-
|
|
152
|
+
kind: 'relation',
|
|
153
|
+
type: 'User',
|
|
148
154
|
nonNull: true,
|
|
149
155
|
reverse: `updated${getModelPlural(model)}`,
|
|
150
156
|
generated: true,
|
|
@@ -158,7 +164,7 @@ export const getModels = (rawModels: RawModels): Models => {
|
|
|
158
164
|
name: 'deleted',
|
|
159
165
|
type: 'Boolean',
|
|
160
166
|
nonNull: true,
|
|
161
|
-
|
|
167
|
+
defaultValue: false,
|
|
162
168
|
filterable: { default: false },
|
|
163
169
|
generated: true,
|
|
164
170
|
...(typeof model.deletable === 'object' && model.deletable.deleted),
|
|
@@ -172,8 +178,8 @@ export const getModels = (rawModels: RawModels): Models => {
|
|
|
172
178
|
} satisfies DateTimeField,
|
|
173
179
|
{
|
|
174
180
|
name: 'deletedBy',
|
|
175
|
-
|
|
176
|
-
|
|
181
|
+
kind: 'relation',
|
|
182
|
+
type: 'User',
|
|
177
183
|
reverse: `deleted${getModelPlural(model)}`,
|
|
178
184
|
generated: true,
|
|
179
185
|
...(typeof model.deletable === 'object' && model.deletable.deletedBy),
|
|
@@ -183,7 +189,7 @@ export const getModels = (rawModels: RawModels): Models => {
|
|
|
183
189
|
] satisfies ModelField[]
|
|
184
190
|
).map((field: ModelField) => ({
|
|
185
191
|
...field,
|
|
186
|
-
...(field.
|
|
192
|
+
...(field.kind === 'relation' && {
|
|
187
193
|
foreignKey: field.foreignKey || `${field.name}Id`,
|
|
188
194
|
}),
|
|
189
195
|
})),
|
|
@@ -198,17 +204,17 @@ export const getModels = (rawModels: RawModels): Models => {
|
|
|
198
204
|
|
|
199
205
|
for (const model of models) {
|
|
200
206
|
for (const field of model.fields) {
|
|
201
|
-
if (field.
|
|
207
|
+
if (field.kind !== 'relation') {
|
|
202
208
|
continue;
|
|
203
209
|
}
|
|
204
210
|
|
|
205
|
-
const fieldModel = summonByName(models, field.
|
|
211
|
+
const fieldModel = summonByName(models, field.type);
|
|
206
212
|
|
|
207
213
|
const reverseRelation: ReverseRelation = {
|
|
208
|
-
|
|
214
|
+
kind: 'relation',
|
|
209
215
|
name: field.reverse || (field.toOne ? typeToField(model.name) : getModelPluralField(model)),
|
|
210
216
|
foreignKey: get(field, 'foreignKey'),
|
|
211
|
-
|
|
217
|
+
type: model.name,
|
|
212
218
|
toOne: !!field.toOne,
|
|
213
219
|
fieldModel,
|
|
214
220
|
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
|
|
package/src/resolvers/filters.ts
CHANGED
|
@@ -85,7 +85,7 @@ const applyWhere = (node: WhereNode, where: Where, ops: Ops<Knex.QueryBuilder>,
|
|
|
85
85
|
const field = summonByName(node.model.fields, key);
|
|
86
86
|
const fullKey = `${node.shortTableAlias}.${key}`;
|
|
87
87
|
|
|
88
|
-
if (field.
|
|
88
|
+
if (field.kind === 'relation') {
|
|
89
89
|
const relation = get(node.model.relationsByName, field.name);
|
|
90
90
|
const tableAlias = `${node.model.name}__W__${key}`;
|
|
91
91
|
const subNode: WhereNode = {
|