@aws-amplify/graphql-model-transformer 0.14.6 → 0.15.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.
@@ -6,7 +6,6 @@ const featureFlags = {
6
6
  getBoolean: jest.fn(),
7
7
  getNumber: jest.fn(),
8
8
  getObject: jest.fn(),
9
- getString: jest.fn(),
10
9
  };
11
10
 
12
11
  describe('ModelTransformer: ', () => {
@@ -19,7 +19,6 @@ const featureFlags = {
19
19
  getBoolean: jest.fn(),
20
20
  getNumber: jest.fn(),
21
21
  getObject: jest.fn(),
22
- getString: jest.fn(),
23
22
  };
24
23
 
25
24
  describe('ModelTransformer: ', () => {
@@ -5,6 +5,12 @@ export const FLOAT_CONDITIONS = ['ne', 'eq', 'le', 'lt', 'ge', 'gt', 'between'];
5
5
  export const BOOLEAN_CONDITIONS = ['ne', 'eq'];
6
6
  export const SIZE_CONDITIONS = ['ne', 'eq', 'le', 'lt', 'ge', 'gt', 'between'];
7
7
 
8
+ export const SUBSCRIPTION_STRING_CONDITIONS = ['ne', 'eq', 'le', 'lt', 'ge', 'gt', 'contains', 'notContains', 'between', 'beginsWith', 'in', 'notIn'];
9
+ export const SUBSCRIPTION_ID_CONDITIONS = ['ne', 'eq', 'le', 'lt', 'ge', 'gt', 'contains', 'notContains', 'between', 'beginsWith', 'in', 'notIn'];
10
+ export const SUBSCRIPTION_INT_CONDITIONS = ['ne', 'eq', 'le', 'lt', 'ge', 'gt', 'between', 'in', 'notIn'];
11
+ export const SUBSCRIPTION_FLOAT_CONDITIONS = ['ne', 'eq', 'le', 'lt', 'ge', 'gt', 'between', 'in', 'notIn'];
12
+ export const SUBSCRIPTION_BOOLEAN_CONDITIONS = ['ne', 'eq'];
13
+
8
14
  export const STRING_FUNCTIONS = new Set<string>(['attributeExists', 'attributeType', 'size']);
9
15
  export const ID_FUNCTIONS = new Set<string>(['attributeExists', 'attributeType', 'size']);
10
16
  export const INT_FUNCTIONS = new Set<string>(['attributeExists', 'attributeType']);
@@ -66,6 +66,7 @@ import {
66
66
  makeCreateInputField,
67
67
  makeDeleteInputField,
68
68
  makeListQueryFilterInput,
69
+ makeSubscriptionQueryFilterInput,
69
70
  makeListQueryModel,
70
71
  makeModelSortDirectionEnumObject,
71
72
  makeMutationConditionInput,
@@ -756,8 +757,8 @@ export class ModelTransformer extends TransformerModelBase implements Transforme
756
757
  const maps = subscriptionToMutationsMap[subscriptionFieldName];
757
758
 
758
759
  const args: InputValueDefinitionNode[] = [];
759
- maps.map(it => args.concat(
760
- this.getInputs(ctx, def!, {
760
+ maps.map(it => args.push(
761
+ ...this.getInputs(ctx, def!, {
761
762
  fieldName: it.fieldName,
762
763
  typeName: it.typeName,
763
764
  type: it.type,
@@ -978,7 +979,19 @@ export class ModelTransformer extends TransformerModelBase implements Transforme
978
979
  case SubscriptionFieldType.ON_CREATE:
979
980
  case SubscriptionFieldType.ON_DELETE:
980
981
  case SubscriptionFieldType.ON_UPDATE:
981
- return [];
982
+ const filterInputName = toPascalCase(['ModelSubscription', type.name.value, 'FilterInput']);
983
+ const filterInputs = createEnumModelFilters(ctx, type);
984
+ filterInputs.push(makeSubscriptionQueryFilterInput(ctx, filterInputName, type));
985
+ filterInputs.forEach(input => {
986
+ const conditionInputName = input.name.value;
987
+ if (!ctx.output.getType(conditionInputName)) {
988
+ ctx.output.addInput(input);
989
+ }
990
+ });
991
+
992
+ return [
993
+ makeInputValueDefinition('filter', makeNamedType(filterInputName)),
994
+ ];
982
995
 
983
996
  default:
984
997
  throw new Error('Unknown operation type');
@@ -21,7 +21,15 @@ import {
21
21
  makeNamedType,
22
22
  makeValueNode,
23
23
  ModelResourceIDs,
24
+ toPascalCase,
24
25
  } from 'graphql-transformer-common';
26
+ import {
27
+ EnumWrapper,
28
+ FieldWrapper,
29
+ InputFieldWrapper,
30
+ InputObjectDefinitionWrapper,
31
+ ObjectDefinitionWrapper,
32
+ } from '@aws-amplify/graphql-transformer-core';
25
33
  import {
26
34
  ATTRIBUTE_TYPES,
27
35
  BOOLEAN_CONDITIONS,
@@ -36,14 +44,12 @@ import {
36
44
  STRING_CONDITIONS,
37
45
  STRING_FUNCTIONS,
38
46
  API_KEY_DIRECTIVE,
47
+ SUBSCRIPTION_STRING_CONDITIONS,
48
+ SUBSCRIPTION_ID_CONDITIONS,
49
+ SUBSCRIPTION_INT_CONDITIONS,
50
+ SUBSCRIPTION_FLOAT_CONDITIONS,
51
+ SUBSCRIPTION_BOOLEAN_CONDITIONS,
39
52
  } from '../definitions';
40
- import {
41
- EnumWrapper,
42
- FieldWrapper,
43
- InputFieldWrapper,
44
- InputObjectDefinitionWrapper,
45
- ObjectDefinitionWrapper,
46
- } from '@aws-amplify/graphql-transformer-core';
47
53
 
48
54
  /**
49
55
  * Creates the condition/filter input for a model
@@ -59,36 +65,96 @@ export const makeConditionFilterInput = (
59
65
  const supportsConditions = true;
60
66
  const input = InputObjectDefinitionWrapper.create(name);
61
67
  const wrappedObject = new ObjectDefinitionWrapper(object);
62
- for (let field of wrappedObject.fields) {
68
+ for (const field of wrappedObject.fields) {
63
69
  const fieldType = ctx.output.getType(field.getTypeName());
64
70
  const isEnumType = fieldType && fieldType.kind === Kind.ENUM_TYPE_DEFINITION;
65
71
  if (field.isScalar() || isEnumType) {
66
- const conditionTypeName =
67
- isEnumType && field.isList()
68
- ? ModelResourceIDs.ModelFilterListInputTypeName(field.getTypeName(), !supportsConditions)
69
- : ModelResourceIDs.ModelFilterScalarInputTypeName(field.getTypeName(), !supportsConditions);
72
+ const conditionTypeName = isEnumType && field.isList()
73
+ ? ModelResourceIDs.ModelFilterListInputTypeName(field.getTypeName(), !supportsConditions)
74
+ : ModelResourceIDs.ModelFilterScalarInputTypeName(field.getTypeName(), !supportsConditions);
70
75
  const inputField = InputFieldWrapper.create(field.name, conditionTypeName, true);
71
76
  input.addField(inputField);
72
77
  }
73
78
  }
74
79
 
75
80
  // additional conditions of list type
76
- for (let additionalField of ['and', 'or']) {
81
+ for (const additionalField of ['and', 'or']) {
77
82
  const inputField = InputFieldWrapper.create(additionalField, name, true, true);
78
83
  input.addField(inputField);
79
84
  }
80
85
  // additional conditions of non-list type
81
- for (let additionalField of ['not']) {
86
+ for (const additionalField of ['not']) {
82
87
  const inputField = InputFieldWrapper.create(additionalField, name, true, false);
83
88
  input.addField(inputField);
84
89
  }
85
90
  return input;
86
91
  };
87
92
 
93
+ /**
94
+ * Generates subscription filter input type
95
+ */
96
+ export const makeSubscriptionFilterInput = (
97
+ ctx: TransformerTransformSchemaStepContextProvider,
98
+ name: string,
99
+ object: ObjectTypeDefinitionNode,
100
+ ): InputObjectDefinitionWrapper => {
101
+ const supportsConditions = true;
102
+ const input = InputObjectDefinitionWrapper.create(name);
103
+ const wrappedObject = new ObjectDefinitionWrapper(object);
104
+ for (const field of wrappedObject.fields) {
105
+ const fieldType = ctx.output.getType(field.getTypeName());
106
+ const isEnumType = fieldType && fieldType.kind === Kind.ENUM_TYPE_DEFINITION;
107
+ if (field.isScalar() || isEnumType) {
108
+ const conditionTypeName = ModelResourceIDs.ModelFilterScalarInputTypeName(
109
+ isEnumType ? 'String' : field.getTypeName(),
110
+ !supportsConditions,
111
+ true,
112
+ );
113
+ const inputField = InputFieldWrapper.create(field.name, conditionTypeName, true);
114
+ input.addField(inputField);
115
+ }
116
+ }
117
+
118
+ // additional conditions of list type
119
+ for (const additionalField of ['and', 'or']) {
120
+ const inputField = InputFieldWrapper.create(additionalField, name, true, true);
121
+ input.addField(inputField);
122
+ }
123
+
124
+ return input;
125
+ };
126
+
127
+ /**
128
+ * Generates the Subscription filter input type name
129
+ */
130
+ export const getSubscriptionFilterInputName = (name: string): string => toPascalCase(['ModelSubscription', name, 'FilterInput']);
131
+
132
+ /**
133
+ * Removes the given attribute from the subscription filter input type
134
+ */
135
+ export const removeSubscriptionFilterInputAttribute = (
136
+ ctx: TransformerTransformSchemaStepContextProvider,
137
+ typeName: string,
138
+ fieldName: string,
139
+ ): void => {
140
+ const filterTypeName = getSubscriptionFilterInputName(typeName);
141
+ const filterType = ctx.output.getType(filterTypeName) as InputObjectTypeDefinitionNode;
142
+ if (!filterType) {
143
+ return;
144
+ }
145
+ const newFilterType: InputObjectTypeDefinitionNode = {
146
+ ...filterType,
147
+ fields: filterType.fields?.filter(field => field.name.value !== fieldName),
148
+ };
149
+ ctx.output.putType(newFilterType);
150
+ };
151
+
152
+ /**
153
+ * Generates model condition input type
154
+ */
88
155
  export const addModelConditionInputs = (ctx: TransformerTransformSchemaStepContextProvider): void => {
89
- const conditionsInput: TypeDefinitionNode[] = ['String', 'Int', 'Float', 'Boolean', 'ID'].map(scalarName =>
90
- makeModelScalarFilterInputObject(scalarName, true),
91
- );
156
+ const conditionsInput: TypeDefinitionNode[] = ['String', 'Int', 'Float', 'Boolean', 'ID'].map(scalarName => makeModelScalarFilterInputObject(scalarName, true));
157
+ ['String', 'Int', 'Float', 'Boolean', 'ID'].map(scalarName => conditionsInput.push(makeModelScalarFilterInputObject(scalarName, true, true)));
92
158
  conditionsInput.push(makeAttributeTypeEnum());
93
159
  conditionsInput.push(makeSizeInputType());
94
160
  conditionsInput.forEach(input => {
@@ -104,14 +170,17 @@ export const addModelConditionInputs = (ctx: TransformerTransformSchemaStepConte
104
170
  * @param typeName Name of the scalar type
105
171
  * @param includeFilter add filter suffix to input
106
172
  */
107
- export function generateModelScalarFilterInputName(typeName: string, includeFilter: boolean): string {
173
+ export function generateModelScalarFilterInputName(typeName: string, includeFilter: boolean, isSubscriptionFilter = false): string {
108
174
  const nameOverride = DEFAULT_SCALARS[typeName];
109
175
  if (nameOverride) {
110
- return `Model${nameOverride}${includeFilter ? 'Filter' : ''}Input`;
176
+ return `Model${isSubscriptionFilter ? 'Subscription' : ''}${nameOverride}${includeFilter ? 'Filter' : ''}Input`;
111
177
  }
112
- return `Model${typeName}${includeFilter ? 'Filter' : ''}Input`;
178
+ return `Model${isSubscriptionFilter ? 'Subscription' : ''}${typeName}${includeFilter ? 'Filter' : ''}Input`;
113
179
  }
114
180
 
181
+ /**
182
+ * Creates Enum Model Filters
183
+ */
115
184
  export const createEnumModelFilters = (
116
185
  ctx: TransformerTransformSchemaStepContextProvider,
117
186
  type: ObjectTypeDefinitionNode,
@@ -132,11 +201,15 @@ export const createEnumModelFilters = (
132
201
  * @param type scalar type name
133
202
  * @param supportsConditions add filter suffix to input
134
203
  */
135
- export function makeModelScalarFilterInputObject(type: string, supportsConditions: boolean): InputObjectTypeDefinitionNode {
136
- const name = generateModelScalarFilterInputName(type, !supportsConditions);
137
- const conditions = getScalarConditions(type);
204
+ export function makeModelScalarFilterInputObject(
205
+ type: string,
206
+ supportsConditions: boolean,
207
+ isSubscriptionFilter = false,
208
+ ): InputObjectTypeDefinitionNode {
209
+ const name = generateModelScalarFilterInputName(type, !supportsConditions, isSubscriptionFilter);
210
+ const conditions = isSubscriptionFilter ? getSubscriptionScalarConditions(type) : getScalarConditions(type);
138
211
  const scalarConditionInput = InputObjectDefinitionWrapper.create(name);
139
- for (let condition of conditions) {
212
+ for (const condition of conditions) {
140
213
  let typeName;
141
214
  switch (condition) {
142
215
  case 'and':
@@ -147,12 +220,16 @@ export function makeModelScalarFilterInputObject(type: string, supportsCondition
147
220
  typeName = type;
148
221
  }
149
222
  const field = InputFieldWrapper.create(condition, typeName, true);
150
- if (condition === 'between') {
223
+ if (condition === 'between' || condition === 'in' || condition === 'notIn') {
151
224
  field.wrapListType();
152
225
  }
153
226
  scalarConditionInput.addField(field);
154
227
  }
155
- makeFunctionInputFields(type).map(f => scalarConditionInput.addField(f));
228
+
229
+ if (!isSubscriptionFilter) {
230
+ makeFunctionInputFields(type).map(f => scalarConditionInput.addField(f));
231
+ }
232
+
156
233
  return scalarConditionInput.serialize();
157
234
  }
158
235
 
@@ -173,6 +250,23 @@ function getScalarConditions(type: string): string[] {
173
250
  }
174
251
  }
175
252
 
253
+ function getSubscriptionScalarConditions(type: string): string[] {
254
+ switch (type) {
255
+ case 'String':
256
+ return SUBSCRIPTION_STRING_CONDITIONS;
257
+ case 'ID':
258
+ return SUBSCRIPTION_ID_CONDITIONS;
259
+ case 'Int':
260
+ return SUBSCRIPTION_INT_CONDITIONS;
261
+ case 'Float':
262
+ return SUBSCRIPTION_FLOAT_CONDITIONS;
263
+ case 'Boolean':
264
+ return SUBSCRIPTION_BOOLEAN_CONDITIONS;
265
+ default:
266
+ throw new Error('Valid types are String, ID, Int, Float, Boolean');
267
+ }
268
+ }
269
+
176
270
  function getFunctionListForType(typeName: string): Set<string> {
177
271
  switch (typeName) {
178
272
  case 'String':
@@ -209,21 +303,30 @@ function makeFunctionInputFields(typeName: string): InputFieldWrapper[] {
209
303
  return fields;
210
304
  }
211
305
 
306
+ /**
307
+ * Makes Attribute Type Enum
308
+ */
212
309
  export function makeAttributeTypeEnum(): EnumTypeDefinitionNode {
213
310
  return EnumWrapper.create('ModelAttributeTypes', ATTRIBUTE_TYPES).serialize();
214
311
  }
215
312
 
313
+ /**
314
+ * Makes subscription field
315
+ */
216
316
  export function makeSubscriptionField(fieldName: string, returnTypeName: string, mutations: string[]): FieldDefinitionNode {
217
317
  return makeField(fieldName, [], makeNamedType(returnTypeName), [
218
318
  makeDirective('aws_subscribe', [makeArgument('mutations', makeValueNode(mutations))]),
219
319
  ]);
220
320
  }
221
321
 
322
+ /**
323
+ * Makes Input Type size
324
+ */
222
325
  export function makeSizeInputType(): InputObjectTypeDefinitionNode {
223
326
  const name = 'ModelSizeInput';
224
327
  const input = InputObjectDefinitionWrapper.create(name);
225
328
 
226
- for (let condition of SIZE_CONDITIONS) {
329
+ for (const condition of SIZE_CONDITIONS) {
227
330
  const field = InputFieldWrapper.create(condition, 'Int', true);
228
331
  if (condition === 'between') field.wrapListType();
229
332
  input.addField(field);
@@ -231,6 +334,9 @@ export function makeSizeInputType(): InputObjectTypeDefinitionNode {
231
334
  return input.serialize();
232
335
  }
233
336
 
337
+ /**
338
+ * Makes enum filter input
339
+ */
234
340
  export function makeEnumFilterInput(fieldWrapper: FieldWrapper): InputObjectTypeDefinitionNode {
235
341
  const supportsConditions = true;
236
342
  const conditionTypeName = fieldWrapper.isList()
@@ -252,12 +358,15 @@ export function makeEnumFilterInput(fieldWrapper: FieldWrapper): InputObjectType
252
358
  return input.serialize();
253
359
  }
254
360
 
361
+ /**
362
+ * Adds the directive to the field
363
+ */
255
364
  export const addDirectivesToField = (
256
365
  ctx: TransformerTransformSchemaStepContextProvider,
257
366
  typeName: string,
258
367
  fieldName: string,
259
368
  directives: Array<DirectiveNode>,
260
- ) => {
369
+ ): void => {
261
370
  const type = ctx.output.getType(typeName) as ObjectTypeDefinitionNode;
262
371
  if (type) {
263
372
  const field = type.fields?.find(f => f.name.value === fieldName);
@@ -274,12 +383,15 @@ export const addDirectivesToField = (
274
383
  }
275
384
  };
276
385
 
386
+ /**
387
+ * Adds directives to operation
388
+ */
277
389
  export const addDirectivesToOperation = (
278
390
  ctx: TransformerTransformSchemaStepContextProvider,
279
391
  typeName: string,
280
392
  operationName: string,
281
393
  directives: Array<DirectiveNode>,
282
- ) => {
394
+ ): void => {
283
395
  // add directives to the given operation
284
396
  addDirectivesToField(ctx, typeName, operationName, directives);
285
397
 
@@ -300,6 +412,9 @@ export const addDirectivesToOperation = (
300
412
  }
301
413
  };
302
414
 
415
+ /**
416
+ * Extends type with directives
417
+ */
303
418
  export const extendTypeWithDirectives = (
304
419
  ctx: TransformerTransformSchemaStepContextProvider,
305
420
  typeName: string,
@@ -310,17 +425,23 @@ export const extendTypeWithDirectives = (
310
425
  ctx.output.addObjectExtension(objectTypeExtension);
311
426
  };
312
427
 
313
- export function makeModelSortDirectionEnumObject(): EnumTypeDefinitionNode {
428
+ /**
429
+ * Makes model sort direction enum object
430
+ */
431
+ export const makeModelSortDirectionEnumObject = (): EnumTypeDefinitionNode => {
314
432
  const name = 'ModelSortDirection';
315
433
  return EnumWrapper.create(name, ['ASC', 'DESC']).serialize();
316
- }
434
+ };
317
435
  // the smaller version of it's @auth equivalent since we only support
318
436
  // apikey as the only global auth rule
437
+ /**
438
+ * Propagates api key to nested types
439
+ */
319
440
  export const propagateApiKeyToNestedTypes = (
320
441
  ctx: TransformerContextProvider,
321
442
  def: ObjectTypeDefinitionNode,
322
443
  seenNonModelTypes: Set<string>,
323
- ) => {
444
+ ): void => {
324
445
  const nonModelTypePredicate = (fieldType: TypeDefinitionNode): TypeDefinitionNode | undefined => {
325
446
  if (fieldType) {
326
447
  if (fieldType.kind !== 'ObjectTypeDefinition') {
@@ -1,7 +1,7 @@
1
1
  import { TransformerTransformSchemaStepContextProvider } from '@aws-amplify/graphql-transformer-interfaces';
2
2
  import { InputObjectTypeDefinitionNode, ObjectTypeDefinitionNode } from 'graphql';
3
3
  import { FieldWrapper, ObjectDefinitionWrapper } from '@aws-amplify/graphql-transformer-core';
4
- import { makeConditionFilterInput } from './common';
4
+ import { makeConditionFilterInput, makeSubscriptionFilterInput } from './common';
5
5
  export const makeListQueryFilterInput = (
6
6
  ctx: TransformerTransformSchemaStepContextProvider,
7
7
  name: string,
@@ -10,6 +10,14 @@ export const makeListQueryFilterInput = (
10
10
  return makeConditionFilterInput(ctx, name, object).serialize();
11
11
  };
12
12
 
13
+ export const makeSubscriptionQueryFilterInput = (
14
+ ctx: TransformerTransformSchemaStepContextProvider,
15
+ name: string,
16
+ object: ObjectTypeDefinitionNode,
17
+ ): InputObjectTypeDefinitionNode => {
18
+ return makeSubscriptionFilterInput(ctx, name, object).serialize();
19
+ };
20
+
13
21
  export const makeListQueryModel = (type: ObjectTypeDefinitionNode, modelName: string, isSyncEnabled: boolean): ObjectTypeDefinitionNode => {
14
22
  const outputType = ObjectDefinitionWrapper.create(modelName);
15
23
 
@@ -1,11 +1,25 @@
1
- import { compoundExpression, Expression, obj, printBlock, str, toJson, nul } from 'graphql-mapping-template';
1
+ import {
2
+ compoundExpression, Expression, obj, printBlock, str, toJson, nul, iff, not, isNullOrEmpty, ref,
3
+ } from 'graphql-mapping-template';
2
4
 
5
+ /**
6
+ * Generates subscription request template
7
+ */
3
8
  export const generateSubscriptionRequestTemplate = (): string => {
4
9
  const statements: Expression[] = [toJson(obj({ version: str('2018-05-29'), payload: obj({}) }))];
5
10
  return printBlock('Subscription Request template')(compoundExpression(statements));
6
11
  };
7
12
 
13
+ /**
14
+ * Generates subscription response template
15
+ */
8
16
  export const generateSubscriptionResponseTemplate = (): string => {
9
- const statements: Expression[] = [toJson(nul())];
17
+ const statements: Expression[] = [
18
+ iff(
19
+ not(isNullOrEmpty(ref('ctx.args.filter'))),
20
+ ref('extensions.setSubscriptionFilter($util.transform.toSubscriptionFilter($ctx.args.filter))'),
21
+ ),
22
+ toJson(nul()),
23
+ ];
10
24
  return printBlock('Subscription Response template')(compoundExpression(statements));
11
25
  };