@smartive/graphql-magic 3.1.0 → 4.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 +196 -170
- package/dist/esm/client/queries.d.ts +4 -1
- package/dist/esm/client/queries.js +7 -4
- package/dist/esm/client/queries.js.map +1 -1
- package/dist/esm/db/generate.js +21 -13
- package/dist/esm/db/generate.js.map +1 -1
- package/dist/esm/generate/generate.js +17 -22
- package/dist/esm/generate/generate.js.map +1 -1
- package/dist/esm/generate/utils.d.ts +10 -1
- package/dist/esm/generate/utils.js.map +1 -1
- package/dist/esm/migrations/generate.js +82 -89
- package/dist/esm/migrations/generate.js.map +1 -1
- package/dist/esm/models.d.ts +115 -93
- package/dist/esm/models.js +1 -26
- package/dist/esm/models.js.map +1 -1
- package/dist/esm/permissions/check.js +2 -2
- package/dist/esm/permissions/check.js.map +1 -1
- package/dist/esm/permissions/generate.js +2 -2
- package/dist/esm/permissions/generate.js.map +1 -1
- package/dist/esm/resolvers/filters.js +1 -1
- package/dist/esm/resolvers/filters.js.map +1 -1
- package/dist/esm/resolvers/mutations.js +5 -6
- package/dist/esm/resolvers/mutations.js.map +1 -1
- package/dist/esm/resolvers/node.js +4 -5
- package/dist/esm/resolvers/node.js.map +1 -1
- package/dist/esm/resolvers/resolver.js +2 -2
- package/dist/esm/resolvers/resolver.js.map +1 -1
- package/dist/esm/utils.d.ts +181 -1
- package/dist/esm/utils.js +69 -21
- package/dist/esm/utils.js.map +1 -1
- package/package.json +2 -2
- package/src/client/queries.ts +16 -10
- package/src/db/generate.ts +22 -15
- package/src/generate/generate.ts +26 -34
- package/src/generate/utils.ts +11 -1
- package/src/migrations/generate.ts +84 -82
- package/src/models.ts +113 -157
- package/src/permissions/check.ts +2 -2
- package/src/permissions/generate.ts +2 -2
- package/src/resolvers/filters.ts +1 -1
- package/src/resolvers/mutations.ts +10 -9
- package/src/resolvers/node.ts +6 -6
- package/src/resolvers/resolver.ts +2 -2
- package/src/utils.ts +158 -57
- package/tests/utils/models.ts +7 -6
|
@@ -4,8 +4,8 @@ import { SchemaInspector } from 'knex-schema-inspector';
|
|
|
4
4
|
import { Column } from 'knex-schema-inspector/dist/types/column';
|
|
5
5
|
import { SchemaInspector as SchemaInspectorType } from 'knex-schema-inspector/dist/types/schema-inspector';
|
|
6
6
|
import lowerFirst from 'lodash/lowerFirst';
|
|
7
|
-
import { EnumModel,
|
|
8
|
-
import { get, getModels, summonByName, typeToField } from '../utils';
|
|
7
|
+
import { EnumModel, Model, ModelField, Models, RawModels } from '../models';
|
|
8
|
+
import { get, getModels, isEnumModel, summonByName, typeToField } from '../utils';
|
|
9
9
|
import { Value } from '../values';
|
|
10
10
|
|
|
11
11
|
type Callbacks = (() => void)[];
|
|
@@ -130,20 +130,23 @@ export class MigrationGenerator {
|
|
|
130
130
|
this.createFields(
|
|
131
131
|
model,
|
|
132
132
|
model.fields.filter(
|
|
133
|
-
({ name,
|
|
134
|
-
|
|
133
|
+
({ name, ...field }) =>
|
|
134
|
+
field.type !== 'raw' &&
|
|
135
|
+
!this.columns[model.name].some(
|
|
136
|
+
(col) => col.name === (field.type === 'relation' ? field.foreignKey || `${name}Id` : name)
|
|
137
|
+
)
|
|
135
138
|
),
|
|
136
139
|
up,
|
|
137
140
|
down
|
|
138
141
|
);
|
|
139
142
|
|
|
140
143
|
// Update fields
|
|
141
|
-
const existingFields = model.fields.filter(({ name,
|
|
142
|
-
const col = this.columns[model.name].find((col) => col.name === (relation ? `${name}Id` : name));
|
|
144
|
+
const existingFields = model.fields.filter(({ name, type, nonNull }) => {
|
|
145
|
+
const col = this.columns[model.name].find((col) => col.name === (type === 'relation' ? `${name}Id` : name));
|
|
143
146
|
if (!col) {
|
|
144
147
|
return false;
|
|
145
148
|
}
|
|
146
|
-
return !
|
|
149
|
+
return !nonNull && !col.is_nullable;
|
|
147
150
|
});
|
|
148
151
|
this.updateFields(model, existingFields, up, down);
|
|
149
152
|
}
|
|
@@ -174,8 +177,8 @@ export class MigrationGenerator {
|
|
|
174
177
|
writer.writeLine(`deleted: row.deleted,`);
|
|
175
178
|
}
|
|
176
179
|
|
|
177
|
-
for (const { name,
|
|
178
|
-
const col = relation ? `${name}Id` : name;
|
|
180
|
+
for (const { name, type } of model.fields.filter(({ updatable }) => updatable)) {
|
|
181
|
+
const col = type === 'relation' ? `${name}Id` : name;
|
|
179
182
|
|
|
180
183
|
writer.writeLine(`${col}: row.${col},`);
|
|
181
184
|
}
|
|
@@ -194,21 +197,25 @@ export class MigrationGenerator {
|
|
|
194
197
|
} else {
|
|
195
198
|
const revisionTable = `${model.name}Revision`;
|
|
196
199
|
const missingRevisionFields = model.fields.filter(
|
|
197
|
-
({ name,
|
|
198
|
-
|
|
200
|
+
({ name, updatable, ...field }) =>
|
|
201
|
+
field.type !== 'raw' &&
|
|
199
202
|
updatable &&
|
|
200
|
-
!this.columns[revisionTable].some(
|
|
203
|
+
!this.columns[revisionTable].some(
|
|
204
|
+
(col) => col.name === (field.type === 'relation' ? field.foreignKey || `${name}Id` : name)
|
|
205
|
+
)
|
|
201
206
|
);
|
|
202
207
|
|
|
203
208
|
this.createRevisionFields(model, missingRevisionFields, up, down);
|
|
204
209
|
|
|
205
210
|
const revisionFieldsToRemove = model.fields.filter(
|
|
206
|
-
({ name, updatable,
|
|
211
|
+
({ name, updatable, generated, ...field }) =>
|
|
207
212
|
!generated &&
|
|
208
|
-
|
|
213
|
+
field.type !== 'raw' &&
|
|
209
214
|
!updatable &&
|
|
210
|
-
foreignKey
|
|
211
|
-
this.columns[revisionTable].some(
|
|
215
|
+
!(field.type === 'relation' && field.foreignKey === 'id') &&
|
|
216
|
+
this.columns[revisionTable].some(
|
|
217
|
+
(col) => col.name === (field.type === 'relation' ? field.foreignKey || `${name}Id` : name)
|
|
218
|
+
)
|
|
212
219
|
);
|
|
213
220
|
this.createRevisionFields(model, revisionFieldsToRemove, down, up);
|
|
214
221
|
}
|
|
@@ -269,8 +276,8 @@ export class MigrationGenerator {
|
|
|
269
276
|
for (const field of fields) {
|
|
270
277
|
this.alterTable(model.name, () => {
|
|
271
278
|
this.renameColumn(
|
|
272
|
-
field.relation ? `${field.oldName}Id` : get(field, 'oldName'),
|
|
273
|
-
field.relation ? `${field.name}Id` : field.name
|
|
279
|
+
field.type === 'relation' ? `${field.oldName}Id` : get(field, 'oldName'),
|
|
280
|
+
field.type === 'relation' ? `${field.name}Id` : field.name
|
|
274
281
|
);
|
|
275
282
|
});
|
|
276
283
|
}
|
|
@@ -280,8 +287,8 @@ export class MigrationGenerator {
|
|
|
280
287
|
for (const field of fields) {
|
|
281
288
|
this.alterTable(model.name, () => {
|
|
282
289
|
this.renameColumn(
|
|
283
|
-
field.relation ? `${field.name}Id` : field.name,
|
|
284
|
-
field.relation ? `${field.oldName}Id` : get(field, 'oldName')
|
|
290
|
+
field.type === 'relation' ? `${field.name}Id` : field.name,
|
|
291
|
+
field.type === 'relation' ? `${field.oldName}Id` : get(field, 'oldName')
|
|
285
292
|
);
|
|
286
293
|
});
|
|
287
294
|
}
|
|
@@ -289,9 +296,8 @@ export class MigrationGenerator {
|
|
|
289
296
|
|
|
290
297
|
for (const field of fields) {
|
|
291
298
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
292
|
-
summonByName(this.columns[model.name]!, field.relation ? `${field.oldName!}Id` : field.oldName!).name =
|
|
293
|
-
? `${field.name}Id`
|
|
294
|
-
: field.name;
|
|
299
|
+
summonByName(this.columns[model.name]!, field.type === 'relation' ? `${field.oldName!}Id` : field.oldName!).name =
|
|
300
|
+
field.type === 'relation' ? `${field.name}Id` : field.name;
|
|
295
301
|
}
|
|
296
302
|
}
|
|
297
303
|
|
|
@@ -337,8 +343,8 @@ export class MigrationGenerator {
|
|
|
337
343
|
|
|
338
344
|
down.push(() => {
|
|
339
345
|
this.alterTable(model.name, () => {
|
|
340
|
-
for (const {
|
|
341
|
-
this.dropColumn(relation ? `${name}Id` : name);
|
|
346
|
+
for (const { type, name } of fields) {
|
|
347
|
+
this.dropColumn(type === 'relation' ? `${name}Id` : name);
|
|
342
348
|
}
|
|
343
349
|
});
|
|
344
350
|
});
|
|
@@ -363,7 +369,7 @@ export class MigrationGenerator {
|
|
|
363
369
|
this.column(
|
|
364
370
|
field,
|
|
365
371
|
{ alter: true },
|
|
366
|
-
summonByName(this.columns[model.name], field.relation ? `${field.name}Id` : field.name)
|
|
372
|
+
summonByName(this.columns[model.name], field.type === 'relation' ? `${field.name}Id` : field.name)
|
|
367
373
|
);
|
|
368
374
|
}
|
|
369
375
|
});
|
|
@@ -389,7 +395,7 @@ export class MigrationGenerator {
|
|
|
389
395
|
this.column(
|
|
390
396
|
field,
|
|
391
397
|
{ alter: true },
|
|
392
|
-
summonByName(this.columns[model.name], field.relation ? `${field.name}Id` : field.name)
|
|
398
|
+
summonByName(this.columns[model.name], field.type === 'relation' ? `${field.name}Id` : field.name)
|
|
393
399
|
);
|
|
394
400
|
}
|
|
395
401
|
});
|
|
@@ -405,9 +411,7 @@ export class MigrationGenerator {
|
|
|
405
411
|
writer.writeLine(`table.uuid('id').notNullable().primary();`);
|
|
406
412
|
writer.writeLine(`table.uuid('${typeToField(model.name)}Id').notNullable();`);
|
|
407
413
|
writer.write(`table.uuid('createdById')`);
|
|
408
|
-
|
|
409
|
-
writer.write('.notNullable()');
|
|
410
|
-
}
|
|
414
|
+
writer.write('.notNullable()');
|
|
411
415
|
writer.write(';').newLine();
|
|
412
416
|
writer.writeLine(`table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now(0));`);
|
|
413
417
|
if (model.deletable) {
|
|
@@ -435,8 +439,8 @@ export class MigrationGenerator {
|
|
|
435
439
|
this.writer
|
|
436
440
|
.write(`await knex('${model.name}Revision').update(`)
|
|
437
441
|
.inlineBlock(() => {
|
|
438
|
-
for (const { name,
|
|
439
|
-
const col = relation ? `${name}Id` : name;
|
|
442
|
+
for (const { name, type } of missingRevisionFields) {
|
|
443
|
+
const col = type === 'relation' ? `${name}Id` : name;
|
|
440
444
|
this.writer
|
|
441
445
|
.write(
|
|
442
446
|
`${col}: knex.raw('(select "${col}" from "${model.name}" where "${model.name}".id = "${
|
|
@@ -463,7 +467,7 @@ export class MigrationGenerator {
|
|
|
463
467
|
down.push(() => {
|
|
464
468
|
this.alterTable(revisionTable, () => {
|
|
465
469
|
for (const field of missingRevisionFields) {
|
|
466
|
-
this.dropColumn(field.relation ? `${field.name}Id` : field.name);
|
|
470
|
+
this.dropColumn(field.type === 'relation' ? `${field.name}Id` : field.name);
|
|
467
471
|
}
|
|
468
472
|
});
|
|
469
473
|
});
|
|
@@ -538,7 +542,7 @@ export class MigrationGenerator {
|
|
|
538
542
|
}
|
|
539
543
|
|
|
540
544
|
private column(
|
|
541
|
-
{ name,
|
|
545
|
+
{ name, primary, list, ...field }: ModelField,
|
|
542
546
|
{ setUnique = true, setNonNull = true, alter = false, foreign = true, setDefault = true } = {},
|
|
543
547
|
toColumn?: Column
|
|
544
548
|
) {
|
|
@@ -574,60 +578,58 @@ export class MigrationGenerator {
|
|
|
574
578
|
}
|
|
575
579
|
this.writer.write(';').newLine();
|
|
576
580
|
};
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
581
|
+
switch (field.type) {
|
|
582
|
+
case 'Boolean':
|
|
583
|
+
col(`table.boolean('${name}')`);
|
|
584
|
+
break;
|
|
585
|
+
case 'Int':
|
|
586
|
+
col(`table.integer('${name}')`);
|
|
587
|
+
break;
|
|
588
|
+
case 'Float':
|
|
589
|
+
if (field.double) {
|
|
590
|
+
col(`table.double('${name}')`);
|
|
591
|
+
} else {
|
|
592
|
+
col(`table.decimal('${name}', ${get(field, 'precision')}, ${get(field, 'scale')})`);
|
|
593
|
+
}
|
|
594
|
+
break;
|
|
595
|
+
case 'String':
|
|
596
|
+
if (field.large) {
|
|
597
|
+
col(`table.text('${name}')`);
|
|
598
|
+
} else {
|
|
599
|
+
col(`table.string('${name}', ${field.maxLength})`);
|
|
600
|
+
}
|
|
601
|
+
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
|
+
case 'relation':
|
|
611
|
+
col(`table.uuid('${name}Id')`);
|
|
612
|
+
if (foreign && !alter) {
|
|
613
|
+
this.writer.writeLine(`table.foreign('${name}Id').references('id').inTable('${field.typeName}');`);
|
|
614
|
+
}
|
|
615
|
+
break;
|
|
616
|
+
case 'enum':
|
|
617
|
+
if (list) {
|
|
618
|
+
this.writer.write(`table.specificType('${name}', '"${typeToField(field.type)}"[]');`);
|
|
619
|
+
} else {
|
|
620
|
+
this.writer
|
|
586
621
|
.write(`table.enum('${name}', null as any, `)
|
|
587
622
|
.inlineBlock(() => {
|
|
588
623
|
this.writer.writeLine(`useNative: true,`);
|
|
589
624
|
this.writer.writeLine(`existingType: true,`);
|
|
590
|
-
this.writer.writeLine(`enumName: '${typeToField(type)}',`);
|
|
625
|
+
this.writer.writeLine(`enumName: '${typeToField(field.type)}',`);
|
|
591
626
|
})
|
|
592
627
|
.write(')');
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
break;
|
|
599
|
-
case 'Int':
|
|
600
|
-
col(`table.integer('${name}')`);
|
|
601
|
-
break;
|
|
602
|
-
case 'Float':
|
|
603
|
-
if (field.double) {
|
|
604
|
-
col(`table.double('${name}')`);
|
|
605
|
-
} else {
|
|
606
|
-
col(`table.decimal('${name}', ${get(field, 'precision')}, ${get(field, 'scale')})`);
|
|
607
|
-
}
|
|
608
|
-
break;
|
|
609
|
-
case 'String':
|
|
610
|
-
if (field.large) {
|
|
611
|
-
col(`table.text('${name}')`);
|
|
612
|
-
} else {
|
|
613
|
-
col(`table.string('${name}', ${field.maxLength})`);
|
|
614
|
-
}
|
|
615
|
-
break;
|
|
616
|
-
case 'DateTime':
|
|
617
|
-
col(`table.timestamp('${name}')`);
|
|
618
|
-
break;
|
|
619
|
-
case 'ID':
|
|
620
|
-
if (field.maxLength) {
|
|
621
|
-
col(`table.string('${name}', ${get(field, 'maxLength')})`);
|
|
622
|
-
} else {
|
|
623
|
-
col(`table.uuid('${name}')`);
|
|
624
|
-
}
|
|
625
|
-
break;
|
|
626
|
-
case 'Upload':
|
|
627
|
-
break;
|
|
628
|
-
default:
|
|
629
|
-
throw new Error(`Unknown field type ${type}`);
|
|
630
|
-
}
|
|
628
|
+
}
|
|
629
|
+
col();
|
|
630
|
+
break;
|
|
631
|
+
default:
|
|
632
|
+
throw new Error(`Unknown field type ${field.type}`);
|
|
631
633
|
}
|
|
632
634
|
}
|
|
633
635
|
}
|
package/src/models.ts
CHANGED
|
@@ -1,17 +1,12 @@
|
|
|
1
|
+
import { DateTime } from 'luxon';
|
|
2
|
+
import { Field } from '.';
|
|
1
3
|
import type { Context } from './context';
|
|
2
4
|
import type { OrderBy } from './resolvers/arguments';
|
|
3
|
-
import type {
|
|
5
|
+
import type { Value } from './values';
|
|
4
6
|
|
|
5
7
|
export type RawModels = RawModel[];
|
|
6
8
|
|
|
7
|
-
export type RawModel =
|
|
8
|
-
| ScalarModel
|
|
9
|
-
| EnumModel
|
|
10
|
-
| RawEnumModel
|
|
11
|
-
| InterfaceModel
|
|
12
|
-
| ObjectModel
|
|
13
|
-
| RawObjectModel
|
|
14
|
-
| JsonObjectModel;
|
|
9
|
+
export type RawModel = ScalarModel | EnumModel | RawEnumModel | InterfaceModel | ObjectModel | RawObjectModel;
|
|
15
10
|
|
|
16
11
|
type BaseModel = {
|
|
17
12
|
name: string;
|
|
@@ -28,19 +23,11 @@ export type RawEnumModel = BaseModel & { type: 'raw-enum'; values: string[] };
|
|
|
28
23
|
export type InterfaceModel = BaseModel & { type: 'interface'; fields: ModelField[] };
|
|
29
24
|
|
|
30
25
|
export type RawObjectModel = BaseModel & {
|
|
31
|
-
type: 'raw
|
|
32
|
-
fields:
|
|
33
|
-
rawFilters?: { name: string; type: string; list?: boolean; nonNull?: boolean }[];
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export type JsonObjectModel = BaseModel & {
|
|
37
|
-
type: 'json-object';
|
|
38
|
-
json: true;
|
|
39
|
-
fields: Pick<ModelField, 'type' | 'name' | 'nonNull'>[];
|
|
26
|
+
type: 'raw';
|
|
27
|
+
fields: RawObjectField[];
|
|
40
28
|
};
|
|
41
29
|
|
|
42
|
-
|
|
43
|
-
export type Entity = Record<string, any>;
|
|
30
|
+
export type Entity = Record<string, unknown> & { createdAt?: DateTime; deletedAt?: DateTime };
|
|
44
31
|
|
|
45
32
|
export type Action = 'create' | 'update' | 'delete' | 'restore';
|
|
46
33
|
|
|
@@ -55,13 +42,13 @@ export type MutationHook = (
|
|
|
55
42
|
export type ObjectModel = BaseModel & {
|
|
56
43
|
type: 'object';
|
|
57
44
|
interfaces?: string[];
|
|
58
|
-
// createdAt, createdBy, updatedAt, updatedBy can be null
|
|
59
|
-
nonStrict?: boolean;
|
|
60
45
|
queriable?: boolean;
|
|
61
46
|
listQueriable?: boolean;
|
|
62
|
-
creatable?: boolean;
|
|
63
|
-
updatable?: boolean;
|
|
64
|
-
deletable?:
|
|
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> };
|
|
65
52
|
displayField?: string;
|
|
66
53
|
defaultOrderBy?: OrderBy;
|
|
67
54
|
fields: ModelField[];
|
|
@@ -71,136 +58,109 @@ export type ObjectModel = BaseModel & {
|
|
|
71
58
|
oldName?: string;
|
|
72
59
|
};
|
|
73
60
|
|
|
74
|
-
|
|
75
|
-
name: string;
|
|
76
|
-
type: string;
|
|
77
|
-
nonNull?: boolean;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
export const isObjectModel = (model: RawModel): model is ObjectModel => model.type === 'object';
|
|
81
|
-
|
|
82
|
-
export const isEnumModel = (model: RawModel): model is EnumModel => model.type === 'enum';
|
|
83
|
-
|
|
84
|
-
export const isRawEnumModel = (model: RawModel): model is RawEnumModel => model.type === 'raw-enum';
|
|
85
|
-
|
|
86
|
-
export const isScalarModel = (model: RawModel): model is ScalarModel => model.type === 'scalar';
|
|
87
|
-
|
|
88
|
-
export const isRawObjectModel = (model: RawModel): model is RawObjectModel => model.type === 'raw-object';
|
|
89
|
-
|
|
90
|
-
export const isJsonObjectModel = (model: RawModel): model is RawObjectModel => model.type === 'json-object';
|
|
91
|
-
|
|
92
|
-
export const isEnumList = (models: RawModels, field: ModelField) =>
|
|
93
|
-
field?.list === true && models.find(({ name }) => name === field.type)?.type === 'enum';
|
|
94
|
-
|
|
95
|
-
export const and =
|
|
96
|
-
(...predicates: ((field: ModelField) => boolean)[]) =>
|
|
97
|
-
(field: ModelField) =>
|
|
98
|
-
predicates.every((predicate) => predicate(field));
|
|
99
|
-
|
|
100
|
-
export const not = (predicate: (field: ModelField) => boolean) => (field: ModelField) => !predicate(field);
|
|
101
|
-
|
|
102
|
-
export const isRelation = ({ relation }: ModelField) => !!relation;
|
|
103
|
-
|
|
104
|
-
export type VisibleRelationsByRole = Record<string, Record<string, string[]>>;
|
|
105
|
-
|
|
106
|
-
export const isVisibleRelation = (visibleRelationsByRole: VisibleRelationsByRole, modelName: string, role: string) => {
|
|
107
|
-
const whitelist = visibleRelationsByRole[role]?.[modelName];
|
|
108
|
-
return ({ name }: Field) => (whitelist ? whitelist.includes(name) : true);
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
export const isToOneRelation = ({ toOne }: ModelField) => !!toOne;
|
|
112
|
-
|
|
113
|
-
export const isQueriableField = ({ queriable }: ModelField) => queriable !== false;
|
|
114
|
-
|
|
115
|
-
export const isRaw = ({ raw }: ModelField) => !!raw;
|
|
116
|
-
|
|
117
|
-
export const isVisible = ({ hidden }: ModelField) => hidden !== true;
|
|
118
|
-
|
|
119
|
-
export const isSimpleField = and(not(isRelation), not(isRaw));
|
|
120
|
-
|
|
121
|
-
export const isUpdatable = ({ updatable }: ModelField) => !!updatable;
|
|
122
|
-
|
|
123
|
-
export const isCreatable = ({ creatable }: ModelField) => !!creatable;
|
|
124
|
-
|
|
125
|
-
export const isQueriableBy = (role: string) => (field: ModelField) =>
|
|
126
|
-
isQueriableField(field) && (!field.queriableBy || field.queriableBy.includes(role));
|
|
127
|
-
|
|
128
|
-
export const isUpdatableBy = (role: string) => (field: ModelField) =>
|
|
129
|
-
isUpdatable(field) && (!field.updatableBy || field.updatableBy.includes(role));
|
|
130
|
-
|
|
131
|
-
export const isCreatableBy = (role: string) => (field: ModelField) =>
|
|
132
|
-
isCreatable(field) && (!field.creatableBy || field.creatableBy.includes(role));
|
|
133
|
-
|
|
134
|
-
export const actionableRelations = (model: Model, action: 'create' | 'update' | 'filter') =>
|
|
135
|
-
model.fields.filter(
|
|
136
|
-
({ relation, ...field }) =>
|
|
137
|
-
relation &&
|
|
138
|
-
field[`${action === 'filter' ? action : action.slice(0, -1)}able` as 'filterable' | 'creatable' | 'updatable']
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
export type Field = {
|
|
142
|
-
name: string;
|
|
143
|
-
type: string;
|
|
144
|
-
default?: Value;
|
|
145
|
-
list?: boolean;
|
|
146
|
-
nonNull?: boolean;
|
|
147
|
-
args?: Field[];
|
|
148
|
-
directives?: Directive[];
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
export type ModelField = Field & {
|
|
152
|
-
primary?: boolean;
|
|
153
|
-
unique?: boolean;
|
|
154
|
-
filterable?: boolean;
|
|
155
|
-
defaultFilter?: Value;
|
|
156
|
-
searchable?: boolean;
|
|
157
|
-
possibleValues?: Value[];
|
|
158
|
-
orderable?: boolean;
|
|
159
|
-
comparable?: boolean;
|
|
160
|
-
relation?: boolean;
|
|
161
|
-
onDelete?: 'cascade' | 'set-null';
|
|
162
|
-
reverse?: string;
|
|
163
|
-
toOne?: boolean;
|
|
164
|
-
foreignKey?: string;
|
|
165
|
-
queriable?: false;
|
|
166
|
-
queriableBy?: string[];
|
|
167
|
-
creatable?: boolean;
|
|
168
|
-
creatableBy?: string[];
|
|
169
|
-
updatable?: boolean;
|
|
170
|
-
updatableBy?: string[];
|
|
171
|
-
generated?: boolean;
|
|
172
|
-
raw?: boolean;
|
|
173
|
-
json?: boolean;
|
|
174
|
-
dateTimeType?: 'year' | 'date' | 'datetime' | 'year_and_month';
|
|
175
|
-
stringType?: 'email' | 'url' | 'phone';
|
|
176
|
-
floatType?: 'currency' | 'percentage';
|
|
61
|
+
type BaseNumberType = {
|
|
177
62
|
unit?: 'million';
|
|
178
|
-
intType?: 'currency';
|
|
179
63
|
min?: number;
|
|
180
64
|
max?: number;
|
|
181
|
-
// The tooltip is "hidden" behind an icon in the admin forms
|
|
182
|
-
tooltip?: string;
|
|
183
|
-
// The description is always visible below the inputs in the admin forms
|
|
184
|
-
description?: string;
|
|
185
|
-
large?: true;
|
|
186
|
-
maxLength?: number;
|
|
187
|
-
double?: boolean;
|
|
188
|
-
precision?: number;
|
|
189
|
-
scale?: number;
|
|
190
|
-
defaultValue?: string | number | ReadonlyArray<string> | undefined;
|
|
191
|
-
endOfDay?: boolean;
|
|
192
|
-
obfuscate?: true;
|
|
193
|
-
// If true the field must be filled within forms but can be null in the database
|
|
194
|
-
required?: boolean;
|
|
195
|
-
indent?: boolean;
|
|
196
|
-
// If true the field is hidden in the admin interface
|
|
197
|
-
hidden?: boolean;
|
|
198
|
-
|
|
199
|
-
// temporary fields for the generation of migrations
|
|
200
|
-
deleted?: true;
|
|
201
|
-
oldName?: string;
|
|
202
65
|
};
|
|
203
66
|
|
|
67
|
+
type BaseField = Omit<Field, 'type'>;
|
|
68
|
+
|
|
69
|
+
type PrimitiveField =
|
|
70
|
+
| { type: 'ID' }
|
|
71
|
+
| { type: 'Boolean' }
|
|
72
|
+
| {
|
|
73
|
+
type: 'String';
|
|
74
|
+
stringType?: 'email' | 'url' | 'phone';
|
|
75
|
+
large?: true;
|
|
76
|
+
maxLength?: number;
|
|
77
|
+
}
|
|
78
|
+
| {
|
|
79
|
+
type: 'DateTime';
|
|
80
|
+
dateTimeType?: 'year' | 'date' | 'datetime' | 'year_and_month';
|
|
81
|
+
endOfDay?: boolean;
|
|
82
|
+
}
|
|
83
|
+
| ({
|
|
84
|
+
type: 'Int';
|
|
85
|
+
intType?: 'currency';
|
|
86
|
+
} & BaseNumberType)
|
|
87
|
+
| ({
|
|
88
|
+
type: 'Float';
|
|
89
|
+
floatType?: 'currency' | 'percentage';
|
|
90
|
+
double?: boolean;
|
|
91
|
+
precision?: number;
|
|
92
|
+
scale?: number;
|
|
93
|
+
} & BaseNumberType)
|
|
94
|
+
| { type: 'Upload' };
|
|
95
|
+
|
|
96
|
+
type RawObjectField = BaseField & PrimitiveField;
|
|
97
|
+
|
|
98
|
+
export type ModelField = BaseField &
|
|
99
|
+
(
|
|
100
|
+
| PrimitiveField
|
|
101
|
+
| { type: 'json'; typeName: string }
|
|
102
|
+
| { type: 'enum'; typeName: string; possibleValues?: Value[] }
|
|
103
|
+
| { type: 'raw'; typeName: string }
|
|
104
|
+
| {
|
|
105
|
+
type: 'relation';
|
|
106
|
+
typeName: string;
|
|
107
|
+
toOne?: boolean;
|
|
108
|
+
reverse?: string;
|
|
109
|
+
foreignKey?: string;
|
|
110
|
+
onDelete?: 'cascade' | 'set-null';
|
|
111
|
+
}
|
|
112
|
+
) & {
|
|
113
|
+
primary?: boolean;
|
|
114
|
+
unique?: boolean;
|
|
115
|
+
filterable?:
|
|
116
|
+
| boolean
|
|
117
|
+
| {
|
|
118
|
+
default?: Value;
|
|
119
|
+
};
|
|
120
|
+
searchable?: boolean;
|
|
121
|
+
orderable?: boolean;
|
|
122
|
+
comparable?: boolean;
|
|
123
|
+
queriable?:
|
|
124
|
+
| boolean
|
|
125
|
+
| {
|
|
126
|
+
roles?: string[];
|
|
127
|
+
};
|
|
128
|
+
creatable?:
|
|
129
|
+
| boolean
|
|
130
|
+
| {
|
|
131
|
+
roles?: string[];
|
|
132
|
+
};
|
|
133
|
+
updatable?:
|
|
134
|
+
| boolean
|
|
135
|
+
| {
|
|
136
|
+
roles?: string[];
|
|
137
|
+
};
|
|
138
|
+
generated?: boolean;
|
|
139
|
+
// The tooltip is "hidden" behind an icon in the admin forms
|
|
140
|
+
tooltip?: string;
|
|
141
|
+
defaultValue?: string | number | ReadonlyArray<string> | undefined;
|
|
142
|
+
// If true the field must be filled within forms but can be null in the database
|
|
143
|
+
required?: boolean;
|
|
144
|
+
indent?: boolean;
|
|
145
|
+
// If true the field is hidden in the admin interface
|
|
146
|
+
hidden?: boolean;
|
|
147
|
+
|
|
148
|
+
// temporary fields for the generation of migrations
|
|
149
|
+
deleted?: true;
|
|
150
|
+
oldName?: string;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export type IDField = Extract<ModelField, { type: 'ID' }>;
|
|
154
|
+
export type BooleanField = Extract<ModelField, { type: 'Boolean' }>;
|
|
155
|
+
export type StringField = Extract<ModelField, { type: 'String' }>;
|
|
156
|
+
export type DateTimeField = Extract<ModelField, { type: 'DateTime' }>;
|
|
157
|
+
export type IntField = Extract<ModelField, { type: 'Int' }>;
|
|
158
|
+
export type FloatField = Extract<ModelField, { type: 'Float' }>;
|
|
159
|
+
export type JsonField = Extract<ModelField, { type: 'json' }>;
|
|
160
|
+
export type EnumField = Extract<ModelField, { type: 'enum' }>;
|
|
161
|
+
export type RawField = Extract<ModelField, { type: 'raw' }>;
|
|
162
|
+
export type RelationField = Extract<ModelField, { type: 'relation' }>;
|
|
163
|
+
|
|
204
164
|
export type Models = Model[];
|
|
205
165
|
|
|
206
166
|
export type Model = ObjectModel & {
|
|
@@ -212,17 +172,13 @@ export type Model = ObjectModel & {
|
|
|
212
172
|
};
|
|
213
173
|
|
|
214
174
|
export type Relation = {
|
|
215
|
-
field:
|
|
175
|
+
field: RelationField;
|
|
216
176
|
model: Model;
|
|
217
177
|
reverseRelation: ReverseRelation;
|
|
218
178
|
};
|
|
219
179
|
|
|
220
|
-
export type ReverseRelation = {
|
|
221
|
-
name: string;
|
|
222
|
-
type: string;
|
|
223
|
-
foreignKey: string;
|
|
224
|
-
toOne: boolean;
|
|
180
|
+
export type ReverseRelation = RelationField & {
|
|
225
181
|
model: Model;
|
|
226
|
-
field:
|
|
182
|
+
field: RelationField;
|
|
227
183
|
fieldModel: Model;
|
|
228
184
|
};
|
package/src/permissions/check.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { FullContext } from '../context';
|
|
|
3
3
|
import { NotFoundError, PermissionError } from '../errors';
|
|
4
4
|
import { Model } from '../models';
|
|
5
5
|
import { AliasGenerator, hash, ors } from '../resolvers/utils';
|
|
6
|
-
import { get, getModelPlural, summonByName } from '../utils';
|
|
6
|
+
import { get, getModelPlural, isRelation, summonByName } from '../utils';
|
|
7
7
|
import { BasicValue } from '../values';
|
|
8
8
|
import { PermissionAction, PermissionLink, PermissionStack } from './generate';
|
|
9
9
|
|
|
@@ -139,7 +139,7 @@ export const checkCanWrite = async (
|
|
|
139
139
|
let linked = false;
|
|
140
140
|
|
|
141
141
|
for (const field of model.fields
|
|
142
|
-
.filter(
|
|
142
|
+
.filter(isRelation)
|
|
143
143
|
.filter((field) => field.generated || (action === 'CREATE' ? field.creatable : field.updatable))) {
|
|
144
144
|
const foreignKey = field.foreignKey || `${field.name}Id`;
|
|
145
145
|
const foreignId = data[foreignKey] as string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Models } from '../models';
|
|
2
|
-
import { summonByName } from '../utils';
|
|
2
|
+
import { isRelation, summonByName } from '../utils';
|
|
3
3
|
|
|
4
4
|
export type PermissionAction = 'READ' | 'CREATE' | 'UPDATE' | 'DELETE' | 'RESTORE' | 'LINK';
|
|
5
5
|
|
|
@@ -114,7 +114,7 @@ const addPermissions = (models: Models, permissions: RolePermissions, links: Per
|
|
|
114
114
|
|
|
115
115
|
if (block.RELATIONS) {
|
|
116
116
|
for (const [relation, subBlock] of Object.entries(block.RELATIONS)) {
|
|
117
|
-
const field = model.fields.find((field) => field.
|
|
117
|
+
const field = model.fields.filter(isRelation).find((field) => field.name === relation);
|
|
118
118
|
let link: PermissionLink;
|
|
119
119
|
if (field) {
|
|
120
120
|
link = {
|
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.relation) {
|
|
88
|
+
if (field.type === '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 = {
|