@smartive/graphql-magic 23.6.1-next.2 → 23.7.0-next.1

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 (46) hide show
  1. package/.gqmrc.json +2 -1
  2. package/CHANGELOG.md +3 -3
  3. package/dist/bin/gqm.cjs +278 -115
  4. package/dist/cjs/index.cjs +288 -125
  5. package/dist/esm/db/generate.js +5 -5
  6. package/dist/esm/db/generate.js.map +1 -1
  7. package/dist/esm/migrations/generate.d.ts +13 -1
  8. package/dist/esm/migrations/generate.js +197 -41
  9. package/dist/esm/migrations/generate.js.map +1 -1
  10. package/dist/esm/migrations/index.d.ts +2 -1
  11. package/dist/esm/migrations/index.js +2 -1
  12. package/dist/esm/migrations/index.js.map +1 -1
  13. package/dist/esm/models/model-definitions.d.ts +27 -2
  14. package/dist/esm/models/models.d.ts +1 -5
  15. package/dist/esm/models/models.js +4 -1
  16. package/dist/esm/models/models.js.map +1 -1
  17. package/dist/esm/models/utils.d.ts +16 -7
  18. package/dist/esm/models/utils.js +16 -6
  19. package/dist/esm/models/utils.js.map +1 -1
  20. package/dist/esm/permissions/check.js +2 -2
  21. package/dist/esm/permissions/check.js.map +1 -1
  22. package/dist/esm/resolvers/mutations.js +6 -6
  23. package/dist/esm/resolvers/mutations.js.map +1 -1
  24. package/dist/esm/resolvers/resolvers.js +3 -9
  25. package/dist/esm/resolvers/resolvers.js.map +1 -1
  26. package/dist/esm/schema/generate.js +3 -9
  27. package/dist/esm/schema/generate.js.map +1 -1
  28. package/docker-compose.yml +2 -3
  29. package/docs/docs/2-models.md +18 -4
  30. package/docs/docs/5-migrations.md +11 -5
  31. package/package.json +3 -3
  32. package/src/bin/gqm/parse-knexfile.ts +1 -0
  33. package/src/bin/gqm/settings.ts +4 -0
  34. package/src/db/generate.ts +5 -15
  35. package/src/migrations/generate.ts +257 -42
  36. package/src/migrations/index.ts +2 -1
  37. package/src/models/model-definitions.ts +20 -1
  38. package/src/models/models.ts +4 -1
  39. package/src/models/utils.ts +27 -8
  40. package/src/permissions/check.ts +2 -2
  41. package/src/resolvers/mutations.ts +6 -6
  42. package/src/resolvers/resolvers.ts +7 -13
  43. package/src/schema/generate.ts +28 -26
  44. package/tests/unit/constraints.spec.ts +98 -2
  45. package/tests/unit/generate-as.spec.ts +6 -6
  46. package/tests/utils/functions.ts +1 -0
@@ -1,6 +1,14 @@
1
1
  import { DefinitionNode, DocumentNode, GraphQLSchema, buildASTSchema, print } from 'graphql';
2
2
  import { Models } from '../models/models';
3
- import { isGenerateAsField, isQueriableField, isRootModel, not, typeToField } from '../models/utils';
3
+ import {
4
+ and,
5
+ isCreatable,
6
+ isQueriableField,
7
+ isRootModel,
8
+ isStoredInDatabase,
9
+ isUpdatable,
10
+ typeToField,
11
+ } from '../models/utils';
4
12
  import { Field, document, enm, iface, input, object, scalar, union } from './utils';
5
13
 
