@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.
- package/.eslintrc +2 -10
- package/.github/workflows/release.yml +1 -1
- package/.gqmrc.json +6 -0
- package/CHANGELOG.md +2 -2
- package/README.md +1 -1
- package/dist/bin/gqm.cjs +684 -330
- package/dist/cjs/index.cjs +998 -554
- package/dist/esm/api/execute.js +1 -1
- package/dist/esm/api/execute.js.map +1 -1
- package/dist/esm/client/mutations.d.ts +2 -2
- package/dist/esm/client/mutations.js +5 -4
- package/dist/esm/client/mutations.js.map +1 -1
- package/dist/esm/client/queries.d.ts +12 -17
- package/dist/esm/client/queries.js +30 -50
- package/dist/esm/client/queries.js.map +1 -1
- package/dist/esm/context.d.ts +1 -2
- package/dist/esm/db/generate.d.ts +3 -3
- package/dist/esm/db/generate.js +31 -29
- package/dist/esm/db/generate.js.map +1 -1
- package/dist/esm/migrations/generate.d.ts +3 -4
- package/dist/esm/migrations/generate.js +114 -107
- 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/model-definitions.d.ts +189 -0
- package/dist/esm/models/model-definitions.js +2 -0
- package/dist/esm/models/model-definitions.js.map +1 -0
- package/dist/esm/models/models.d.ts +128 -174
- package/dist/esm/models/models.js +411 -1
- package/dist/esm/models/models.js.map +1 -1
- package/dist/esm/models/mutation-hook.d.ts +2 -2
- package/dist/esm/models/utils.d.ts +35 -497
- package/dist/esm/models/utils.js +21 -144
- package/dist/esm/models/utils.js.map +1 -1
- package/dist/esm/permissions/check.d.ts +3 -3
- package/dist/esm/permissions/check.js +14 -7
- package/dist/esm/permissions/check.js.map +1 -1
- package/dist/esm/permissions/generate.js +6 -6
- package/dist/esm/permissions/generate.js.map +1 -1
- package/dist/esm/resolvers/filters.d.ts +8 -0
- package/dist/esm/resolvers/filters.js +28 -25
- package/dist/esm/resolvers/filters.js.map +1 -1
- package/dist/esm/resolvers/index.d.ts +1 -0
- package/dist/esm/resolvers/index.js +1 -0
- package/dist/esm/resolvers/index.js.map +1 -1
- package/dist/esm/resolvers/mutations.js +85 -21
- package/dist/esm/resolvers/mutations.js.map +1 -1
- package/dist/esm/resolvers/node.d.ts +13 -15
- package/dist/esm/resolvers/node.js +41 -36
- package/dist/esm/resolvers/node.js.map +1 -1
- package/dist/esm/resolvers/resolver.js +19 -49
- package/dist/esm/resolvers/resolver.js.map +1 -1
- package/dist/esm/resolvers/resolvers.d.ts +1 -8
- package/dist/esm/resolvers/resolvers.js +15 -7
- package/dist/esm/resolvers/resolvers.js.map +1 -1
- package/dist/esm/resolvers/selects.d.ts +3 -0
- package/dist/esm/resolvers/selects.js +50 -0
- package/dist/esm/resolvers/selects.js.map +1 -0
- package/dist/esm/resolvers/utils.d.ts +12 -4
- package/dist/esm/resolvers/utils.js +30 -22
- package/dist/esm/resolvers/utils.js.map +1 -1
- package/dist/esm/schema/generate.d.ts +4 -4
- package/dist/esm/schema/generate.js +122 -131
- 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 -1
- package/dist/esm/schema/utils.js.map +1 -1
- package/knexfile.ts +31 -0
- package/migrations/20230912185644_setup.ts +127 -0
- package/package.json +16 -14
- package/src/api/execute.ts +1 -1
- package/src/bin/gqm/gqm.ts +25 -23
- package/src/bin/gqm/parse-models.ts +5 -5
- package/src/bin/gqm/settings.ts +13 -4
- package/src/bin/gqm/static-eval.ts +5 -0
- package/src/bin/gqm/templates.ts +23 -3
- package/src/client/mutations.ts +11 -5
- package/src/client/queries.ts +43 -80
- package/src/context.ts +1 -2
- package/src/db/generate.ts +41 -41
- package/src/migrations/generate.ts +165 -146
- package/src/models/index.ts +1 -0
- package/src/models/model-definitions.ts +168 -0
- package/src/models/models.ts +510 -166
- package/src/models/mutation-hook.ts +2 -2
- package/src/models/utils.ts +53 -187
- package/src/permissions/check.ts +19 -11
- package/src/permissions/generate.ts +6 -6
- package/src/resolvers/filters.ts +44 -28
- package/src/resolvers/index.ts +1 -0
- package/src/resolvers/mutations.ts +98 -36
- package/src/resolvers/node.ts +79 -51
- package/src/resolvers/resolver.ts +20 -74
- package/src/resolvers/resolvers.ts +18 -7
- package/src/resolvers/selects.ts +77 -0
- package/src/resolvers/utils.ts +41 -25
- package/src/schema/generate.ts +106 -127
- package/src/schema/utils.ts +2 -1
- package/tests/api/__snapshots__/inheritance.spec.ts.snap +83 -0
- package/tests/api/inheritance.spec.ts +130 -0
- package/tests/generated/api/index.ts +1174 -0
- package/tests/generated/client/index.ts +1163 -0
- package/tests/generated/client/mutations.ts +109 -0
- package/tests/generated/db/index.ts +291 -0
- package/tests/generated/db/knex.ts +14 -0
- package/tests/generated/models.json +675 -0
- package/tests/generated/schema.graphql +325 -0
- package/tests/unit/__snapshots__/resolve.spec.ts.snap +23 -0
- package/tests/unit/queries.spec.ts +5 -5
- package/tests/unit/resolve.spec.ts +8 -8
- package/tests/utils/database/knex.ts +5 -13
- package/tests/utils/database/seed.ts +57 -18
- package/tests/utils/models.ts +62 -7
- package/tests/utils/server.ts +5 -5
- package/tsconfig.eslint.json +1 -0
- package/tests/unit/__snapshots__/generate.spec.ts.snap +0 -128
- package/tests/unit/generate.spec.ts +0 -8
- package/tests/utils/database/schema.ts +0 -64
- package/tests/utils/generate-migration.ts +0 -24
package/src/db/generate.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import CodeBlockWriter from 'code-block-writer';
|
|
2
|
-
import {
|
|
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 = (
|
|
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
|
|
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
|
|
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.
|
|
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(`'${
|
|
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
|
-
`'${
|
|
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
|
-
`'${
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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[]
|
|
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
|
|
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 = (
|
|
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 {
|
|
8
|
-
import {
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
76
|
-
this.renameTable(model.oldName!, model.name);
|
|
83
|
+
this.renameTable(model.oldName, model.name);
|
|
77
84
|
});
|
|
78
85
|
down.push(() => {
|
|
79
|
-
|
|
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
|
|
92
|
+
if (isUpdatableModel(model)) {
|
|
87
93
|
up.push(() => {
|
|
88
|
-
|
|
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 (
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
169
|
+
if (isUpdatableModel(model)) {
|
|
170
|
+
if (!tables.includes(`${model.name}Revision`)) {
|
|
161
171
|
up.push(() => {
|
|
162
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
|
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
|
|
251
|
+
if (isUpdatableModel(model)) {
|
|
235
252
|
this.createRevisionFields(
|
|
236
253
|
model,
|
|
237
|
-
model.fields.filter(({ 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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
379
|
-
const updatableFields = fields.filter(
|
|
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:
|
|
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
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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((
|
|
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:
|
|
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 }:
|
|
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}', ${
|
|
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('${
|
|
634
|
+
col(`table.uuid('${field.foreignKey}')`);
|
|
618
635
|
if (foreign && !alter) {
|
|
619
|
-
this.writer.writeLine(
|
|
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, `)
|