@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
|
|
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
|
-
?
|
|
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
|
|
169
|
-
* the return type resolved CustomType to the `
|
|
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,
|
|
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 (
|
|
179
|
-
|
|
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
|
-
|
|
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,
|
|
230
|
+
const { gqlFields, implicitTypes } = processFields(typeName, fieldArgs, {}, {});
|
|
212
231
|
callSignature += `(${gqlFields.join(', ')})`;
|
|
213
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
485
|
-
const provider =
|
|
510
|
+
isCustomHandler && validateCustomHandlerAuthRule(rule);
|
|
511
|
+
const provider = getAppSyncAuthDirectiveFromRule(rule);
|
|
486
512
|
if (rule.groups) {
|
|
487
513
|
// example: (cognito_groups: ["Bloggers", "Readers"])
|
|
488
|
-
rules.
|
|
514
|
+
rules.add(`${provider}(cognito_groups: [${rule.groups
|
|
489
515
|
.map((group) => `"${group}"`)
|
|
490
516
|
.join(', ')}])`);
|
|
491
517
|
}
|
|
492
518
|
else {
|
|
493
|
-
rules.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
699
|
-
topLevelTypes.push(...
|
|
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} ${
|
|
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,
|
|
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(...
|
|
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,
|
|
740
|
-
topLevelTypes.push(...
|
|
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,
|
|
763
|
-
topLevelTypes.push(...
|
|
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,
|
|
991
|
+
const { gqlField, implicitTypes, customTypeAuthRules, lambdaFunctionDefinition, customSqlDataSourceStrategy, } = customOperationToGql(typeName, typeDef, authRules, isCustom, databaseType, getRefType);
|
|
923
992
|
return {
|
|
924
993
|
gqlField,
|
|
925
|
-
|
|
994
|
+
implicitTypes,
|
|
995
|
+
customTypeAuthRules,
|
|
926
996
|
jsFunctionForField,
|
|
927
997
|
lambdaFunctionDefinition,
|
|
928
998
|
customSqlDataSourceStrategy,
|