6
14
  export const generateDefinitions = ({
@@ -133,19 +141,16 @@ export const generateDefinitions = ({
133
141
  types.push(
134
142
  input(
135
143
  `Create${model.name}`,
136
- model.fields
137
- .filter(({ creatable }) => creatable)
138
- .filter(not(isGenerateAsField))
139
- .map((field) =>
140
- field.kind === 'relation'
141
- ? { name: `${field.name}Id`, type: 'ID', nonNull: field.nonNull }
142
- : {
143
- name: field.name,
144
- type: field.kind === 'json' ? `Create${field.type}` : field.type,
145
- list: field.list,
146
- nonNull: field.nonNull && field.defaultValue === undefined,
147
- },
148
- ),
144
+ model.fields.filter(and(isCreatable, isStoredInDatabase)).map((field) =>
145
+ field.kind === 'relation'
146
+ ? { name: `${field.name}Id`, type: 'ID', nonNull: field.nonNull }
147
+ : {
148
+ name: field.name,
149
+ type: field.kind === 'json' ? `Create${field.type}` : field.type,
150
+ list: field.list,
151
+ nonNull: field.nonNull && field.defaultValue === undefined,
152
+ },
153
+ ),
149
154
  ),
150
155
  );
151
156
  }
@@ -154,18 +159,15 @@ export const generateDefinitions = ({
154
159
  types.push(
155
160
  input(
156
161
  `Update${model.name}`,
157
- model.fields
158
- .filter(({ updatable }) => updatable)
159
- .filter(not(isGenerateAsField))
160
- .map((field) =>
161
- field.kind === 'relation'
162
- ? { name: `${field.name}Id`, type: 'ID' }
163
- : {
164
- name: field.name,
165
- type: field.kind === 'json' ? `Update${field.type}` : field.type,
166
- list: field.list,
167
- },
168
- ),
162
+ model.fields.filter(and(isUpdatable, isStoredInDatabase)).map((field) =>
163
+ field.kind === 'relation'
164
+ ? { name: `${field.name}Id`, type: 'ID' }
165
+ : {
166
+ name: field.name,
167
+ type: field.kind === 'json' ? `Update${field.type}` : field.type,
168
+ list: field.list,
169
+ },
170
+ ),
169
171
  ),
170
172
  );
171
173
  }
@@ -1,7 +1,11 @@
1
1
  import { ModelDefinitions, Models } from '../../src/models';
2
- import { extractColumnReferencesFromCheckExpression, validateCheckConstraint } from '../../src/models/utils';
2
+ import {
3
+ extractColumnReferencesFromCheckExpression,
4
+ validateCheckConstraint,
5
+ validateExcludeConstraint,
6
+ } from '../../src/models/utils';
3
7
 
4
- describe('check constraints', () => {
8
+ describe('constraints', () => {
5
9
  const modelDefinitions: ModelDefinitions = [
6
10
  {
7
11
  kind: 'entity',
@@ -80,4 +84,96 @@ describe('check constraints', () => {
80
84
  ).toThrow(/Valid columns:.*\bscore\b.*\bstatus\b/);
81
85
  });
82
86
  });
87
+
88
+ describe('validateExcludeConstraint', () => {
89
+ it('does not throw when column elements reference valid columns', () => {
90
+ expect(() =>
91
+ validateExcludeConstraint(productModel, {
92
+ name: 'valid',
93
+ elements: [
94
+ { column: 'score', operator: '=' },
95
+ { expression: 'tsrange("startDate", "endDate")', operator: '&&' },
96
+ ],
97
+ }),
98
+ ).not.toThrow();
99
+ });
100
+
101
+ it('does not throw when only expression elements', () => {
102
+ expect(() =>
103
+ validateExcludeConstraint(productModel, {
104
+ name: 'valid',
105
+ elements: [{ expression: 'tsrange(now(), now())', operator: '&&' }],
106
+ }),
107
+ ).not.toThrow();
108
+ });
109
+
110
+ it('uses relation column name when validating', () => {
111
+ expect(() =>
112
+ validateExcludeConstraint(productModel, {
113
+ name: 'valid',
114
+ elements: [{ column: 'parentId', operator: '=' }],
115
+ }),
116
+ ).not.toThrow();
117
+ });
118
+
119
+ it('throws when column element references missing column', () => {
120
+ expect(() =>
121
+ validateExcludeConstraint(productModel, {
122
+ name: 'bad',
123
+ elements: [{ column: 'unknown_column', operator: '=' }],
124
+ }),
125
+ ).toThrow(
126
+ /Exclude constraint "bad" references column "unknown_column" which does not exist on model Product/,
127
+ );
128
+ });
129
+ });
130
+
131
+ describe('exclude and constraint_trigger constraints', () => {
132
+ const allocationDefinitions: ModelDefinitions = [
133
+ {
134
+ kind: 'entity',
135
+ name: 'PortfolioAllocation',
136
+ fields: [
137
+ { name: 'portfolio', kind: 'relation', type: 'Portfolio', reverse: 'allocations' },
138
+ { name: 'startDate', type: 'DateTime' },
139
+ { name: 'endDate', type: 'DateTime' },
140
+ { name: 'deleted', type: 'Boolean', nonNull: true, defaultValue: false },
141
+ ],
142
+ constraints: [
143
+ {
144
+ kind: 'exclude',
145
+ name: 'no_overlap_per_portfolio',
146
+ using: 'gist',
147
+ elements: [
148
+ { column: 'portfolioId', operator: '=' },
149
+ {
150
+ expression: 'tsrange("startDate", COALESCE("endDate", \'infinity\'::timestamptz))',
151
+ operator: '&&',
152
+ },
153
+ ],
154
+ where: '"deleted" = false',
155
+ deferrable: 'INITIALLY DEFERRED',
156
+ },
157
+ {
158
+ kind: 'constraint_trigger',
159
+ name: 'contiguous_periods',
160
+ when: 'AFTER',
161
+ events: ['INSERT', 'UPDATE'],
162
+ forEach: 'ROW',
163
+ deferrable: 'INITIALLY DEFERRED',
164
+ function: { name: 'contiguous_periods_check' },
165
+ },
166
+ ],
167
+ },
168
+ {
169
+ kind: 'entity',
170
+ name: 'Portfolio',
171
+ fields: [{ name: 'name', type: 'String' }],
172
+ },
173
+ ];
174
+
175
+ it('constructs models with exclude and constraint_trigger without throwing', () => {
176
+ expect(() => new Models(allocationDefinitions)).not.toThrow();
177
+ });
178
+ });
83
179
  });
@@ -1,7 +1,7 @@
1
1
  import { Kind } from 'graphql';
2
2
  import { generateDBModels } from '../../src/db/generate';
3
3
  import {
4
- isGenerateAsField,
4
+ isDynamicField,
5
5
  isStoredInDatabase,
6
6
  } from '../../src/models/utils';
7
7
  import { ModelDefinitions, Models } from '../../src/models';
@@ -42,10 +42,10 @@ jest.mock('code-block-writer', () => {
42
42
  });
43
43
 
44
44
  describe('generateAs helpers', () => {
45
- describe('isGenerateAsField', () => {
45
+ describe('isDynamicField', () => {
46
46
  it('returns false when field.generateAs is undefined', () => {
47
47
  const field: EntityField = { name: 'price', type: 'Float' };
48
- expect(isGenerateAsField(field)).toBe(false);
48
+ expect(isDynamicField(field)).toBe(false);
49
49
  });
50
50
 
51
51
  it('returns true when field has generateAs with type expression', () => {
@@ -54,7 +54,7 @@ describe('generateAs helpers', () => {
54
54
  type: 'Float',
55
55
  generateAs: { expression: 'price * quantity', type: 'expression' },
56
56
  };
57
- expect(isGenerateAsField(field)).toBe(true);
57
+ expect(isDynamicField(field)).toBe(true);
58
58
  });
59
59
 
60
60
  it('returns true when field has generateAs with type stored', () => {
@@ -63,7 +63,7 @@ describe('generateAs helpers', () => {
63
63
  type: 'Float',
64
64
  generateAs: { expression: 'price * quantity', type: 'stored' },
65
65
  };
66
- expect(isGenerateAsField(field)).toBe(true);
66
+ expect(isDynamicField(field)).toBe(true);
67
67
  });
68
68
 
69
69
  it('returns true when field has generateAs with type virtual', () => {
@@ -72,7 +72,7 @@ describe('generateAs helpers', () => {
72
72
  type: 'Float',
73
73
  generateAs: { expression: 'price * quantity', type: 'virtual' },
74
74
  };
75
- expect(isGenerateAsField(field)).toBe(true);
75
+ expect(isDynamicField(field)).toBe(true);
76
76
  });
77
77
  });
78
78
 
@@ -0,0 +1 @@
1
+ export const functions: string[] = [];