@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.
Files changed (61) hide show
  1. package/CHANGELOG.md +1 -6
  2. package/dist/cjs/index.cjs +149 -140
  3. package/dist/esm/client/index.d.ts +1 -0
  4. package/dist/esm/client/index.js +1 -0
  5. package/dist/esm/client/index.js.map +1 -1
  6. package/dist/esm/client/models.d.ts +1 -0
  7. package/dist/esm/client/models.js +2 -0
  8. package/dist/esm/client/models.js.map +1 -0
  9. package/dist/esm/client/queries.d.ts +2 -2
  10. package/dist/esm/client/queries.js +3 -3
  11. package/dist/esm/client/queries.js.map +1 -1
  12. package/dist/esm/context.d.ts +5 -1
  13. package/dist/esm/db/generate.js +17 -19
  14. package/dist/esm/db/generate.js.map +1 -1
  15. package/dist/esm/migrations/generate.js +69 -57
  16. package/dist/esm/migrations/generate.js.map +1 -1
  17. package/dist/esm/models/index.d.ts +1 -0
  18. package/dist/esm/models/index.js +1 -0
  19. package/dist/esm/models/index.js.map +1 -1
  20. package/dist/esm/models/models.d.ts +65 -59
  21. package/dist/esm/models/mutation-hook.d.ts +17 -0
  22. package/dist/esm/models/mutation-hook.js +2 -0
  23. package/dist/esm/models/mutation-hook.js.map +1 -0
  24. package/dist/esm/models/utils.d.ts +398 -22
  25. package/dist/esm/models/utils.js +22 -20
  26. package/dist/esm/models/utils.js.map +1 -1
  27. package/dist/esm/permissions/check.js +2 -2
  28. package/dist/esm/permissions/check.js.map +1 -1
  29. package/dist/esm/resolvers/filters.js +1 -1
  30. package/dist/esm/resolvers/mutations.js +4 -4
  31. package/dist/esm/resolvers/mutations.js.map +1 -1
  32. package/dist/esm/resolvers/node.js +2 -2
  33. package/dist/esm/resolvers/node.js.map +1 -1
  34. package/dist/esm/resolvers/resolver.js +1 -1
  35. package/dist/esm/schema/generate.js +27 -37
  36. package/dist/esm/schema/generate.js.map +1 -1
  37. package/dist/esm/schema/utils.d.ts +1 -1
  38. package/dist/esm/schema/utils.js +2 -2
  39. package/dist/esm/schema/utils.js.map +1 -1
  40. package/package.json +5 -5
  41. package/src/client/index.ts +1 -0
  42. package/src/client/models.ts +1 -0
  43. package/src/client/queries.ts +5 -5
  44. package/src/context.ts +4 -1
  45. package/src/db/generate.ts +25 -27
  46. package/src/migrations/generate.ts +69 -57
  47. package/src/models/index.ts +1 -0
  48. package/src/models/models.ts +88 -97
  49. package/src/models/mutation-hook.ts +17 -0
  50. package/src/models/utils.ts +27 -21
  51. package/src/permissions/check.ts +2 -2
  52. package/src/resolvers/filters.ts +1 -1
  53. package/src/resolvers/mutations.ts +9 -8
  54. package/src/resolvers/node.ts +2 -2
  55. package/src/resolvers/resolver.ts +1 -1
  56. package/src/schema/generate.ts +31 -48
  57. package/src/schema/utils.ts +3 -3
  58. package/tests/unit/__snapshots__/generate.spec.ts.snap +2 -2
  59. package/tests/unit/resolve.spec.ts +1 -0
  60. package/tests/utils/models.ts +12 -12
  61. 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.type !== 'raw' &&
134
+ field.kind !== 'raw' &&
135
135
  !this.columns[model.name].some(
136
- (col) => col.name === (field.type === 'relation' ? field.foreignKey || `${name}Id` : name)
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, type, nonNull }) => {
145
- const col = this.columns[model.name].find((col) => col.name === (type === 'relation' ? `${name}Id` : 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, type } of model.fields.filter(({ updatable }) => updatable)) {
181
- const col = type === 'relation' ? `${name}Id` : name;
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.type !== 'raw' &&
201
+ field.kind !== 'raw' &&
202
202
  updatable &&
203
203
  !this.columns[revisionTable].some(
204
- (col) => col.name === (field.type === 'relation' ? field.foreignKey || `${name}Id` : name)
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.type !== 'raw' &&
213
+ field.kind !== 'raw' &&
214
214
  !updatable &&
215
- !(field.type === 'relation' && field.foreignKey === 'id') &&
215
+ !(field.kind === 'relation' && field.foreignKey === 'id') &&
216
216
  this.columns[revisionTable].some(
217
- (col) => col.name === (field.type === 'relation' ? field.foreignKey || `${name}Id` : name)
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.type === 'relation' ? `${field.oldName}Id` : get(field, 'oldName'),
280
- field.type === 'relation' ? `${field.name}Id` : field.name
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.type === 'relation' ? `${field.name}Id` : field.name,
291
- field.type === 'relation' ? `${field.oldName}Id` : get(field, 'oldName')
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.type === 'relation' ? `${field.oldName!}Id` : field.oldName!).name =
300
- field.type === 'relation' ? `${field.name}Id` : field.name;
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.default !== undefined }));
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.default === undefined) {
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 { type, name } of fields) {
347
- this.dropColumn(type === 'relation' ? `${name}Id` : name);
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.type === 'relation' ? `${field.name}Id` : field.name)
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.type === 'relation' ? `${field.name}Id` : field.name)
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.type === 'relation' ? `${field.name}Id` : field.name);
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.default !== undefined) {
569
- this.writer.write(`.defaultTo(${this.value(field.default)})`);
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
- 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})`);
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.typeName}');`);
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
- default:
632
- throw new Error(`Unknown field type ${field.type}`);
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
  }
