@smartive/graphql-magic 9.1.2 → 10.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/.eslintrc +2 -10
  2. package/.github/workflows/release.yml +1 -1
  3. package/.gqmrc.json +6 -0
  4. package/CHANGELOG.md +2 -2
  5. package/README.md +1 -1
  6. package/dist/bin/gqm.cjs +684 -330
  7. package/dist/cjs/index.cjs +998 -554
  8. package/dist/esm/api/execute.js +1 -1
  9. package/dist/esm/api/execute.js.map +1 -1
  10. package/dist/esm/client/mutations.d.ts +2 -2
  11. package/dist/esm/client/mutations.js +5 -4
  12. package/dist/esm/client/mutations.js.map +1 -1
  13. package/dist/esm/client/queries.d.ts +12 -17
  14. package/dist/esm/client/queries.js +30 -50
  15. package/dist/esm/client/queries.js.map +1 -1
  16. package/dist/esm/context.d.ts +1 -2
  17. package/dist/esm/db/generate.d.ts +3 -3
  18. package/dist/esm/db/generate.js +31 -29
  19. package/dist/esm/db/generate.js.map +1 -1
  20. package/dist/esm/migrations/generate.d.ts +3 -4
  21. package/dist/esm/migrations/generate.js +114 -107
  22. package/dist/esm/migrations/generate.js.map +1 -1
  23. package/dist/esm/models/index.d.ts +1 -0
  24. package/dist/esm/models/index.js +1 -0
  25. package/dist/esm/models/index.js.map +1 -1
  26. package/dist/esm/models/model-definitions.d.ts +189 -0
  27. package/dist/esm/models/model-definitions.js +2 -0
  28. package/dist/esm/models/model-definitions.js.map +1 -0
  29. package/dist/esm/models/models.d.ts +128 -174
  30. package/dist/esm/models/models.js +411 -1
  31. package/dist/esm/models/models.js.map +1 -1
  32. package/dist/esm/models/mutation-hook.d.ts +2 -2
  33. package/dist/esm/models/utils.d.ts +35 -497
  34. package/dist/esm/models/utils.js +21 -144
  35. package/dist/esm/models/utils.js.map +1 -1
  36. package/dist/esm/permissions/check.d.ts +3 -3
  37. package/dist/esm/permissions/check.js +14 -7
  38. package/dist/esm/permissions/check.js.map +1 -1
  39. package/dist/esm/permissions/generate.js +6 -6
  40. package/dist/esm/permissions/generate.js.map +1 -1
  41. package/dist/esm/resolvers/filters.d.ts +8 -0
  42. package/dist/esm/resolvers/filters.js +28 -25
  43. package/dist/esm/resolvers/filters.js.map +1 -1
  44. package/dist/esm/resolvers/index.d.ts +1 -0
  45. package/dist/esm/resolvers/index.js +1 -0
  46. package/dist/esm/resolvers/index.js.map +1 -1
  47. package/dist/esm/resolvers/mutations.js +85 -21
  48. package/dist/esm/resolvers/mutations.js.map +1 -1
  49. package/dist/esm/resolvers/node.d.ts +13 -15
  50. package/dist/esm/resolvers/node.js +41 -36
  51. package/dist/esm/resolvers/node.js.map +1 -1
  52. package/dist/esm/resolvers/resolver.js +19 -49
  53. package/dist/esm/resolvers/resolver.js.map +1 -1
  54. package/dist/esm/resolvers/resolvers.d.ts +1 -8
  55. package/dist/esm/resolvers/resolvers.js +15 -7
  56. package/dist/esm/resolvers/resolvers.js.map +1 -1
  57. package/dist/esm/resolvers/selects.d.ts +3 -0
  58. package/dist/esm/resolvers/selects.js +50 -0
  59. package/dist/esm/resolvers/selects.js.map +1 -0
  60. package/dist/esm/resolvers/utils.d.ts +12 -4
  61. package/dist/esm/resolvers/utils.js +30 -22
  62. package/dist/esm/resolvers/utils.js.map +1 -1
  63. package/dist/esm/schema/generate.d.ts +4 -4
  64. package/dist/esm/schema/generate.js +122 -131
  65. package/dist/esm/schema/generate.js.map +1 -1
  66. package/dist/esm/schema/utils.d.ts +1 -1
  67. package/dist/esm/schema/utils.js +2 -1
  68. package/dist/esm/schema/utils.js.map +1 -1
  69. package/knexfile.ts +31 -0
  70. package/migrations/20230912185644_setup.ts +127 -0
  71. package/package.json +16 -14
  72. package/src/api/execute.ts +1 -1
  73. package/src/bin/gqm/gqm.ts +25 -23
  74. package/src/bin/gqm/parse-models.ts +5 -5
  75. package/src/bin/gqm/settings.ts +13 -4
  76. package/src/bin/gqm/static-eval.ts +5 -0
  77. package/src/bin/gqm/templates.ts +23 -3
  78. package/src/client/mutations.ts +11 -5
  79. package/src/client/queries.ts +43 -80
  80. package/src/context.ts +1 -2
  81. package/src/db/generate.ts +41 -41
  82. package/src/migrations/generate.ts +165 -146
  83. package/src/models/index.ts +1 -0
  84. package/src/models/model-definitions.ts +168 -0
  85. package/src/models/models.ts +510 -166
  86. package/src/models/mutation-hook.ts +2 -2
  87. package/src/models/utils.ts +53 -187
  88. package/src/permissions/check.ts +19 -11
  89. package/src/permissions/generate.ts +6 -6
  90. package/src/resolvers/filters.ts +44 -28
  91. package/src/resolvers/index.ts +1 -0
  92. package/src/resolvers/mutations.ts +98 -36
  93. package/src/resolvers/node.ts +79 -51
  94. package/src/resolvers/resolver.ts +20 -74
  95. package/src/resolvers/resolvers.ts +18 -7
  96. package/src/resolvers/selects.ts +77 -0
  97. package/src/resolvers/utils.ts +41 -25
  98. package/src/schema/generate.ts +106 -127
  99. package/src/schema/utils.ts +2 -1
  100. package/tests/api/__snapshots__/inheritance.spec.ts.snap +83 -0
  101. package/tests/api/inheritance.spec.ts +130 -0
  102. package/tests/generated/api/index.ts +1174 -0
  103. package/tests/generated/client/index.ts +1163 -0
  104. package/tests/generated/client/mutations.ts +109 -0
  105. package/tests/generated/db/index.ts +291 -0
  106. package/tests/generated/db/knex.ts +14 -0
  107. package/tests/generated/models.json +675 -0
  108. package/tests/generated/schema.graphql +325 -0
  109. package/tests/unit/__snapshots__/resolve.spec.ts.snap +23 -0
  110. package/tests/unit/queries.spec.ts +5 -5
  111. package/tests/unit/resolve.spec.ts +8 -8
  112. package/tests/utils/database/knex.ts +5 -13
  113. package/tests/utils/database/seed.ts +57 -18
  114. package/tests/utils/models.ts +62 -7
  115. package/tests/utils/server.ts +5 -5
  116. package/tsconfig.eslint.json +1 -0
  117. package/tests/unit/__snapshots__/generate.spec.ts.snap +0 -128
  118. package/tests/unit/generate.spec.ts +0 -8
  119. package/tests/utils/database/schema.ts +0 -64
  120. package/tests/utils/generate-migration.ts +0 -24
