@aws-amplify/data-schema 1.1.1 → 1.1.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.
@@ -157,29 +157,48 @@ function transformFunctionHandler(handlers, functionFieldName) {
157
157
  function customOperationToGql(typeName, typeDef, authorization, isCustom = false, databaseType, getRefType) {
158
158
  const { arguments: fieldArgs, typeName: opType, returnType, handlers, subscriptionSource, } = typeDef.data;
159
159
  let callSignature = typeName;
160
- const implicitModels = [];
160
+ const implicitTypes = [];
161
+ // When Custom Operations are defined with a Custom Type return type,
162
+ // the Custom Type inherits the operation's auth rules
163
+ let customTypeAuthRules = undefined;
161
164
  const { authString } = isCustom
162
- ? calculateCustomAuth(authorization)
165
+ ? mapToNativeAppSyncAuthDirectives(authorization, true)
163
166
  : calculateAuth(authorization);
164
167
  /**
165
168
  *
166
169
  * @param returnType The return type from the `data` field of a customer operation.
167
170
  * @param refererTypeName The type the refers {@link returnType} by `a.ref()`.
168
- * @param shouldAddCustomTypeToImplicitModels A flag indicates wether it should push
169
- * the return type resolved CustomType to the `implicitModels` list.
171
+ * @param shouldAddCustomTypeToImplicitTypes A flag indicates wether it should push
172
+ * the return type resolved CustomType to the `implicitTypes` list.
170
173
  * @returns
171
174
  */
172
- const resolveReturnTypeNameFromReturnType = (returnType, { refererTypeName, shouldAddCustomTypeToImplicitModels = true, }) => {
175
+ const resolveReturnTypeNameFromReturnType = (returnType, { refererTypeName, shouldAddCustomTypeToImplicitTypes = true, }) => {
173
176
  if (isRefField(returnType)) {
177
+ const { type } = getRefType(returnType.data.link, typeName);
178
+ if (type === 'CustomType') {
179
+ customTypeAuthRules = {
180
+ typeName: returnType.data.link,
181
+ authRules: authorization,
182
+ };
183
+ }
174
184
  return refFieldToGql(returnType?.data);
175
185
  }
176
186
  else if (isCustomType(returnType)) {
177
187
  const returnTypeName = `${capitalize(refererTypeName)}ReturnType`;
178
- if (shouldAddCustomTypeToImplicitModels) {
179
- implicitModels.push([returnTypeName, returnType]);
188
+ if (shouldAddCustomTypeToImplicitTypes) {
189
+ customTypeAuthRules = {
190
+ typeName: returnTypeName,
191
+ authRules: authorization,
192
+ };
193
+ implicitTypes.push([returnTypeName, returnType]);
180
194
  }
181
195
  return returnTypeName;
182
196
  }
197
+ else if (isEnumType(returnType)) {
198
+ const returnTypeName = `${capitalize(refererTypeName)}ReturnType`;
199
+ implicitTypes.push([returnTypeName, returnType]);
200
+ return returnTypeName;
201
+ }
183
202
  else if (isScalarField(returnType)) {
184
203
  return scalarFieldToGql(returnType?.data);
185
204
  }
@@ -195,7 +214,7 @@ function customOperationToGql(typeName, typeDef, authorization, isCustom = false
195
214
  if (type === 'CustomOperation') {
196
215
  returnTypeName = resolveReturnTypeNameFromReturnType(def.data.returnType, {
197
216
  refererTypeName: subscriptionSource[0].data.link,
198
- shouldAddCustomTypeToImplicitModels: false,
217
+ shouldAddCustomTypeToImplicitTypes: false,
199
218
  });
200
219
  }
201
220
  else {
@@ -208,9 +227,9 @@ function customOperationToGql(typeName, typeDef, authorization, isCustom = false
208
227
  });
209
228
  }
210
229
  if (Object.keys(fieldArgs).length > 0) {
211
- const { gqlFields, models } = processFields(typeName, fieldArgs, {}, {});
230
+ const { gqlFields, implicitTypes } = processFields(typeName, fieldArgs, {}, {});
212
231
  callSignature += `(${gqlFields.join(', ')})`;
213
- implicitModels.push(...models);
232
+ implicitTypes.push(...implicitTypes);
214
233
  }
215
234
  const handler = handlers && handlers[0];
216
235
  const brand = handler && getBrand(handler);
@@ -258,7 +277,8 @@ function customOperationToGql(typeName, typeDef, authorization, isCustom = false
258
277
  const gqlField = `${callSignature}: ${returnTypeName} ${gqlHandlerContent}${authString}`;
259
278
  return {
260
279
  gqlField,
261
- models: implicitModels,
280
+ implicitTypes: implicitTypes,
281
+ customTypeAuthRules,
262
282
  lambdaFunctionDefinition,
263
283
  customSqlDataSourceStrategy,
264
284
  };
@@ -439,23 +459,29 @@ function calculateAuth(authorization) {
439
459
  const authString = rules.length > 0 ? `@auth(rules: [${rules.join(',\n ')}])` : '';
440
460
  return { authString, authFields };
441
461
  }
442
- function validateCustomAuthRule(rule) {
462
+ function validateCustomHandlerAuthRule(rule) {
443
463
  if (rule.groups && rule.provider === 'oidc') {
444
464
  throw new Error('OIDC group auth is not supported with a.handler.custom');
445
465
  }
466
+ // not currently supported with handler.custom (JS Resolvers), but will be in the future
467
+ if (rule.provider === 'identityPool' || rule.provider === 'iam') {
468
+ throw new Error("identityPool-based auth (allow.guest() and allow.authenticated('identityPool')) is not supported with a.handler.custom");
469
+ }
446
470
  }
447
- function getCustomAuthProvider(rule) {
471
+ function getAppSyncAuthDirectiveFromRule(rule) {
448
472
  const strategyDict = {
449
473
  public: {
450
474
  default: '@aws_api_key',
451
475
  apiKey: '@aws_api_key',
452
476
  iam: '@aws_iam',
477
+ identityPool: '@aws_iam',
453
478
  },
454
479
  private: {
455
480
  default: '@aws_cognito_user_pools',
456
481
  userPools: '@aws_cognito_user_pools',
457
482
  oidc: '@aws_oidc',
458
483
  iam: '@aws_iam',
484
+ identityPool: '@aws_iam',
459
485
  },
460
486
  groups: {
461
487
  default: '@aws_cognito_user_pools',
@@ -477,23 +503,23 @@ function getCustomAuthProvider(rule) {
477
503
  }
478
504
  return stratProvider;
479
505
  }
480
- function calculateCustomAuth(authorization) {
481
- const rules = [];
506
+ function mapToNativeAppSyncAuthDirectives(authorization, isCustomHandler) {
507
+ const rules = new Set();
482
508
  for (const entry of authorization) {
483
509
  const rule = accessData(entry);
484
- validateCustomAuthRule(rule);
485
- const provider = getCustomAuthProvider(rule);
510
+ isCustomHandler && validateCustomHandlerAuthRule(rule);
511
+ const provider = getAppSyncAuthDirectiveFromRule(rule);
486
512
  if (rule.groups) {
487
513
  // example: (cognito_groups: ["Bloggers", "Readers"])
488
- rules.push(`${provider}(cognito_groups: [${rule.groups
514
+ rules.add(`${provider}(cognito_groups: [${rule.groups
489
515
  .map((group) => `"${group}"`)
490
516
  .join(', ')}])`);
491
517
  }
492
518
  else {
493
- rules.push(provider);
519
+ rules.add(provider);
494
520
  }
495
521
  }
496
- const authString = rules.join(' ');
522
+ const authString = [...rules].join(' ');
497
523
  return { authString };
498
524
  }
499
525
  function capitalize(s) {
@@ -514,7 +540,9 @@ function processFieldLevelAuthRules(fields, authFields) {
514
540
  }
515
541
  function processFields(typeName, fields, impliedFields, fieldLevelAuthRules, identifier, partitionKey, secondaryIndexes = {}) {
516
542
  const gqlFields = [];
517
- const models = [];
543
+ // stores nested, field-level type definitions (custom types and enums)
544
+ // the need to be hoisted to top-level schema types and processed accordingly
545
+ const implicitTypes = [];
518
546
  validateImpliedFields(fields, impliedFields);
519
547
  for (const [fieldName, fieldDef] of Object.entries(fields)) {
520
548
  const fieldAuth = fieldLevelAuthRules[fieldName]
@@ -534,14 +562,14 @@ function processFields(typeName, fields, impliedFields, fieldLevelAuthRules, ide
534
562
  // The inline enum type name should be `<TypeName><FieldName>` to avoid
535
563
  // enum type name conflicts
536
564
  const enumName = `${capitalize(typeName)}${capitalize(fieldName)}`;
537
- models.push([enumName, fieldDef]);
565
+ implicitTypes.push([enumName, fieldDef]);
538
566
  gqlFields.push(`${fieldName}: ${enumFieldToGql(enumName, secondaryIndexes[fieldName])}`);
539
567
  }
540
568
  else if (isCustomType(fieldDef)) {
541
569
  // The inline CustomType name should be `<TypeName><FieldName>` to avoid
542
570
  // CustomType name conflicts
543
571
  const customTypeName = `${capitalize(typeName)}${capitalize(fieldName)}`;
544
- models.push([customTypeName, fieldDef]);
572
+ implicitTypes.push([customTypeName, fieldDef]);
545
573
  gqlFields.push(`${fieldName}: ${customTypeName}`);
546
574
  }
547
575
  else {
@@ -552,7 +580,7 @@ function processFields(typeName, fields, impliedFields, fieldLevelAuthRules, ide
552
580
  throw new Error(`Unexpected field definition: ${fieldDef}`);
553
581
  }
554
582
  }
555
- return { gqlFields, models };
583
+ return { gqlFields, implicitTypes };
556
584
  }
557
585
  /**
558
586
  *
@@ -661,11 +689,47 @@ const getRefTypeForSchema = (schema) => {
661
689
  };
662
690
  return getRefType;
663
691
  };
692
+ /**
693
+ * Sorts top-level schema types to where Custom Types are processed last
694
+ * This allows us to accrue and then apply inherited auth rules for custom types from custom operations
695
+ * that reference them in their return values
696
+ */
697
+ const sortTopLevelTypes = (topLevelTypes) => {
698
+ return topLevelTypes.sort(([_typeNameA, typeDefA], [_typeNameB, typeDefB]) => {
699
+ if ((isCustomType(typeDefA) && isCustomType(typeDefB)) ||
700
+ (!isCustomType(typeDefA) && !isCustomType(typeDefB))) {
701
+ return 0;
702
+ }
703
+ else if (isCustomType(typeDefA) && !isCustomType(typeDefB)) {
704
+ return 1;
705
+ }
706
+ else {
707
+ return -1;
708
+ }
709
+ });
710
+ };
711
+ /**
712
+ * Builds up dictionary of Custom Type name - array of inherited auth rules
713
+ */
714
+ const mergeCustomTypeAuthRules = (existing, added) => {
715
+ if (!added)
716
+ return;
717
+ const { typeName, authRules } = added;
718
+ if (typeName in existing) {
719
+ existing[typeName] = [...existing[typeName], ...authRules];
720
+ }
721
+ else {
722
+ existing[typeName] = authRules;
723
+ }
724
+ };
664
725
  const schemaPreprocessor = (schema) => {
665
726
  const gqlModels = [];
666
727
  const customQueries = [];
667
728
  const customMutations = [];
668
729
  const customSubscriptions = [];
730
+ // Dict of auth rules to be applied to custom types
731
+ // Inherited from the auth configured on the custom operations that return these custom types
732
+ const customTypeInheritedAuthRules = {};
669
733
  const jsFunctions = [];
670
734
  const lambdaFunctions = {};
671
735
  const customSqlDataSourceStrategies = [];
@@ -673,7 +737,7 @@ const schemaPreprocessor = (schema) => {
673
737
  ? 'dynamodb'
674
738
  : 'sql';
675
739
  const staticSchema = schema.data.configuration.database.engine === 'dynamodb' ? false : true;
676
- const topLevelTypes = Object.entries(schema.data.types);
740
+ const topLevelTypes = sortTopLevelTypes(Object.entries(schema.data.types));
677
741
  const { schemaAuth, functionSchemaAccess } = extractFunctionSchemaAccess(schema.data.authorization);
678
742
  const getRefType = getRefTypeForSchema(schema);
679
743
  for (const [typeName, typeDef] of topLevelTypes) {
@@ -692,20 +756,25 @@ const schemaPreprocessor = (schema) => {
692
756
  const fields = typeDef.data.fields;
693
757
  validateRefUseCases(typeName, 'customType', fields, getRefType);
694
758
  const fieldAuthApplicableFields = Object.fromEntries(Object.entries(fields).filter((pair) => isModelField(pair[1])));
695
- const authString = '';
759
+ let customAuth = '';
760
+ if (typeName in customTypeInheritedAuthRules) {
761
+ const { authString } = mapToNativeAppSyncAuthDirectives(customTypeInheritedAuthRules[typeName], false);
762
+ customAuth = authString;
763
+ }
696
764
  const authFields = {};
697
765
  const fieldLevelAuthRules = processFieldLevelAuthRules(fieldAuthApplicableFields, authFields);
698
- const { gqlFields, models } = processFields(typeName, fields, authFields, fieldLevelAuthRules);
699
- topLevelTypes.push(...models);
766
+ const { gqlFields, implicitTypes } = processFields(typeName, fields, authFields, fieldLevelAuthRules);
767
+ topLevelTypes.push(...implicitTypes);
700
768
  const joined = gqlFields.join('\n ');
701
- const model = `type ${typeName} ${authString}\n{\n ${joined}\n}`;
769
+ const model = `type ${typeName} ${customAuth}\n{\n ${joined}\n}`;
702
770
  gqlModels.push(model);
703
771
  }
704
772
  else if (isCustomOperation(typeDef)) {
705
773
  const { typeName: opType } = typeDef.data;
706
- const { gqlField, models, jsFunctionForField, lambdaFunctionDefinition, customSqlDataSourceStrategy, } = transformCustomOperations(typeDef, typeName, mostRelevantAuthRules, databaseType, getRefType);
774
+ const { gqlField, implicitTypes, customTypeAuthRules, jsFunctionForField, lambdaFunctionDefinition, customSqlDataSourceStrategy, } = transformCustomOperations(typeDef, typeName, mostRelevantAuthRules, databaseType, getRefType);
775
+ mergeCustomTypeAuthRules(customTypeInheritedAuthRules, customTypeAuthRules);
707
776
  Object.assign(lambdaFunctions, lambdaFunctionDefinition);
708
- topLevelTypes.push(...models);
777
+ topLevelTypes.push(...implicitTypes);
709
778
  if (jsFunctionForField) {
710
779
  jsFunctions.push(jsFunctionForField);
711
780
  }
@@ -736,8 +805,8 @@ const schemaPreprocessor = (schema) => {
736
805
  }
737
806
  const fieldLevelAuthRules = processFieldLevelAuthRules(fields, authFields);
738
807
  validateStaticFields(fields, authFields);
739
- const { gqlFields, models } = processFields(typeName, fields, authFields, fieldLevelAuthRules, identifier, partitionKey);
740
- topLevelTypes.push(...models);
808
+ const { gqlFields, implicitTypes } = processFields(typeName, fields, authFields, fieldLevelAuthRules, identifier, partitionKey);
809
+ topLevelTypes.push(...implicitTypes);
741
810
  const joined = gqlFields.join('\n ');
742
811
  // TODO: update @model(timestamps: null) once a longer term solution gets
743
812
  // determined.
@@ -759,8 +828,8 @@ const schemaPreprocessor = (schema) => {
759
828
  throw new Error(`Model \`${typeName}\` is missing authorization rules. Add global rules to the schema or ensure every model has its own rules.`);
760
829
  }
761
830
  const fieldLevelAuthRules = processFieldLevelAuthRules(fields, authFields);
762
- const { gqlFields, models } = processFields(typeName, fields, authFields, fieldLevelAuthRules, identifier, partitionKey, transformedSecondaryIndexes);
763
- topLevelTypes.push(...models);
831
+ const { gqlFields, implicitTypes } = processFields(typeName, fields, authFields, fieldLevelAuthRules, identifier, partitionKey, transformedSecondaryIndexes);
832
+ topLevelTypes.push(...implicitTypes);
764
833
  const joined = gqlFields.join('\n ');
765
834
  const model = `type ${typeName} @model ${authString}\n{\n ${joined}\n}`;
766
835
  gqlModels.push(model);
@@ -919,10 +988,11 @@ function transformCustomOperations(typeDef, typeName, authRules, databaseType, g
919
988
  jsFunctionForField = handleCustom(handlers, opType, typeName);
920
989
  }
921
990
  const isCustom = Boolean(jsFunctionForField);
922
- const { gqlField, models, lambdaFunctionDefinition, customSqlDataSourceStrategy, } = customOperationToGql(typeName, typeDef, authRules, isCustom, databaseType, getRefType);
991
+ const { gqlField, implicitTypes, customTypeAuthRules, lambdaFunctionDefinition, customSqlDataSourceStrategy, } = customOperationToGql(typeName, typeDef, authRules, isCustom, databaseType, getRefType);
923
992
  return {
924
993
  gqlField,
925
- models,
994
+ implicitTypes,
995
+ customTypeAuthRules,
926
996
  jsFunctionForField,
927
997
  lambdaFunctionDefinition,
928
998
  customSqlDataSourceStrategy,