@@ -1,4 +1,5 @@
1
1
  // created from 'create-ts-index'
2
2
 
3
3
  export * from './models';
4
+ export * from './mutation-hook';
4
5
  export * from './utils';
@@ -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 = ScalarModel | EnumModel | RawEnumModel | InterfaceModel | ObjectModel | RawObjectModel;
10
-
11
- type BaseModel = {
7
+ export type RawModel = {
12
8
  name: string;
13
9
  plural?: string;
14
10
  description?: string;
15
- };
16
-
17
- export type ScalarModel = BaseModel & { type: 'scalar' };
18
-
19
- export type EnumModel = BaseModel & { type: 'enum'; values: string[]; deleted?: true };
20
-
21
- export type RawEnumModel = BaseModel & { type: 'raw-enum'; values: string[] };
22
-
23
- export type InterfaceModel = BaseModel & { type: 'interface'; fields: ModelField[] };
24
-
25
- export type RawObjectModel = BaseModel & {
26
- type: 'raw';
27
- fields: RawObjectField[];
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 Entity = Record<string, unknown> & { createdAt?: DateTime; deletedAt?: DateTime };
31
-
32
- export type Action = 'create' | 'update' | 'delete' | 'restore';
33
-
34
- export type MutationHook = (
35
- model: Model,
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 BaseField = Omit<Field, 'type'>;
53
+ type FieldBase = Omit<Field, 'type'>;
68
54
 
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 &
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
- | PrimitiveField
101
- | { type: 'json'; typeName: string }
102
- | { type: 'enum'; typeName: string; possibleValues?: Value[] }
103
- | { type: 'raw'; typeName: string }
90
+ | FieldBase2
91
+ | { kind: 'json'; type: string }
104
92
  | {
105
- type: 'relation';
106
- typeName: string;
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 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' }>;
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>;
@@ -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.type === 'object';
48
+ export const isObjectModel = (model: RawModel): model is ObjectModel => model.kind === 'object';
47
49
 
48
- export const isEnumModel = (model: RawModel): model is EnumModel => model.type === 'enum';
50
+ export const isEnumModel = (model: RawModel): model is EnumModel => model.kind === 'enum';
49
51
 
50
- export const isRawEnumModel = (model: RawModel): model is RawEnumModel => model.type === 'raw-enum';
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.type === 'scalar';
54
+ export const isScalarModel = (model: RawModel): model is ScalarModel => model.kind === 'scalar';
53
55
 
54
- export const isRawObjectModel = (model: RawModel): model is RawObjectModel => model.type === 'raw';
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.type)?.type === 'enum';
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 isRelation = (field: ModelField): field is RelationField => field.type === 'relation';
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.type === 'raw';
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
- type: 'relation',
126
- typeName: 'User',
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
- type: 'relation',
147
- typeName: 'User',
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
- default: false,
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
- type: 'relation',
176
- typeName: 'User',
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.type === 'relation' && {
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.type !== 'relation') {
207
+ if (field.kind !== 'relation') {
202
208
  continue;
203
209
  }
204
210
 
205
- const fieldModel = summonByName(models, field.typeName);
211
+ const fieldModel = summonByName(models, field.type);
206
212
 
207
213
  const reverseRelation: ReverseRelation = {
208
- type: 'relation',
214
+ kind: 'relation',
209
215
  name: field.reverse || (field.toOne ? typeToField(model.name) : getModelPluralField(model)),
210
216
  foreignKey: get(field, 'foreignKey'),
211
- typeName: model.name,
217
+ type: model.name,
212
218
  toOne: !!field.toOne,
213
219
  fieldModel,
214
220
  field,
@@ -147,8 +147,8 @@ export const checkCanWrite = async (
147
147
  continue;
148
148
  }
149
149
 
150
- const fieldPermissions = field[action === 'CREATE' ? 'creatableBy' : 'updatableBy'];
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
 
@@ -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.type === 'relation') {
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 = {