@@ -1,5 +1,6 @@
1
1
  import CodeBlockWriter from 'code-block-writer';
2
- import { ModelField, RawModels, get, getModels, isCustomField, isEnumModel, not } from '..';
2
+ import { EntityField, get, getColumnName, isCustomField, isInTable, isRootModel, not } from '..';
3
+ import { Models } from '../models/models';
3
4
 
4
5
  const PRIMITIVE_TYPES = {
5
6
  ID: 'string',
@@ -13,7 +14,7 @@ const PRIMITIVE_TYPES = {
13
14
 
14
15
  const OPTIONAL_SEED_FIELDS = ['createdAt', 'createdById', 'updatedAt', 'updatedById', 'deletedAt', 'deletedById'];
15
16
 
16
- export const generateDBModels = (rawModels: RawModels) => {
17
+ export const generateDBModels = (models: Models) => {
17
18
  const writer: CodeBlockWriter = new CodeBlockWriter['default']({
18
19
  useSingleQuote: true,
19
20
  indentNumberOfSpaces: 2,
@@ -21,15 +22,13 @@ export const generateDBModels = (rawModels: RawModels) => {
21
22
 
22
23
  writer.write(`import { DateTime } from 'luxon';`).blankLine();
23
24
 
24
- for (const enm of rawModels.filter(isEnumModel)) {
25
+ for (const enm of models.enums) {
25
26
  writer.write(`export type ${enm.name} = ${enm.values.map((v) => `'${v}'`).join(' | ')};`).blankLine();
26
27
  }
27
28
 
28
- const models = getModels(rawModels);
29
-
30
- for (const model of models) {
29
+ for (const model of models.entities) {
31
30
  // TODO: deprecate allowing to define foreignKey
32
- const fields = model.fields.some((field) => field.kind === 'relation' && field.foreignKey === 'id')
31
+ const fields = model.relations.some((relation) => relation.field.foreignKey === 'id')
33
32
  ? model.fields.filter((field) => field.name !== 'id')
34
33
  : model.fields;
35
34
 
@@ -37,7 +36,7 @@ export const generateDBModels = (rawModels: RawModels) => {
37
36
  .write(`export type ${model.name} = `)
38
37
  .inlineBlock(() => {
39
38
  for (const field of fields.filter(not(isCustomField))) {
40
- writer.write(`'${getFieldName(field)}': ${getFieldType(field)}${field.nonNull ? '' : ' | null'},`).newLine();
39
+ writer.write(`'${getColumnName(field)}': ${getFieldType(field)}${field.nonNull ? '' : ' | null'};`).newLine();
41
40
  }
42
41
  })
43
42
  .blankLine();
@@ -45,12 +44,12 @@ export const generateDBModels = (rawModels: RawModels) => {
45
44
  writer
46
45
  .write(`export type ${model.name}Initializer = `)
47
46
  .inlineBlock(() => {
48
- for (const field of fields.filter(not(isCustomField))) {
47
+ for (const field of fields.filter(not(isCustomField)).filter(isInTable)) {
49
48
  writer
50
49
  .write(
51
- `'${getFieldName(field)}'${field.nonNull && field.defaultValue === undefined ? '' : '?'}: ${getFieldType(
50
+ `'${getColumnName(field)}'${field.nonNull && field.defaultValue === undefined ? '' : '?'}: ${getFieldType(
52
51
  field
53
- )}${field.list ? ' | string' : ''}${field.nonNull ? '' : ' | null'},`
52
+ )}${field.list ? ' | string' : ''}${field.nonNull ? '' : ' | null'};`
54
53
  )
55
54
  .newLine();
56
55
  }
@@ -60,49 +59,52 @@ export const generateDBModels = (rawModels: RawModels) => {
60
59
  writer
61
60
  .write(`export type ${model.name}Mutator = `)
62
61
  .inlineBlock(() => {
63
- for (const field of fields.filter(not(isCustomField))) {
62
+ for (const field of fields.filter(not(isCustomField)).filter(isInTable)) {
64
63
  writer
65
64
  .write(
66
- `'${getFieldName(field)}'?: ${getFieldType(field)}${field.list ? ' | string' : ''}${
65
+ `'${getColumnName(field)}'?: ${getFieldType(field)}${field.list ? ' | string' : ''}${
67
66
  field.nonNull ? '' : ' | null'
68
- },`
67
+ };`
69
68
  )
70
69
  .newLine();
71
70
  }
72
71
  })
73
72
  .blankLine();
74
73
 
75
- writer
76
- .write(`export type ${model.name}Seed = `)
77
- .inlineBlock(() => {
78
- for (const field of fields.filter(not(isCustomField))) {
79
- const fieldName = getFieldName(field);
80
- writer
81
- .write(
82
- `'${getFieldName(field)}'${
83
- field.nonNull && field.defaultValue === undefined && !OPTIONAL_SEED_FIELDS.includes(fieldName) ? '' : '?'
84
- }: ${field.kind === 'enum' ? (field.list ? 'string[]' : 'string') : getFieldType(field)}${
85
- field.list ? ' | string' : ''
86
- }${field.nonNull ? '' : ' | null'},`
87
- )
88
- .newLine();
89
- }
90
- })
91
- .blankLine();
74
+ if (!isRootModel(model)) {
75
+ writer
76
+ .write(`export type ${model.name}Seed = `)
77
+ .inlineBlock(() => {
78
+ for (const field of fields.filter(not(isCustomField))) {
79
+ if (model.parent && field.name === 'type') {
80
+ continue;
81
+ }
82
+ const fieldName = getColumnName(field);
83
+ writer
84
+ .write(
85
+ `'${getColumnName(field)}'${
86
+ field.nonNull && field.defaultValue === undefined && !OPTIONAL_SEED_FIELDS.includes(fieldName) ? '' : '?'
87
+ }: ${field.kind === 'enum' ? (field.list ? 'string[]' : 'string') : getFieldType(field)}${
88
+ field.list ? ' | string' : ''
89
+ }${field.nonNull ? '' : ' | null'};`
90
+ )
91
+ .newLine();
92
+ }
93
+ })
94
+ .blankLine();
95
+ }
92
96
  }
93
97
 
94
98
  writer.write(`export type SeedData = `).inlineBlock(() => {
95
- for (const model of models) {
96
- writer.write(`${model.name}: ${model.name}Seed[],`).newLine();
99
+ for (const model of models.entities.filter(not(isRootModel))) {
100
+ writer.write(`${model.name}: ${model.name}Seed[];`).newLine();
97
101
  }
98
102
  });
99
103
 
100
104
  return writer.toString();
101
105
  };
102
106
 
103
- const getFieldName = (field: ModelField) => (field.kind === 'relation' ? field.foreignKey || `${field.name}Id` : field.name);
104
-
105
- const getFieldType = (field: ModelField) => {
107
+ const getFieldType = (field: EntityField) => {
106
108
  const kind = field.kind;
107
109
  switch (kind) {
108
110
  case 'json':
@@ -125,18 +127,16 @@ const getFieldType = (field: ModelField) => {
125
127
  }
126
128
  };
127
129
 
128
- export const generateKnexTables = (rawModels: RawModels) => {
130
+ export const generateKnexTables = (models: Models) => {
129
131
  const writer: CodeBlockWriter = new CodeBlockWriter['default']({
130
132
  useSingleQuote: true,
131
133
  indentNumberOfSpaces: 2,
132
134
  });
133
135
 
134
- const models = getModels(rawModels);
135
-
136
136
  writer.write(`import { Knex } from 'knex';`).newLine();
137
137
  writer
138
138
  .write(
139
- `import { ${models
139
+ `import { ${models.entities
140
140
  .map((model) => `${model.name}, ${model.name}Initializer, ${model.name}Mutator`)
141
141
  .join(', ')} } from '.';`
142
142
  )
@@ -144,7 +144,7 @@ export const generateKnexTables = (rawModels: RawModels) => {
144
144
 
145
145
  writer.write(`declare module 'knex/types/tables' `).inlineBlock(() => {
146
146
  writer.write(`interface Tables `).inlineBlock(() => {
147
- for (const model of models) {
147
+ for (const model of models.entities) {
148
148
  writer
149
149
  .write(`'${model.name}': Knex.CompositeTableType<${model.name}, ${model.name}Initializer, ${model.name}Mutator>,`)
150
150
  .newLine();
@@ -4,8 +4,18 @@ 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, Model, ModelField, Models, RawModels } from '../models/models';
8
- import { get, getModels, isEnumModel, summonByName, typeToField } from '../models/utils';
7
+ import { EntityField, EntityModel, EnumModel, Models } from '../models/models';
8
+ import {
9
+ and,
10
+ get,
11
+ isInherited,
12
+ isUpdatableField,
13
+ isUpdatableModel,
14
+ modelNeedsTable,
15
+ not,
16
+ summonByName,
17
+ typeToField,
18
+ } from '../models/utils';
9
19
  import { Value } from '../values';
10
20
 
11
21
  type Callbacks = (() => void)[];
@@ -19,15 +29,13 @@ export class MigrationGenerator {
19
29
  private columns: Record<string, Column[]> = {};
20
30
  private uuidUsed?: boolean;
21
31
  private nowUsed?: boolean;
22
- private models: Models;
23
32
 
24
- constructor(knex: Knex, private rawModels: RawModels) {
33
+ constructor(knex: Knex, private models: Models) {
25
34
  this.schema = SchemaInspector(knex);
26
- this.models = getModels(rawModels);
27
35
  }
28
36
 
29
37
  public async generate() {
30
- const { writer, schema, rawModels, models } = this;
38
+ const { writer, schema, models } = this;
31
39
  const enums = (await schema.knex('pg_type').where({ typtype: 'e' }).select('typname')).map(({ typname }) => typname);
32
40
  const tables = await schema.tables();
33
41
  for (const table of tables) {
@@ -38,12 +46,12 @@ export class MigrationGenerator {
38
46
  const down: Callbacks = [];
39
47
 
40
48
  this.createEnums(
41
- rawModels.filter(isEnumModel).filter((enm) => !enums.includes(lowerFirst(enm.name))),
49
+ this.models.enums.filter((enm) => !enums.includes(lowerFirst(enm.name))),
42
50
  up,
43
51
  down
44
52
  );
45
53
 
46
- for (const model of models) {
54
+ for (const model of models.entities) {
47
55
  if (model.deleted) {
48
56
  up.push(() => {
49
57
  this.dropTable(model.name);
@@ -58,7 +66,7 @@ export class MigrationGenerator {
58
66
  });
59
67
 
60
68
  // TODO: also add revision table if it's deletable
61
- if (model.updatable) {
69
+ if (isUpdatableModel(model)) {
62
70
  up.push(() => {
63
71
  this.dropTable(`${model.name}Revision`);
64
72
  });
@@ -72,27 +80,23 @@ export class MigrationGenerator {
72
80
  if (model.oldName) {
73
81
  // Rename table
74
82
  up.push(() => {
75
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- checked above
76
- this.renameTable(model.oldName!, model.name);
83
+ this.renameTable(model.oldName, model.name);
77
84
  });
78
85
  down.push(() => {
79
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- checked above
80
- this.renameTable(model.name, model.oldName!);
86
+ this.renameTable(model.name, model.oldName);
81
87
  });
82
88
  tables[tables.indexOf(model.oldName)] = model.name;
83
89
  this.columns[model.name] = this.columns[model.oldName];
84
90
  delete this.columns[model.oldName];
85
91
 
86
- if (model.updatable) {
92
+ if (isUpdatableModel(model)) {
87
93
  up.push(() => {
88
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- checked above
89
- this.renameTable(`${model.oldName!}Revision`, `${model.name}Revision`);
94
+ this.renameTable(`${model.oldName}Revision`, `${model.name}Revision`);
90
95
  this.alterTable(`${model.name}Revision`, () => {
91
96
  this.renameColumn(`${typeToField(get(model, 'oldName'))}Id`, `${typeToField(model.name)}Id`);
92
97
  });
93
98
  });
94
99
  down.push(() => {
95
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- checked above
96
100
  this.renameTable(`${model.name}Revision`, `${model.oldName}Revision`);
97
101
  this.alterTable(`${model.oldName}Revision`, () => {
98
102
  this.renameColumn(`${typeToField(model.name)}Id`, `${typeToField(get(model, 'oldName'))}Id`);
@@ -104,125 +108,138 @@ export class MigrationGenerator {
104
108
  }
105
109
  }
106
110
 
107
- if (!tables.includes(model.name)) {
108
- // Create missing table
109
- up.push(() => {
110
- this.createTable(model.name, () => {
111
- for (const field of model.fields) {
112
- this.column(field);
113
- }
111
+ if (modelNeedsTable(model)) {
112
+ if (!tables.includes(model.name)) {
113
+ // Create missing table
114
+ up.push(() => {
115
+ this.createTable(model.name, () => {
116
+ if (model.parent) {
117
+ this.column({
118
+ ...model.fieldsByName.id,
119
+ kind: 'relation',
120
+ type: model.parent,
121
+ foreignKey: 'id',
122
+ });
123
+ }
124
+ for (const field of model.fields.filter(not(isInherited))) {
125
+ this.column(field);
126
+ }
127
+ });
114
128
  });
115
- });
116
129
 
117
- down.push(() => {
118
- this.dropTable(model.name);
119
- });
120
- } else {
121
- // Rename fields
122
- this.renameFields(
123
- model,
124
- model.fields.filter(({ oldName }) => oldName),
125
- up,
126
- down
127
- );
128
-
129
- // Add missing fields
130
- this.createFields(
131
- model,
132
- model.fields.filter(
133
- ({ name, ...field }) =>
134
- field.kind !== 'custom' &&
135
- !this.columns[model.name].some(
136
- (col) => col.name === (field.kind === 'relation' ? field.foreignKey || `${name}Id` : name)
137
- )
138
- ),
139
- up,
140
- down
141
- );
130
+ down.push(() => {
131
+ this.dropTable(model.name);
132
+ });
133
+ } else {
134
+ // Rename fields
135
+ this.renameFields(
136
+ model,
137
+ model.fields.filter(not(isInherited)).filter(({ oldName }) => oldName),
138
+ up,
139
+ down
140
+ );
142
141
 
143
- // Update fields
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
- if (!col) {
147
- return false;
148
- }
149
- return !nonNull && !col.is_nullable;
150
- });
151
- this.updateFields(model, existingFields, up, down);
152
- }
142
+ // Add missing fields
143
+ this.createFields(
144
+ model,
145
+ model.fields
146
+ .filter(not(isInherited))
147
+ .filter(
148
+ ({ name, ...field }) =>
149
+ field.kind !== 'custom' &&
150
+ !this.columns[model.name].some(
151
+ (col) => col.name === (field.kind === 'relation' ? field.foreignKey || `${name}Id` : name)
152
+ )
153
+ ),
154
+ up,
155
+ down
156
+ );
153
157
 
154
- if (model.updatable) {
155
- if (!tables.includes(`${model.name}Revision`)) {
156
- up.push(() => {
157
- this.createRevisionTable(model);
158
+ // Update fields
159
+ const existingFields = model.fields.filter(({ name, kind, nonNull }) => {
160
+ const col = this.columns[model.name].find((col) => col.name === (kind === 'relation' ? `${name}Id` : name));
161
+ if (!col) {
162
+ return false;
163
+ }
164
+ return !nonNull && !col.is_nullable;
158
165
  });
166
+ this.updateFields(model, existingFields, up, down);
167
+ }
159
168
 
160
- if (tables.includes(model.name)) {
169
+ if (isUpdatableModel(model)) {
170
+ if (!tables.includes(`${model.name}Revision`)) {
161
171
  up.push(() => {
162
- // Populate empty revisions tables
163
- writer
164
- .block(() => {
165
- writer.writeLine(`const data = await knex('${model.name}');`);
166
-
167
- writer.write(`if (data.length)`).block(() => {
168
- writer
169
- .write(`await knex.batchInsert('${model.name}Revision', data.map((row) => (`)
170
- .inlineBlock(() => {
171
- writer.writeLine(`id: uuid(),`);
172
- writer.writeLine(`${typeToField(model.name)}Id: row.id,`);
173
- this.nowUsed = true;
174
- writer.writeLine(`createdAt: row.updatedAt || row.createdAt || now,`);
175
- writer.writeLine(`createdById: row.updatedById || row.createdById,`);
176
- if (model.deletable) {
177
- writer.writeLine(`deleted: row.deleted,`);
178
- }
179
-
180
- for (const { name, kind } of model.fields.filter(({ updatable }) => updatable)) {
181
- const col = kind === 'relation' ? `${name}Id` : name;
182
-
183
- writer.writeLine(`${col}: row.${col},`);
184
- }
185
- })
186
- .write(')));')
187
- .newLine();
188
- });
189
- })
190
- .blankLine();
172
+ this.createRevisionTable(model);
191
173
  });
192
- }
193
174
 
194
- down.push(() => {
195
- this.dropTable(`${model.name}Revision`);
196
- });
197
- } else {
198
- const revisionTable = `${model.name}Revision`;
199
- const missingRevisionFields = model.fields.filter(
200
- ({ name, updatable, ...field }) =>
201
- field.kind !== 'custom' &&
202
- updatable &&
203
- !this.columns[revisionTable].some(
204
- (col) => col.name === (field.kind === 'relation' ? field.foreignKey || `${name}Id` : name)
205
- )
206
- );
175
+ if (tables.includes(model.name)) {
176
+ up.push(() => {
177
+ // Populate empty revisions tables
178
+ writer
179
+ .block(() => {
180
+ writer.writeLine(`const data = await knex('${model.name}');`);
181
+
182
+ writer.write(`if (data.length)`).block(() => {
183
+ writer
184
+ .write(`await knex.batchInsert('${model.name}Revision', data.map((row) => (`)
185
+ .inlineBlock(() => {
186
+ writer.writeLine(`id: uuid(),`);
187
+ writer.writeLine(`${typeToField(model.name)}Id: row.id,`);
188
+ this.nowUsed = true;
189
+ writer.writeLine(`createdAt: row.updatedAt || row.createdAt || now,`);
190
+ writer.writeLine(`createdById: row.updatedById || row.createdById,`);
191
+ if (model.deletable) {
192
+ writer.writeLine(`deleted: row.deleted,`);
193
+ }
194
+
195
+ for (const { name, kind } of model.fields.filter(isUpdatableField)) {
196
+ const col = kind === 'relation' ? `${name}Id` : name;
197
+
198
+ writer.writeLine(`${col}: row.${col},`);
199
+ }
200
+ })
201
+ .write(')));')
202
+ .newLine();
203
+ });
204
+ })
205
+ .blankLine();
206
+ });
207
+ }
207
208
 
208
- this.createRevisionFields(model, missingRevisionFields, up, down);
209
-
210
- const revisionFieldsToRemove = model.fields.filter(
211
- ({ name, updatable, generated, ...field }) =>
212
- !generated &&
213
- field.kind !== 'custom' &&
214
- !updatable &&
215
- !(field.kind === 'relation' && field.foreignKey === 'id') &&
216
- this.columns[revisionTable].some(
217
- (col) => col.name === (field.kind === 'relation' ? field.foreignKey || `${name}Id` : name)
218
- )
219
- );
220
- this.createRevisionFields(model, revisionFieldsToRemove, down, up);
209
+ down.push(() => {
210
+ this.dropTable(`${model.name}Revision`);
211
+ });
212
+ } else {
213
+ const revisionTable = `${model.name}Revision`;
214
+ const missingRevisionFields = model.fields
215
+ .filter(isUpdatableField)
216
+ .filter(
217
+ ({ name, ...field }) =>
218
+ field.kind !== 'custom' &&
219
+ !this.columns[revisionTable].some(
220
+ (col) => col.name === (field.kind === 'relation' ? field.foreignKey || `${name}Id` : name)
221
+ )
222
+ );
223
+
224
+ this.createRevisionFields(model, missingRevisionFields, up, down);
225
+
226
+ const revisionFieldsToRemove = model.fields.filter(
227
+ ({ name, updatable, generated, ...field }) =>
228
+ !generated &&
229
+ field.kind !== 'custom' &&
230
+ !updatable &&
231
+ !(field.kind === 'relation' && field.foreignKey === 'id') &&
232
+ this.columns[revisionTable].some(
233
+ (col) => col.name === (field.kind === 'relation' ? field.foreignKey || `${name}Id` : name)
234
+ )
235
+ );
236
+ this.createRevisionFields(model, revisionFieldsToRemove, down, up);
237
+ }
221
238
  }
222
239
  }
223
240
  }
224
241
 
225
- for (const model of getModels(rawModels)) {
242
+ for (const model of models.entities) {
226
243
  if (tables.includes(model.name)) {
227
244
  this.createFields(
228
245
  model,
@@ -231,10 +248,10 @@ export class MigrationGenerator {
231
248
  up
232
249
  );
233
250
 
234
- if (model.updatable) {
251
+ if (isUpdatableModel(model)) {
235
252
  this.createRevisionFields(
236
253
  model,
237
- model.fields.filter(({ deleted, updatable }) => updatable && deleted),
254
+ model.fields.filter(isUpdatableField).filter(({ deleted }) => deleted),
238
255
  down,
239
256
  up
240
257
  );
@@ -243,7 +260,7 @@ export class MigrationGenerator {
243
260
  }
244
261
 
245
262
  this.createEnums(
246
- rawModels.filter(isEnumModel).filter((enm) => enm.deleted),
263
+ this.models.enums.filter((enm) => enm.deleted),
247
264
  down,
248
265
  up
249
266
  );
@@ -267,7 +284,7 @@ export class MigrationGenerator {
267
284
  return writer.toString();
268
285
  }
269
286
 
270
- private renameFields(model: Model, fields: ModelField[], up: Callbacks, down: Callbacks) {
287
+ private renameFields(model: EntityModel, fields: EntityField[], up: Callbacks, down: Callbacks) {
271
288
  if (!fields.length) {
272
289
  return;
273
290
  }
@@ -301,7 +318,7 @@ export class MigrationGenerator {
301
318
  }
302
319
  }
303
320
 
304
- private createFields(model: Model, fields: ModelField[], up: Callbacks, down: Callbacks) {
321
+ private createFields(model: EntityModel, fields: EntityField[], up: Callbacks, down: Callbacks) {
305
322
  if (!fields.length) {
306
323
  return;
307
324
  }
@@ -350,7 +367,7 @@ export class MigrationGenerator {
350
367
  });
351
368
  }
352
369
 
353
- private updateFields(model: Model, fields: ModelField[], up: Callbacks, down: Callbacks) {
370
+ private updateFields(model: EntityModel, fields: EntityField[], up: Callbacks, down: Callbacks) {
354
371
  if (!fields.length) {
355
372
  return;
356
373
  }
@@ -375,8 +392,8 @@ export class MigrationGenerator {
375
392
  });
376
393
  });
377
394
 
378
- if (model.updatable) {
379
- const updatableFields = fields.filter(({ updatable }) => updatable);
395
+ if (isUpdatableModel(model)) {
396
+ const updatableFields = fields.filter(isUpdatableField);
380
397
  if (!updatableFields.length) {
381
398
  return;
382
399
  }
@@ -403,28 +420,28 @@ export class MigrationGenerator {
403
420
  }
404
421
  }
405
422
 
406
- private createRevisionTable(model: Model) {
423
+ private createRevisionTable(model: EntityModel) {
407
424
  const writer = this.writer;
408
425
 
409
426
  // Create missing revisions table
410
427
  this.createTable(`${model.name}Revision`, () => {
411
428
  writer.writeLine(`table.uuid('id').notNullable().primary();`);
412
- writer.writeLine(`table.uuid('${typeToField(model.name)}Id').notNullable();`);
413
- writer.write(`table.uuid('createdById')`);
414
- writer.write('.notNullable()');
415
- writer.write(';').newLine();
416
- writer.writeLine(`table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now(0));`);
417
- if (model.deletable) {
418
- writer.writeLine(`table.boolean('deleted').notNullable();`);
429
+ if (!model.parent) {
430
+ writer.writeLine(`table.uuid('${typeToField(model.name)}Id').notNullable();`);
431
+ writer.writeLine(`table.uuid('createdById').notNullable();`);
432
+ writer.writeLine(`table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now(0));`);
433
+ if (model.deletable) {
434
+ writer.writeLine(`table.boolean('deleted').notNullable();`);
435
+ }
419
436
  }
420
437
 
421
- for (const field of model.fields.filter((field) => field.updatable)) {
438
+ for (const field of model.fields.filter(and(isUpdatableField, not(isInherited)))) {
422
439
  this.column(field, { setUnique: false, setDefault: false });
423
440
  }
424
441
  });
425
442
  }
426
443
 
427
- private createRevisionFields(model: Model, missingRevisionFields: ModelField[], up: Callbacks, down: Callbacks) {
444
+ private createRevisionFields(model: EntityModel, missingRevisionFields: EntityField[], up: Callbacks, down: Callbacks) {
428
445
  const revisionTable = `${model.name}Revision`;
429
446
  if (missingRevisionFields.length) {
430
447
  up.push(() => {
@@ -484,7 +501,7 @@ export class MigrationGenerator {
484
501
  )
485
502
  .newLine()
486
503
  );
487
- down.push(() => this.writer.writeLine(`await knex.raw('DROP TYPE "${name}"')`));
504
+ down.push(() => this.writer.writeLine(`await knex.raw('DROP TYPE "${name}"');`));
488
505
  }
489
506
  }
490
507
 
@@ -542,7 +559,7 @@ export class MigrationGenerator {
542
559
  }
543
560
 
544
561
  private column(
545
- { name, primary, list, ...field }: ModelField,
562
+ { name, primary, list, ...field }: EntityField,
546
563
  { setUnique = true, setNonNull = true, alter = false, foreign = true, setDefault = true } = {},
547
564
  toColumn?: Column
548
565
  ) {
@@ -593,7 +610,7 @@ export class MigrationGenerator {
593
610
  if (field.double) {
594
611
  col(`table.double('${name}')`);
595
612
  } else {
596
- col(`table.decimal('${name}', ${get(field, 'precision')}, ${get(field, 'scale')})`);
613
+ col(`table.decimal('${name}', ${field.precision ?? 'undefined'}, ${field.scale ?? 'undefined'})`);
597
614
  }
598
615
  break;
599
616
  case 'String':
@@ -614,14 +631,16 @@ export class MigrationGenerator {
614
631
  }
615
632
  break;
616
633
  case 'relation':
617
- col(`table.uuid('${name}Id')`);
634
+ col(`table.uuid('${field.foreignKey}')`);
618
635
  if (foreign && !alter) {
619
- this.writer.writeLine(`table.foreign('${name}Id').references('id').inTable('${field.type}');`);
636
+ this.writer.writeLine(
637
+ `table.foreign('${field.foreignKey}').references('id').inTable('${field.type}').onDelete('CASCADE');`
638
+ );
620
639
  }
621
640
  break;
622
641
  case 'enum':
623
642
  if (list) {
624
- this.writer.write(`table.specificType('${name}', '"${typeToField(field.type)}"[]');`);
643
+ this.writer.write(`table.specificType('${name}', '"${typeToField(field.type)}"[]')`);
625
644
  } else {
626
645
  this.writer
627
646
  .write(`table.enum('${name}', null as any, `)
@@ -1,5 +1,6 @@
1
1
  // created from 'create-ts-index'
2
2
 
3
+ export * from './model-definitions';
3
4
  export * from './models';
4
5
  export * from './mutation-hook';
5
6
  export * from './utils';