@smartive/graphql-magic 23.6.0 → 23.6.1-next.2
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 +3 -3
- package/dist/bin/gqm.cjs +14 -12
- package/dist/cjs/index.cjs +34 -20
- package/dist/esm/db/generate.js +5 -5
- package/dist/esm/db/generate.js.map +1 -1
- package/dist/esm/migrations/generate.js +6 -8
- package/dist/esm/migrations/generate.js.map +1 -1
- package/dist/esm/models/utils.d.ts +4 -0
- package/dist/esm/models/utils.js +4 -0
- package/dist/esm/models/utils.js.map +1 -1
- package/dist/esm/permissions/check.js +4 -2
- package/dist/esm/permissions/check.js.map +1 -1
- package/dist/esm/resolvers/mutations.js +16 -6
- package/dist/esm/resolvers/mutations.js.map +1 -1
- package/dist/esm/schema/generate.js +3 -1
- package/dist/esm/schema/generate.js.map +1 -1
- package/package.json +2 -2
- package/src/db/generate.ts +15 -5
- package/src/migrations/generate.ts +6 -7
- package/src/models/utils.ts +6 -0
- package/src/permissions/check.ts +4 -4
- package/src/resolvers/mutations.ts +16 -6
- package/src/schema/generate.ts +3 -1
- package/tests/unit/generate-as.spec.ts +238 -0
package/src/db/generate.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import CodeBlockWriter from 'code-block-writer';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
EntityField,
|
|
4
|
+
get,
|
|
5
|
+
getColumnName,
|
|
6
|
+
isCustomField,
|
|
7
|
+
isGenerateAsField,
|
|
8
|
+
isInTable,
|
|
9
|
+
isRootModel,
|
|
10
|
+
isStoredInDatabase,
|
|
11
|
+
not,
|
|
12
|
+
} from '..';
|
|
3
13
|
import { Models } from '../models/models';
|
|
4
14
|
import { DATE_CLASS, DATE_CLASS_IMPORT, DateLibrary } from '../utils/dates';
|
|
5
15
|
|
|
@@ -60,7 +70,7 @@ export const generateDBModels = (models: Models, dateLibrary: DateLibrary) => {
|
|
|
60
70
|
writer
|
|
61
71
|
.write(`export type ${model.name} = `)
|
|
62
72
|
.inlineBlock(() => {
|
|
63
|
-
for (const field of fields.filter(not(isCustomField))) {
|
|
73
|
+
for (const field of fields.filter(not(isCustomField)).filter(isStoredInDatabase)) {
|
|
64
74
|
writer
|
|
65
75
|
.write(`'${getColumnName(field)}': ${getFieldType(field, dateLibrary)}${field.nonNull ? '' : ' | null'};`)
|
|
66
76
|
.newLine();
|
|
@@ -71,7 +81,7 @@ export const generateDBModels = (models: Models, dateLibrary: DateLibrary) => {
|
|
|
71
81
|
writer
|
|
72
82
|
.write(`export type ${model.name}Initializer = `)
|
|
73
83
|
.inlineBlock(() => {
|
|
74
|
-
for (const field of fields.filter(not(isCustomField)).filter(isInTable)) {
|
|
84
|
+
for (const field of fields.filter(not(isCustomField)).filter(isInTable).filter(not(isGenerateAsField))) {
|
|
75
85
|
writer
|
|
76
86
|
.write(
|
|
77
87
|
`'${getColumnName(field)}'${field.nonNull && field.defaultValue === undefined ? '' : '?'}: ${getFieldType(
|
|
@@ -88,7 +98,7 @@ export const generateDBModels = (models: Models, dateLibrary: DateLibrary) => {
|
|
|
88
98
|
writer
|
|
89
99
|
.write(`export type ${model.name}Mutator = `)
|
|
90
100
|
.inlineBlock(() => {
|
|
91
|
-
for (const field of fields.filter(not(isCustomField)).filter(isInTable)) {
|
|
101
|
+
for (const field of fields.filter(not(isCustomField)).filter(isInTable).filter(not(isGenerateAsField))) {
|
|
92
102
|
writer
|
|
93
103
|
.write(
|
|
94
104
|
`'${getColumnName(field)}'?: ${getFieldType(field, dateLibrary, true)}${field.list ? ' | string' : ''}${
|
|
@@ -104,7 +114,7 @@ export const generateDBModels = (models: Models, dateLibrary: DateLibrary) => {
|
|
|
104
114
|
writer
|
|
105
115
|
.write(`export type ${model.name}Seed = `)
|
|
106
116
|
.inlineBlock(() => {
|
|
107
|
-
for (const field of fields.filter(not(isCustomField))) {
|
|
117
|
+
for (const field of fields.filter(not(isCustomField)).filter(not(isGenerateAsField))) {
|
|
108
118
|
if (model.parent && field.name === 'type') {
|
|
109
119
|
continue;
|
|
110
120
|
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
and,
|
|
10
10
|
get,
|
|
11
11
|
isCreatableModel,
|
|
12
|
+
isGenerateAsField,
|
|
12
13
|
isInherited,
|
|
13
14
|
isUpdatableField,
|
|
14
15
|
isUpdatableModel,
|
|
@@ -300,7 +301,7 @@ export class MigrationGenerator {
|
|
|
300
301
|
|
|
301
302
|
for (const { name, kind } of model.fields
|
|
302
303
|
.filter(isUpdatableField)
|
|
303
|
-
.filter((
|
|
304
|
+
.filter(not(isGenerateAsField))) {
|
|
304
305
|
const col = kind === 'relation' ? `${name}Id` : name;
|
|
305
306
|
|
|
306
307
|
writer.writeLine(`${col}: row.${col},`);
|
|
@@ -332,10 +333,10 @@ export class MigrationGenerator {
|
|
|
332
333
|
|
|
333
334
|
const missingRevisionFields = model.fields
|
|
334
335
|
.filter(isUpdatableField)
|
|
336
|
+
.filter(not(isGenerateAsField))
|
|
335
337
|
.filter(
|
|
336
338
|
({ name, ...field }) =>
|
|
337
339
|
field.kind !== 'custom' &&
|
|
338
|
-
!(field.generateAs?.type === 'expression') &&
|
|
339
340
|
!this.getColumn(revisionTable, field.kind === 'relation' ? field.foreignKey || `${name}Id` : name),
|
|
340
341
|
);
|
|
341
342
|
|
|
@@ -534,7 +535,7 @@ export class MigrationGenerator {
|
|
|
534
535
|
});
|
|
535
536
|
|
|
536
537
|
if (isUpdatableModel(model)) {
|
|
537
|
-
const updatableFields = fields.filter(isUpdatableField).filter((
|
|
538
|
+
const updatableFields = fields.filter(isUpdatableField).filter(not(isGenerateAsField));
|
|
538
539
|
if (!updatableFields.length) {
|
|
539
540
|
return;
|
|
540
541
|
}
|
|
@@ -588,7 +589,7 @@ export class MigrationGenerator {
|
|
|
588
589
|
});
|
|
589
590
|
|
|
590
591
|
if (isUpdatableModel(model)) {
|
|
591
|
-
const updatableFields = fields.filter(isUpdatableField).filter((
|
|
592
|
+
const updatableFields = fields.filter(isUpdatableField).filter(not(isGenerateAsField));
|
|
592
593
|
if (!updatableFields.length) {
|
|
593
594
|
return;
|
|
594
595
|
}
|
|
@@ -632,9 +633,7 @@ export class MigrationGenerator {
|
|
|
632
633
|
}
|
|
633
634
|
}
|
|
634
635
|
|
|
635
|
-
for (const field of model.fields
|
|
636
|
-
.filter(and(isUpdatableField, not(isInherited)))
|
|
637
|
-
.filter((f) => !(f.generateAs?.type === 'expression'))) {
|
|
636
|
+
for (const field of model.fields.filter(and(isUpdatableField, not(isInherited))).filter(not(isGenerateAsField))) {
|
|
638
637
|
this.column(field, { setUnique: false, setDefault: false });
|
|
639
638
|
}
|
|
640
639
|
});
|
package/src/models/utils.ts
CHANGED
|
@@ -88,6 +88,12 @@ export const isQueriableField = ({ queriable }: EntityField) => queriable !== fa
|
|
|
88
88
|
|
|
89
89
|
export const isCustomField = (field: EntityField): field is CustomField => field.kind === 'custom';
|
|
90
90
|
|
|
91
|
+
/** True if field is computed (generateAs); not user-settable in insert/update. */
|
|
92
|
+
export const isGenerateAsField = (field: EntityField) => !!field.generateAs;
|
|
93
|
+
|
|
94
|
+
/** True if field exists as a column in the DB (excludes expression-only fields). */
|
|
95
|
+
export const isStoredInDatabase = (field: EntityField) => field.generateAs?.type !== 'expression';
|
|
96
|
+
|
|
91
97
|
export const isVisible = ({ hidden }: EntityField) => hidden !== true;
|
|
92
98
|
|
|
93
99
|
export const isSimpleField = and(not(isRelation), not(isCustomField));
|
package/src/permissions/check.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Knex } from 'knex';
|
|
|
2
2
|
import { FullContext } from '../context';
|
|
3
3
|
import { NotFoundError, PermissionError } from '../errors';
|
|
4
4
|
import { EntityModel } from '../models/models';
|
|
5
|
-
import { get, isRelation } from '../models/utils';
|
|
5
|
+
import { get, isGenerateAsField, isRelation, not } from '../models/utils';
|
|
6
6
|
import { AliasGenerator, getColumnName, hash, ors } from '../resolvers/utils';
|
|
7
7
|
import { PermissionAction, PermissionLink, PermissionStack } from './generate';
|
|
8
8
|
|
|
@@ -155,9 +155,9 @@ export const checkCanWrite = async (
|
|
|
155
155
|
const query = ctx.knex.first();
|
|
156
156
|
let linked = false;
|
|
157
157
|
|
|
158
|
-
for (const field of model.fields
|
|
159
|
-
|
|
160
|
-
|
|
158
|
+
for (const field of model.fields
|
|
159
|
+
.filter(not(isGenerateAsField))
|
|
160
|
+
.filter((field) => field.generated || (action === 'CREATE' ? field.creatable : field.updatable))) {
|
|
161
161
|
const fieldPermissions = field[action === 'CREATE' ? 'creatable' : 'updatable'];
|
|
162
162
|
const role = getRole(ctx);
|
|
163
163
|
if (
|
|
@@ -4,7 +4,7 @@ import { Context } from '../context';
|
|
|
4
4
|
import { ForbiddenError, GraphQLError } from '../errors';
|
|
5
5
|
import { EntityField, EntityModel } from '../models/models';
|
|
6
6
|
import { Entity, MutationContext, Trigger } from '../models/mutation-hook';
|
|
7
|
-
import { get, isPrimitive, it, typeToField } from '../models/utils';
|
|
7
|
+
import { get, isGenerateAsField, isPrimitive, it, not, typeToField } from '../models/utils';
|
|
8
8
|
import { applyPermissions, checkCanWrite, getEntityToMutate } from '../permissions/check';
|
|
9
9
|
import { anyDateToLuxon } from '../utils';
|
|
10
10
|
import { resolve } from './resolver';
|
|
@@ -87,7 +87,7 @@ export const createEntity = async (
|
|
|
87
87
|
if (model.parent) {
|
|
88
88
|
const rootInput = {};
|
|
89
89
|
const childInput = { id };
|
|
90
|
-
for (const field of model.fields) {
|
|
90
|
+
for (const field of model.fields.filter(not(isGenerateAsField))) {
|
|
91
91
|
const columnName = field.kind === 'relation' ? `${field.name}Id` : field.name;
|
|
92
92
|
if (columnName in normalizedInput) {
|
|
93
93
|
if (field.inherited) {
|
|
@@ -100,7 +100,12 @@ export const createEntity = async (
|
|
|
100
100
|
await ctx.knex(model.parent).insert(rootInput);
|
|
101
101
|
await ctx.knex(model.name).insert(childInput);
|
|
102
102
|
} else {
|
|
103
|
-
|
|
103
|
+
const insertData = { ...normalizedInput };
|
|
104
|
+
for (const field of model.fields.filter(isGenerateAsField)) {
|
|
105
|
+
const columnName = field.kind === 'relation' ? `${field.name}Id` : field.name;
|
|
106
|
+
delete insertData[columnName];
|
|
107
|
+
}
|
|
108
|
+
await ctx.knex(model.name).insert(insertData);
|
|
104
109
|
}
|
|
105
110
|
await createRevision(model, normalizedInput, ctx);
|
|
106
111
|
await ctx.mutationHook?.({
|
|
@@ -555,7 +560,7 @@ export const createRevision = async (model: EntityModel, data: Entity, ctx: Muta
|
|
|
555
560
|
}
|
|
556
561
|
const childRevisionData = { id: revisionId };
|
|
557
562
|
|
|
558
|
-
for (const field of model.fields.filter(({ updatable }) => updatable)) {
|
|
563
|
+
for (const field of model.fields.filter(({ updatable }) => updatable).filter(not(isGenerateAsField))) {
|
|
559
564
|
const col = field.kind === 'relation' ? `${field.name}Id` : field.name;
|
|
560
565
|
let value;
|
|
561
566
|
if (field.nonNull && (!(col in data) || col === undefined || col === null)) {
|
|
@@ -631,7 +636,7 @@ const doUpdate = async (model: EntityModel, currentEntity: Entity, update: Entit
|
|
|
631
636
|
if (model.parent) {
|
|
632
637
|
const rootInput = {};
|
|
633
638
|
const childInput = {};
|
|
634
|
-
for (const field of model.fields) {
|
|
639
|
+
for (const field of model.fields.filter(not(isGenerateAsField))) {
|
|
635
640
|
const columnName = field.kind === 'relation' ? `${field.name}Id` : field.name;
|
|
636
641
|
if (columnName in update) {
|
|
637
642
|
if (field.inherited) {
|
|
@@ -648,7 +653,12 @@ const doUpdate = async (model: EntityModel, currentEntity: Entity, update: Entit
|
|
|
648
653
|
await ctx.knex(model.name).where({ id: currentEntity.id }).update(childInput);
|
|
649
654
|
}
|
|
650
655
|
} else {
|
|
651
|
-
|
|
656
|
+
const updateData = { ...update };
|
|
657
|
+
for (const field of model.fields.filter(isGenerateAsField)) {
|
|
658
|
+
const columnName = field.kind === 'relation' ? `${field.name}Id` : field.name;
|
|
659
|
+
delete updateData[columnName];
|
|
660
|
+
}
|
|
661
|
+
await ctx.knex(model.name).where({ id: currentEntity.id }).update(updateData);
|
|
652
662
|
}
|
|
653
663
|
await createRevision(model, { ...currentEntity, ...update }, ctx);
|
|
654
664
|
};
|
package/src/schema/generate.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DefinitionNode, DocumentNode, GraphQLSchema, buildASTSchema, print } from 'graphql';
|
|
2
2
|
import { Models } from '../models/models';
|
|
3
|
-
import { isQueriableField, isRootModel, typeToField } from '../models/utils';
|
|
3
|
+
import { isGenerateAsField, isQueriableField, isRootModel, not, typeToField } from '../models/utils';
|
|
4
4
|
import { Field, document, enm, iface, input, object, scalar, union } from './utils';
|
|
5
5
|
|
|
6
6
|
export const generateDefinitions = ({
|
|
@@ -135,6 +135,7 @@ export const generateDefinitions = ({
|
|
|
135
135
|
`Create${model.name}`,
|
|
136
136
|
model.fields
|
|
137
137
|
.filter(({ creatable }) => creatable)
|
|
138
|
+
.filter(not(isGenerateAsField))
|
|
138
139
|
.map((field) =>
|
|
139
140
|
field.kind === 'relation'
|
|
140
141
|
? { name: `${field.name}Id`, type: 'ID', nonNull: field.nonNull }
|
|
@@ -155,6 +156,7 @@ export const generateDefinitions = ({
|
|
|
155
156
|
`Update${model.name}`,
|
|
156
157
|
model.fields
|
|
157
158
|
.filter(({ updatable }) => updatable)
|
|
159
|
+
.filter(not(isGenerateAsField))
|
|
158
160
|
.map((field) =>
|
|
159
161
|
field.kind === 'relation'
|
|
160
162
|
? { name: `${field.name}Id`, type: 'ID' }
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { Kind } from 'graphql';
|
|
2
|
+
import { generateDBModels } from '../../src/db/generate';
|
|
3
|
+
import {
|
|
4
|
+
isGenerateAsField,
|
|
5
|
+
isStoredInDatabase,
|
|
6
|
+
} from '../../src/models/utils';
|
|
7
|
+
import { ModelDefinitions, Models } from '../../src/models';
|
|
8
|
+
import type { EntityField } from '../../src/models/models';
|
|
9
|
+
import { generateDefinitions } from '../../src/schema/generate';
|
|
10
|
+
|
|
11
|
+
jest.mock('code-block-writer', () => {
|
|
12
|
+
const Writer = class {
|
|
13
|
+
private _out = '';
|
|
14
|
+
|
|
15
|
+
write(s: string) {
|
|
16
|
+
this._out += s;
|
|
17
|
+
return this;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
blankLine() {
|
|
21
|
+
this._out += '\n';
|
|
22
|
+
return this;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
newLine() {
|
|
26
|
+
this._out += '\n';
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
inlineBlock(fn: () => void) {
|
|
31
|
+
this._out += ' {\n';
|
|
32
|
+
fn();
|
|
33
|
+
this._out += '}';
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
toString() {
|
|
38
|
+
return this._out;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
return { __esModule: true, default: { default: Writer } };
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('generateAs helpers', () => {
|
|
45
|
+
describe('isGenerateAsField', () => {
|
|
46
|
+
it('returns false when field.generateAs is undefined', () => {
|
|
47
|
+
const field: EntityField = { name: 'price', type: 'Float' };
|
|
48
|
+
expect(isGenerateAsField(field)).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('returns true when field has generateAs with type expression', () => {
|
|
52
|
+
const field: EntityField = {
|
|
53
|
+
name: 'total',
|
|
54
|
+
type: 'Float',
|
|
55
|
+
generateAs: { expression: 'price * quantity', type: 'expression' },
|
|
56
|
+
};
|
|
57
|
+
expect(isGenerateAsField(field)).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('returns true when field has generateAs with type stored', () => {
|
|
61
|
+
const field: EntityField = {
|
|
62
|
+
name: 'total',
|
|
63
|
+
type: 'Float',
|
|
64
|
+
generateAs: { expression: 'price * quantity', type: 'stored' },
|
|
65
|
+
};
|
|
66
|
+
expect(isGenerateAsField(field)).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('returns true when field has generateAs with type virtual', () => {
|
|
70
|
+
const field: EntityField = {
|
|
71
|
+
name: 'total',
|
|
72
|
+
type: 'Float',
|
|
73
|
+
generateAs: { expression: 'price * quantity', type: 'virtual' },
|
|
74
|
+
};
|
|
75
|
+
expect(isGenerateAsField(field)).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('isStoredInDatabase', () => {
|
|
80
|
+
it('returns true when field has no generateAs (normal column)', () => {
|
|
81
|
+
const field: EntityField = { name: 'price', type: 'Float' };
|
|
82
|
+
expect(isStoredInDatabase(field)).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('returns false when field has generateAs.type expression', () => {
|
|
86
|
+
const field: EntityField = {
|
|
87
|
+
name: 'total',
|
|
88
|
+
type: 'Float',
|
|
89
|
+
generateAs: { expression: 'price * quantity', type: 'expression' },
|
|
90
|
+
};
|
|
91
|
+
expect(isStoredInDatabase(field)).toBe(false);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('returns true when field has generateAs.type stored', () => {
|
|
95
|
+
const field: EntityField = {
|
|
96
|
+
name: 'total',
|
|
97
|
+
type: 'Float',
|
|
98
|
+
generateAs: { expression: 'price * quantity', type: 'stored' },
|
|
99
|
+
};
|
|
100
|
+
expect(isStoredInDatabase(field)).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('returns true when field has generateAs.type virtual', () => {
|
|
104
|
+
const field: EntityField = {
|
|
105
|
+
name: 'total',
|
|
106
|
+
type: 'Float',
|
|
107
|
+
generateAs: { expression: 'price * quantity', type: 'virtual' },
|
|
108
|
+
};
|
|
109
|
+
expect(isStoredInDatabase(field)).toBe(true);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('generateDBModels', () => {
|
|
115
|
+
const productModelDefinitions: ModelDefinitions = [
|
|
116
|
+
{
|
|
117
|
+
kind: 'entity',
|
|
118
|
+
name: 'Product',
|
|
119
|
+
fields: [
|
|
120
|
+
{ name: 'price', type: 'Float' },
|
|
121
|
+
{ name: 'quantity', type: 'Int' },
|
|
122
|
+
{
|
|
123
|
+
name: 'totalExpression',
|
|
124
|
+
type: 'Float',
|
|
125
|
+
generateAs: { expression: 'price * quantity', type: 'expression' },
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: 'totalStored',
|
|
129
|
+
type: 'Float',
|
|
130
|
+
generateAs: { expression: 'price * quantity', type: 'stored' },
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
it('entity type includes normal and stored columns but not expression-only fields', () => {
|
|
137
|
+
const models = new Models(productModelDefinitions);
|
|
138
|
+
const output = generateDBModels(models, 'luxon');
|
|
139
|
+
|
|
140
|
+
expect(output).toContain("'price'");
|
|
141
|
+
expect(output).toContain("'quantity'");
|
|
142
|
+
expect(output).toContain("'totalStored'");
|
|
143
|
+
expect(output).not.toContain("'totalExpression'");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('Initializer includes only user-settable fields (excludes all generateAs)', () => {
|
|
147
|
+
const models = new Models(productModelDefinitions);
|
|
148
|
+
const output = generateDBModels(models, 'luxon');
|
|
149
|
+
|
|
150
|
+
const start = output.indexOf('export type ProductInitializer');
|
|
151
|
+
const end = output.indexOf('export type', start + 1);
|
|
152
|
+
const block = end === -1 ? output.slice(start) : output.slice(start, end);
|
|
153
|
+
expect(block).toContain("'price'");
|
|
154
|
+
expect(block).toContain("'quantity'");
|
|
155
|
+
expect(block).not.toContain("'totalExpression'");
|
|
156
|
+
expect(block).not.toContain("'totalStored'");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('Mutator includes only user-settable fields (excludes all generateAs)', () => {
|
|
160
|
+
const models = new Models(productModelDefinitions);
|
|
161
|
+
const output = generateDBModels(models, 'luxon');
|
|
162
|
+
|
|
163
|
+
const start = output.indexOf('export type ProductMutator');
|
|
164
|
+
const end = output.indexOf('export type', start + 1);
|
|
165
|
+
const block = end === -1 ? output.slice(start) : output.slice(start, end);
|
|
166
|
+
expect(block).toContain("'price'");
|
|
167
|
+
expect(block).toContain("'quantity'");
|
|
168
|
+
expect(block).not.toContain("'totalExpression'");
|
|
169
|
+
expect(block).not.toContain("'totalStored'");
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('Seed type excludes all generateAs fields', () => {
|
|
173
|
+
const models = new Models(productModelDefinitions);
|
|
174
|
+
const output = generateDBModels(models, 'luxon');
|
|
175
|
+
|
|
176
|
+
const start = output.indexOf('export type ProductSeed');
|
|
177
|
+
const end = output.indexOf('export type', start + 1);
|
|
178
|
+
const block = end === -1 ? output.slice(start) : output.slice(start, end);
|
|
179
|
+
expect(block).toContain("'price'");
|
|
180
|
+
expect(block).toContain("'quantity'");
|
|
181
|
+
expect(block).not.toContain("'totalExpression'");
|
|
182
|
+
expect(block).not.toContain("'totalStored'");
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe('generateDefinitions Create/Update inputs', () => {
|
|
187
|
+
const itemModelDefinitions: ModelDefinitions = [
|
|
188
|
+
{ kind: 'entity', name: 'User', fields: [{ name: 'id', type: 'ID' }] },
|
|
189
|
+
{
|
|
190
|
+
kind: 'entity',
|
|
191
|
+
name: 'Item',
|
|
192
|
+
creatable: true,
|
|
193
|
+
updatable: true,
|
|
194
|
+
fields: [
|
|
195
|
+
{ name: 'name', type: 'String', creatable: true, updatable: true },
|
|
196
|
+
{
|
|
197
|
+
name: 'computed',
|
|
198
|
+
type: 'Int',
|
|
199
|
+
creatable: true,
|
|
200
|
+
updatable: true,
|
|
201
|
+
generateAs: { expression: '1', type: 'expression' },
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
it('Create input does not include generateAs fields', () => {
|
|
208
|
+
const models = new Models(itemModelDefinitions);
|
|
209
|
+
const definitions = generateDefinitions(models);
|
|
210
|
+
|
|
211
|
+
const createInput = definitions.find(
|
|
212
|
+
(d) => d.kind === Kind.INPUT_OBJECT_TYPE_DEFINITION && d.name?.value === 'CreateItem',
|
|
213
|
+
);
|
|
214
|
+
expect(createInput).toBeDefined();
|
|
215
|
+
expect(createInput!.kind).toBe(Kind.INPUT_OBJECT_TYPE_DEFINITION);
|
|
216
|
+
|
|
217
|
+
const fields = (createInput as { fields?: readonly { name: { value: string } }[] }).fields ?? [];
|
|
218
|
+
const fieldNames = fields.map((f) => f.name.value);
|
|
219
|
+
expect(fieldNames).toContain('name');
|
|
220
|
+
expect(fieldNames).not.toContain('computed');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('Update input does not include generateAs fields', () => {
|
|
224
|
+
const models = new Models(itemModelDefinitions);
|
|
225
|
+
const definitions = generateDefinitions(models);
|
|
226
|
+
|
|
227
|
+
const updateInput = definitions.find(
|
|
228
|
+
(d) => d.kind === Kind.INPUT_OBJECT_TYPE_DEFINITION && d.name?.value === 'UpdateItem',
|
|
229
|
+
);
|
|
230
|
+
expect(updateInput).toBeDefined();
|
|
231
|
+
expect(updateInput!.kind).toBe(Kind.INPUT_OBJECT_TYPE_DEFINITION);
|
|
232
|
+
|
|
233
|
+
const fields = (updateInput as { fields?: readonly { name: { value: string } }[] }).fields ?? [];
|
|
234
|
+
const fieldNames = fields.map((f) => f.name.value);
|
|
235
|
+
expect(fieldNames).toContain('name');
|
|
236
|
+
expect(fieldNames).not.toContain('computed');
|
|
237
|
+
});
|
|
238
|
+
});
|