graphql-connection-transformer
Advanced tools
Comparing version 1.1.0-alpha.96fcde66 to 1.1.0-alpha.9b87c745
@@ -6,2 +6,89 @@ # Change Log | ||
<a name="1.0.33"></a> | ||
## [1.0.33](https://github.com/aws-amplify/amplify-cli/compare/graphql-connection-transformer@1.0.33-beta.0...graphql-connection-transformer@1.0.33) (2018-11-09) | ||
**Note:** Version bump only for package graphql-connection-transformer | ||
<a name="1.0.33-beta.0"></a> | ||
## [1.0.33-beta.0](https://github.com/aws-amplify/amplify-cli/compare/graphql-connection-transformer@1.0.12...graphql-connection-transformer@1.0.33-beta.0) (2018-11-09) | ||
### Bug Fixes | ||
* **graphql-connection-transformer:** Remove unused types ([87c3e0a](https://github.com/aws-amplify/amplify-cli/commit/87c3e0a)) | ||
<a name="1.0.32"></a> | ||
## [1.0.32](https://github.com/aws-amplify/amplify-cli/compare/graphql-connection-transformer@1.0.32-beta.0...graphql-connection-transformer@1.0.32) (2018-11-05) | ||
**Note:** Version bump only for package graphql-connection-transformer | ||
<a name="1.0.32-beta.0"></a> | ||
## [1.0.32-beta.0](https://github.com/aws-amplify/amplify-cli/compare/graphql-connection-transformer@1.0.12...graphql-connection-transformer@1.0.32-beta.0) (2018-11-05) | ||
### Bug Fixes | ||
* **graphql-connection-transformer:** Remove unused types ([87c3e0a](https://github.com/aws-amplify/amplify-cli/commit/87c3e0a)) | ||
<a name="1.0.31"></a> | ||
## [1.0.31](https://github.com/aws-amplify/amplify-cli/compare/graphql-connection-transformer@1.0.12...graphql-connection-transformer@1.0.31) (2018-11-02) | ||
### Bug Fixes | ||
* **graphql-connection-transformer:** Remove unused types ([87c3e0a](https://github.com/aws-amplify/amplify-cli/commit/87c3e0a)) | ||
<a name="1.0.30"></a> | ||
## [1.0.30](https://github.com/aws-amplify/amplify-cli/compare/graphql-connection-transformer@1.0.30-beta.0...graphql-connection-transformer@1.0.30) (2018-11-02) | ||
**Note:** Version bump only for package graphql-connection-transformer | ||
<a name="1.0.30-beta.0"></a> | ||
## [1.0.30-beta.0](https://github.com/aws-amplify/amplify-cli/compare/graphql-connection-transformer@1.0.12...graphql-connection-transformer@1.0.30-beta.0) (2018-11-02) | ||
### Bug Fixes | ||
* **graphql-connection-transformer:** Remove unused types ([87c3e0a](https://github.com/aws-amplify/amplify-cli/commit/87c3e0a)) | ||
<a name="1.0.29"></a> | ||
## [1.0.29](https://github.com/aws-amplify/amplify-cli/compare/graphql-connection-transformer@1.0.29-beta.0...graphql-connection-transformer@1.0.29) (2018-10-23) | ||
**Note:** Version bump only for package graphql-connection-transformer | ||
<a name="1.0.29-beta.0"></a> | ||
## [1.0.29-beta.0](https://github.com/aws-amplify/amplify-cli/compare/graphql-connection-transformer@1.0.12...graphql-connection-transformer@1.0.29-beta.0) (2018-10-23) | ||
### Bug Fixes | ||
* **graphql-connection-transformer:** Remove unused types ([87c3e0a](https://github.com/aws-amplify/amplify-cli/commit/87c3e0a)) | ||
<a name="1.0.28"></a> | ||
@@ -8,0 +95,0 @@ ## [1.0.28](https://github.com/aws-amplify/amplify-cli/compare/graphql-connection-transformer@1.0.28-beta.0...graphql-connection-transformer@1.0.28) (2018-10-18) |
@@ -237,2 +237,43 @@ "use strict"; | ||
}); | ||
test('Test ModelConnectionTransformer with sortField fails if not specified in associated type', function () { | ||
var validSchema = "\n type Post @model {\n id: ID!\n title: String!\n comments: [Comment] @connection(name: \"PostComments\", sortField: \"createdAt\")\n }\n type Comment @model {\n id: ID!\n content: String\n post: Post @connection(name: \"PostComments\")\n }\n "; | ||
var transformer = new graphql_transformer_core_1.default({ | ||
transformers: [ | ||
new graphql_appsync_transformer_1.default(), | ||
new graphql_dynamodb_transformer_1.default(), | ||
new ModelConnectionTransformer_1.ModelConnectionTransformer() | ||
] | ||
}); | ||
expect(function () { transformer.transform(validSchema); }).toThrowError(); | ||
}); | ||
test('Test ModelConnectionTransformer throws with invalid key fields', function () { | ||
var transformer = new graphql_transformer_core_1.default({ | ||
transformers: [ | ||
new graphql_appsync_transformer_1.default(), | ||
new graphql_dynamodb_transformer_1.default(), | ||
new ModelConnectionTransformer_1.ModelConnectionTransformer() | ||
] | ||
}); | ||
var invalidSchema = "\n type Post @model {\n id: ID!\n title: String!\n comments: [Comment] @connection(keyField: \"postId\")\n }\n type Comment @model {\n id: ID!\n content: String\n\n # Key fields must be String or ID.\n postId: [String]\n }\n "; | ||
expect(function () { return transformer.transform(invalidSchema); }).toThrow(); | ||
var invalidSchema2 = "\n type Post @model {\n id: ID!\n title: String!\n comments: [Comment] @connection(name: \"PostComments\", keyField: \"postId\")\n }\n type Comment @model {\n id: ID!\n content: String\n\n # Key fields must be String or ID.\n postId: [String]\n\n post: Post @connection(name: \"PostComments\", keyField: \"postId\")\n }\n "; | ||
expect(function () { return transformer.transform(invalidSchema2); }).toThrow(); | ||
var invalidSchema3 = "\n type Post @model {\n id: ID!\n title: String!\n }\n type Comment @model {\n id: ID!\n content: String\n\n # Key fields must be String or ID.\n postId: [String]\n\n post: Post @connection(keyField: \"postId\")\n }\n "; | ||
expect(function () { return transformer.transform(invalidSchema3); }).toThrow(); | ||
}); | ||
test('Test ModelConnectionTransformer does not throw with valid key fields', function () { | ||
var transformer = new graphql_transformer_core_1.default({ | ||
transformers: [ | ||
new graphql_appsync_transformer_1.default(), | ||
new graphql_dynamodb_transformer_1.default(), | ||
new ModelConnectionTransformer_1.ModelConnectionTransformer() | ||
] | ||
}); | ||
var validSchema = "\n type Post @model {\n id: ID!\n title: String!\n comments: [Comment] @connection(keyField: \"postId\")\n }\n type Comment @model {\n id: ID!\n content: String\n\n # Key fields must be String or ID.\n postId: String\n }\n "; | ||
expect(function () { return transformer.transform(validSchema); }).toBeTruthy(); | ||
var validSchema2 = "\n type Post @model {\n id: ID!\n title: String!\n comments: [Comment] @connection(name: \"PostComments\", keyField: \"postId\")\n }\n type Comment @model {\n id: ID!\n content: String\n\n # Key fields must be String or ID.\n postId: ID\n\n post: Post @connection(name: \"PostComments\", keyField: \"postId\")\n }\n "; | ||
expect(function () { return transformer.transform(validSchema2); }).toBeTruthy(); | ||
var validSchema3 = "\n type Post @model {\n id: ID!\n title: String!\n }\n type Comment @model {\n id: ID!\n content: String\n\n # Key fields must be String or ID.\n postId: String\n\n post: Post @connection(keyField: \"postId\")\n }\n "; | ||
expect(function () { return transformer.transform(validSchema3); }).toBeTruthy(); | ||
}); | ||
function expectFields(type, fields) { | ||
@@ -274,4 +315,4 @@ var _loop_1 = function (fieldName) { | ||
function verifyInputCount(doc, type, count) { | ||
return doc.definitions.filter(function (def) { return def.kind === graphql_1.Kind.INPUT_OBJECT_TYPE_DEFINITION && def.name.value === type; }).length == count; | ||
return doc.definitions.filter(function (def) { return def.kind === graphql_1.Kind.INPUT_OBJECT_TYPE_DEFINITION && def.name.value === type; }).length === count; | ||
} | ||
//# sourceMappingURL=ModelConnectionTransformer.test.js.map |
@@ -14,2 +14,8 @@ "use strict"; | ||
if (nonNull === void 0) { nonNull = false; } | ||
var keyFieldExists = Boolean(input.fields.find(function (f) { return f.name.value === connectionFieldName; })); | ||
// If the key field already exists then do not change the input. | ||
// The @connection field will validate that the key field is valid. | ||
if (keyFieldExists) { | ||
return input; | ||
} | ||
var updatedFields = input.fields.concat([ | ||
@@ -22,2 +28,8 @@ graphql_transformer_common_1.makeInputValueDefinition(connectionFieldName, nonNull ? graphql_transformer_common_1.makeNonNullType(graphql_transformer_common_1.makeNamedType('ID')) : graphql_transformer_common_1.makeNamedType('ID')) | ||
function updateUpdateInputWithConnectionField(input, connectionFieldName) { | ||
var keyFieldExists = Boolean(input.fields.find(function (f) { return f.name.value === connectionFieldName; })); | ||
// If the key field already exists then do not change the input. | ||
// The @connection field will validate that the key field is valid. | ||
if (keyFieldExists) { | ||
return input; | ||
} | ||
var updatedFields = input.fields.concat([ | ||
@@ -24,0 +36,0 @@ graphql_transformer_common_1.makeInputValueDefinition(connectionFieldName, graphql_transformer_common_1.makeNamedType('ID')) |
@@ -31,2 +31,15 @@ "use strict"; | ||
} | ||
function validateKeyField(field) { | ||
if (!field) { | ||
return; | ||
} | ||
var baseType = graphql_transformer_common_1.getBaseType(field.type); | ||
var isAList = graphql_transformer_common_1.isListType(field.type); | ||
// The only valid key fields are single String and ID fields. | ||
if ((baseType === 'ID' || baseType === 'String') && | ||
(!isAList)) { | ||
return; | ||
} | ||
throw new graphql_transformer_core_1.InvalidDirectiveError("If you define a field and specify it as a 'keyField', it must be of type 'ID' or 'String'."); | ||
} | ||
/** | ||
@@ -41,3 +54,3 @@ * The @connection transform. | ||
function ModelConnectionTransformer() { | ||
var _this = _super.call(this, 'ModelConnectionTransformer', "directive @connection(name: String, keyField: String) on FIELD_DEFINITION") || this; | ||
var _this = _super.call(this, 'ModelConnectionTransformer', "directive @connection(name: String, keyField: String, sortField: String) on FIELD_DEFINITION") || this; | ||
_this.before = function (ctx) { | ||
@@ -70,2 +83,4 @@ var template = _this.resources.initTemplate(); | ||
var connectionName = graphql_transformer_common_1.getDirectiveArgument(directive)("name"); | ||
var associatedSortFieldName = null; | ||
var sortType = null; | ||
// Find the associated connection field if one exists. | ||
@@ -76,3 +91,6 @@ var associatedConnectionField = relatedType.fields.find(function (f) { | ||
var relatedDirectiveName = graphql_transformer_common_1.getDirectiveArgument(relatedDirective)("name"); | ||
return connectionName && relatedDirectiveName && relatedDirectiveName === connectionName; | ||
if (connectionName && relatedDirectiveName && relatedDirectiveName === connectionName) { | ||
associatedSortFieldName = graphql_transformer_common_1.getDirectiveArgument(relatedDirective)('sortField'); | ||
return true; | ||
} | ||
} | ||
@@ -90,2 +108,14 @@ return false; | ||
var connectionAttributeName = graphql_transformer_common_1.getDirectiveArgument(directive)("keyField"); | ||
var associatedSortField = associatedSortFieldName && | ||
parent.fields.find(function (f) { return f.name.value === associatedSortFieldName; }); | ||
if (associatedSortField) { | ||
if (graphql_transformer_common_1.isListType(associatedSortField.type)) { | ||
throw new graphql_transformer_core_1.InvalidDirectiveError("sortField \"" + associatedSortFieldName + "\" is a list. It should be a scalar."); | ||
} | ||
sortType = graphql_transformer_common_1.getBaseType(associatedSortField.type); | ||
if (!graphql_transformer_common_1.isScalar(associatedSortField.type) || sortType === graphql_transformer_common_1.STANDARD_SCALARS.Boolean) { | ||
throw new graphql_transformer_core_1.InvalidDirectiveError("sortField \"" + associatedSortFieldName + "\" is of type \"" + sortType + "\". " + | ||
"It should be a scalar that maps to a DynamoDB \"String\", \"Number\", or \"Binary\""); | ||
} | ||
} | ||
// Relationship Cardinalities: | ||
@@ -109,2 +139,5 @@ // 1. [] to [] | ||
} | ||
// Validate the provided key field is legit. | ||
var existingKeyField = relatedType.fields.find(function (f) { return f.name.value === connectionAttributeName; }); | ||
validateKeyField(existingKeyField); | ||
var queryResolver = _this.resources.makeQueryConnectionResolver(parentTypeName, fieldName, relatedTypeName, connectionAttributeName, connectionName); | ||
@@ -118,8 +151,17 @@ ctx.setResource(graphql_transformer_common_2.ResolverResourceIDs.ResolverResourceID(parentTypeName, fieldName), queryResolver); | ||
// This is the inverse of 2. | ||
// if the sortField is not defined as a field, throw an error | ||
// Cannot assume the required type of the field | ||
if (associatedSortFieldName && !associatedSortField) { | ||
throw new graphql_transformer_core_1.InvalidDirectiveError("sortField \"" + associatedSortFieldName + "\" not found on type \"" + parent.name.value + "\", other half of connection \"" + connectionName + "\"."); | ||
} | ||
if (!connectionAttributeName) { | ||
connectionAttributeName = makeConnectionAttributeName(parentTypeName, fieldName); | ||
} | ||
// Validate the provided key field is legit. | ||
var existingKeyField = parent.fields.find(function (f) { return f.name.value === connectionAttributeName; }); | ||
validateKeyField(existingKeyField); | ||
var tableLogicalId = graphql_transformer_common_2.ModelResourceIDs.ModelTableResourceID(parentTypeName); | ||
var table = ctx.getResource(tableLogicalId); | ||
var updated = _this.resources.updateTableForConnection(table, connectionName, connectionAttributeName); | ||
var sortField = associatedSortField ? { name: associatedSortFieldName, type: sortType } : null; | ||
var updated = _this.resources.updateTableForConnection(table, connectionName, connectionAttributeName, sortField); | ||
ctx.setResource(tableLogicalId, updated); | ||
@@ -149,2 +191,5 @@ var getResolver = _this.resources.makeGetItemConnectionResolver(parentTypeName, fieldName, relatedTypeName, connectionAttributeName); | ||
} | ||
// Validate the provided key field is legit. | ||
var existingKeyField = relatedType.fields.find(function (f) { return f.name.value === connectionAttributeName; }); | ||
validateKeyField(existingKeyField); | ||
var tableLogicalId = graphql_transformer_common_2.ModelResourceIDs.ModelTableResourceID(relatedTypeName); | ||
@@ -178,2 +223,5 @@ var table = ctx.getResource(tableLogicalId); | ||
} | ||
// Validate the provided key field is legit. | ||
var existingKeyField = parent.fields.find(function (f) { return f.name.value === connectionAttributeName; }); | ||
validateKeyField(existingKeyField); | ||
var getResolver = _this.resources.makeGetItemConnectionResolver(parentTypeName, fieldName, relatedTypeName, connectionAttributeName); | ||
@@ -180,0 +228,0 @@ ctx.setResource(graphql_transformer_common_2.ResolverResourceIDs.ResolverResourceID(parentTypeName, fieldName), getResolver); |
@@ -29,3 +29,4 @@ "use strict"; | ||
*/ | ||
ResourceFactory.prototype.updateTableForConnection = function (table, connectionName, connectionAttributeName) { | ||
ResourceFactory.prototype.updateTableForConnection = function (table, connectionName, connectionAttributeName, sortField) { | ||
if (sortField === void 0) { sortField = null; } | ||
var gsis = table.Properties.GlobalSecondaryIndexes || []; | ||
@@ -39,10 +40,9 @@ if (gsis.length >= 5) { | ||
if (!existingGSI) { | ||
var keySchema = [new table_1.KeySchema({ AttributeName: connectionAttributeName, KeyType: 'HASH' })]; | ||
if (sortField) { | ||
keySchema.push(new table_1.KeySchema({ AttributeName: sortField.name, KeyType: 'RANGE' })); | ||
} | ||
gsis.push(new table_1.GlobalSecondaryIndex({ | ||
IndexName: connectionGSIName, | ||
KeySchema: [ | ||
new table_1.KeySchema({ | ||
AttributeName: connectionAttributeName, | ||
KeyType: 'HASH' | ||
}) | ||
], | ||
KeySchema: keySchema, | ||
Projection: new table_1.Projection({ | ||
@@ -66,2 +66,11 @@ ProjectionType: 'ALL' | ||
} | ||
// If the attribute definition does not exist yet, add it. | ||
if (sortField) { | ||
var existingSortAttribute = attributeDefinitions.find(function (attr) { return attr.AttributeName === sortField.name; }); | ||
if (!existingSortAttribute) { | ||
var scalarType = graphql_transformer_common_1.DEFAULT_SCALARS[sortField.type]; | ||
var attributeType = scalarType === 'String' ? 'S' : 'N'; | ||
attributeDefinitions.push(new table_1.AttributeDefinition({ AttributeName: sortField.name, AttributeType: attributeType })); | ||
} | ||
} | ||
table.Properties.GlobalSecondaryIndexes = gsis; | ||
@@ -86,3 +95,3 @@ table.Properties.AttributeDefinitions = attributeDefinitions; | ||
key: graphql_mapping_template_1.obj({ | ||
id: graphql_mapping_template_1.ref("util.dynamodb.toDynamoDBJson($ctx.source." + connectionAttribute + ")") | ||
id: graphql_mapping_template_1.ref("util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.source." + connectionAttribute + ", \"" + graphql_transformer_common_1.NONE_VALUE + "\"))") | ||
}) | ||
@@ -89,0 +98,0 @@ })), |
{ | ||
"name": "graphql-connection-transformer", | ||
"version": "1.1.0-alpha.96fcde66", | ||
"version": "1.1.0-alpha.9b87c745", | ||
"description": "An AppSync model transform for connecting objects.", | ||
@@ -8,2 +8,3 @@ "main": "lib/index.js", | ||
"test": "jest", | ||
"test-ci": "jest --ci -i", | ||
"build": "tsc", | ||
@@ -22,6 +23,6 @@ "clean": "rm -rf ./lib" | ||
"graphql": "^0.13.2", | ||
"graphql-dynamodb-transformer": "^1.1.0-alpha.96fcde66", | ||
"graphql-mapping-template": "^1.1.0-alpha.96fcde66", | ||
"graphql-transformer-common": "^1.1.0-alpha.96fcde66", | ||
"graphql-transformer-core": "^1.1.0-alpha.96fcde66" | ||
"graphql-dynamodb-transformer": "^1.1.0-alpha.9b87c745", | ||
"graphql-mapping-template": "^1.1.0-alpha.9b87c745", | ||
"graphql-transformer-common": "^1.1.0-alpha.9b87c745", | ||
"graphql-transformer-core": "^1.1.0-alpha.9b87c745" | ||
}, | ||
@@ -33,3 +34,3 @@ "devDependencies": { | ||
"aws-sdk": "^2.259.1", | ||
"graphql-appsync-transformer": "^1.1.0-alpha.96fcde66", | ||
"graphql-appsync-transformer": "^1.1.0-alpha.9b87c745", | ||
"jest": "^23.1.0", | ||
@@ -36,0 +37,0 @@ "ts-jest": "^22.4.6", |
@@ -6,3 +6,3 @@ import { | ||
} from 'graphql' | ||
import GraphQLTransform from 'graphql-transformer-core' | ||
import GraphQLTransform, { InvalidDirectiveError } from 'graphql-transformer-core' | ||
import { ResourceConstants, ResolverResourceIDs, ModelResourceIDs } from 'graphql-transformer-common' | ||
@@ -366,2 +366,147 @@ import DynamoDBModelTransformer from 'graphql-dynamodb-transformer' | ||
test('Test ModelConnectionTransformer with sortField fails if not specified in associated type', () => { | ||
const validSchema = ` | ||
type Post @model { | ||
id: ID! | ||
title: String! | ||
comments: [Comment] @connection(name: "PostComments", sortField: "createdAt") | ||
} | ||
type Comment @model { | ||
id: ID! | ||
content: String | ||
post: Post @connection(name: "PostComments") | ||
} | ||
` | ||
const transformer = new GraphQLTransform({ | ||
transformers: [ | ||
new AppSyncTransformer(), | ||
new DynamoDBModelTransformer(), | ||
new ModelConnectionTransformer() | ||
] | ||
}) | ||
expect(() => { transformer.transform(validSchema) }).toThrowError() | ||
}); | ||
test('Test ModelConnectionTransformer throws with invalid key fields', () => { | ||
const transformer = new GraphQLTransform({ | ||
transformers: [ | ||
new AppSyncTransformer(), | ||
new DynamoDBModelTransformer(), | ||
new ModelConnectionTransformer() | ||
] | ||
}) | ||
const invalidSchema = ` | ||
type Post @model { | ||
id: ID! | ||
title: String! | ||
comments: [Comment] @connection(keyField: "postId") | ||
} | ||
type Comment @model { | ||
id: ID! | ||
content: String | ||
# Key fields must be String or ID. | ||
postId: [String] | ||
} | ||
` | ||
expect(() => transformer.transform(invalidSchema)).toThrow(); | ||
const invalidSchema2 = ` | ||
type Post @model { | ||
id: ID! | ||
title: String! | ||
comments: [Comment] @connection(name: "PostComments", keyField: "postId") | ||
} | ||
type Comment @model { | ||
id: ID! | ||
content: String | ||
# Key fields must be String or ID. | ||
postId: [String] | ||
post: Post @connection(name: "PostComments", keyField: "postId") | ||
} | ||
` | ||
expect(() => transformer.transform(invalidSchema2)).toThrow(); | ||
const invalidSchema3 = ` | ||
type Post @model { | ||
id: ID! | ||
title: String! | ||
} | ||
type Comment @model { | ||
id: ID! | ||
content: String | ||
# Key fields must be String or ID. | ||
postId: [String] | ||
post: Post @connection(keyField: "postId") | ||
} | ||
` | ||
expect(() => transformer.transform(invalidSchema3)).toThrow(); | ||
}) | ||
test('Test ModelConnectionTransformer does not throw with valid key fields', () => { | ||
const transformer = new GraphQLTransform({ | ||
transformers: [ | ||
new AppSyncTransformer(), | ||
new DynamoDBModelTransformer(), | ||
new ModelConnectionTransformer() | ||
] | ||
}) | ||
const validSchema = ` | ||
type Post @model { | ||
id: ID! | ||
title: String! | ||
comments: [Comment] @connection(keyField: "postId") | ||
} | ||
type Comment @model { | ||
id: ID! | ||
content: String | ||
# Key fields must be String or ID. | ||
postId: String | ||
} | ||
` | ||
expect(() => transformer.transform(validSchema)).toBeTruthy(); | ||
const validSchema2 = ` | ||
type Post @model { | ||
id: ID! | ||
title: String! | ||
comments: [Comment] @connection(name: "PostComments", keyField: "postId") | ||
} | ||
type Comment @model { | ||
id: ID! | ||
content: String | ||
# Key fields must be String or ID. | ||
postId: ID | ||
post: Post @connection(name: "PostComments", keyField: "postId") | ||
} | ||
` | ||
expect(() => transformer.transform(validSchema2)).toBeTruthy(); | ||
const validSchema3 = ` | ||
type Post @model { | ||
id: ID! | ||
title: String! | ||
} | ||
type Comment @model { | ||
id: ID! | ||
content: String | ||
# Key fields must be String or ID. | ||
postId: String | ||
post: Post @connection(keyField: "postId") | ||
} | ||
` | ||
expect(() => transformer.transform(validSchema3)).toBeTruthy(); | ||
}) | ||
function expectFields(type: ObjectTypeDefinitionNode, fields: string[]) { | ||
@@ -402,3 +547,3 @@ for (const fieldName of fields) { | ||
function verifyInputCount(doc: DocumentNode, type: string, count: number): boolean { | ||
return doc.definitions.filter(def => def.kind === Kind.INPUT_OBJECT_TYPE_DEFINITION && def.name.value === type).length == count; | ||
return doc.definitions.filter(def => def.kind === Kind.INPUT_OBJECT_TYPE_DEFINITION && def.name.value === type).length === count; | ||
} |
@@ -9,2 +9,8 @@ import { InputObjectTypeDefinitionNode } from 'graphql' | ||
): InputObjectTypeDefinitionNode { | ||
const keyFieldExists = Boolean(input.fields.find(f => f.name.value === connectionFieldName)) | ||
// If the key field already exists then do not change the input. | ||
// The @connection field will validate that the key field is valid. | ||
if (keyFieldExists) { | ||
return input; | ||
} | ||
const updatedFields = [ | ||
@@ -24,2 +30,8 @@ ...input.fields, | ||
): InputObjectTypeDefinitionNode { | ||
const keyFieldExists = Boolean(input.fields.find(f => f.name.value === connectionFieldName)) | ||
// If the key field already exists then do not change the input. | ||
// The @connection field will validate that the key field is valid. | ||
if (keyFieldExists) { | ||
return input; | ||
} | ||
const updatedFields = [ | ||
@@ -26,0 +38,0 @@ ...input.fields, |
@@ -18,2 +18,3 @@ import { Transformer, TransformerContext, InvalidDirectiveError } from 'graphql-transformer-core' | ||
getBaseType, isListType, getDirectiveArgument, blankObject, | ||
isScalar, STANDARD_SCALARS, | ||
toCamelCase, isNonNullType | ||
@@ -28,2 +29,18 @@ } from 'graphql-transformer-common' | ||
function validateKeyField(field: FieldDefinitionNode): void { | ||
if (!field) { | ||
return | ||
} | ||
const baseType = getBaseType(field.type); | ||
const isAList = isListType(field.type) | ||
// The only valid key fields are single String and ID fields. | ||
if ( | ||
(baseType === 'ID' || baseType === 'String') && | ||
(!isAList) | ||
) { | ||
return; | ||
} | ||
throw new InvalidDirectiveError(`If you define a field and specify it as a 'keyField', it must be of type 'ID' or 'String'.`) | ||
} | ||
/** | ||
@@ -42,3 +59,3 @@ * The @connection transform. | ||
'ModelConnectionTransformer', | ||
`directive @connection(name: String, keyField: String) on FIELD_DEFINITION` | ||
`directive @connection(name: String, keyField: String, sortField: String) on FIELD_DEFINITION` | ||
) | ||
@@ -85,2 +102,4 @@ this.resources = new ResourceFactory(); | ||
let connectionName = getDirectiveArgument(directive)("name") | ||
let associatedSortFieldName = null | ||
let sortType = null | ||
// Find the associated connection field if one exists. | ||
@@ -92,3 +111,6 @@ const associatedConnectionField = relatedType.fields.find( | ||
const relatedDirectiveName = getDirectiveArgument(relatedDirective)("name") | ||
return connectionName && relatedDirectiveName && relatedDirectiveName === connectionName | ||
if (connectionName && relatedDirectiveName && relatedDirectiveName === connectionName) { | ||
associatedSortFieldName = getDirectiveArgument(relatedDirective)('sortField') | ||
return true | ||
} | ||
} | ||
@@ -112,3 +134,20 @@ return false | ||
let connectionAttributeName = getDirectiveArgument(directive)("keyField") | ||
const associatedSortField = associatedSortFieldName && | ||
parent.fields.find((f: FieldDefinitionNode) => f.name.value === associatedSortFieldName) | ||
if (associatedSortField) { | ||
if (isListType(associatedSortField.type)) { | ||
throw new InvalidDirectiveError( | ||
`sortField "${associatedSortFieldName}" is a list. It should be a scalar.` | ||
) | ||
} | ||
sortType = getBaseType(associatedSortField.type) | ||
if (!isScalar(associatedSortField.type) || sortType === STANDARD_SCALARS.Boolean) { | ||
throw new InvalidDirectiveError( | ||
`sortField "${associatedSortFieldName}" is of type "${sortType}". ` + | ||
`It should be a scalar that maps to a DynamoDB "String", "Number", or "Binary"` | ||
) | ||
} | ||
} | ||
// Relationship Cardinalities: | ||
@@ -131,2 +170,6 @@ // 1. [] to [] | ||
} | ||
// Validate the provided key field is legit. | ||
const existingKeyField = relatedType.fields.find(f => f.name.value === connectionAttributeName) | ||
validateKeyField(existingKeyField); | ||
const queryResolver = this.resources.makeQueryConnectionResolver( | ||
@@ -147,8 +190,22 @@ parentTypeName, | ||
// if the sortField is not defined as a field, throw an error | ||
// Cannot assume the required type of the field | ||
if (associatedSortFieldName && !associatedSortField) { | ||
throw new InvalidDirectiveError( | ||
`sortField "${associatedSortFieldName}" not found on type "${parent.name.value}", other half of connection "${connectionName}".` | ||
) | ||
} | ||
if (!connectionAttributeName) { | ||
connectionAttributeName = makeConnectionAttributeName(parentTypeName, fieldName) | ||
} | ||
// Validate the provided key field is legit. | ||
const existingKeyField = parent.fields.find(f => f.name.value === connectionAttributeName) | ||
validateKeyField(existingKeyField); | ||
const tableLogicalId = ModelResourceIDs.ModelTableResourceID(parentTypeName) | ||
const table = ctx.getResource(tableLogicalId) as Table | ||
const updated = this.resources.updateTableForConnection(table, connectionName, connectionAttributeName) | ||
const sortField = associatedSortField ? { name: associatedSortFieldName, type: sortType } : null | ||
const updated = this.resources.updateTableForConnection(table, connectionName, connectionAttributeName, | ||
sortField) | ||
ctx.setResource(tableLogicalId, updated) | ||
@@ -186,2 +243,6 @@ | ||
// Validate the provided key field is legit. | ||
const existingKeyField = relatedType.fields.find(f => f.name.value === connectionAttributeName) | ||
validateKeyField(existingKeyField); | ||
const tableLogicalId = ModelResourceIDs.ModelTableResourceID(relatedTypeName) | ||
@@ -223,2 +284,7 @@ const table = ctx.getResource(tableLogicalId) as Table | ||
} | ||
// Validate the provided key field is legit. | ||
const existingKeyField = parent.fields.find(f => f.name.value === connectionAttributeName) | ||
validateKeyField(existingKeyField); | ||
const getResolver = this.resources.makeGetItemConnectionResolver( | ||
@@ -225,0 +291,0 @@ parentTypeName, |
@@ -10,3 +10,3 @@ import Table, { GlobalSecondaryIndex, KeySchema, Projection, ProvisionedThroughput, AttributeDefinition } from 'cloudform/types/dynamoDb/table' | ||
} from 'graphql-mapping-template' | ||
import { ResourceConstants, ModelResourceIDs } from 'graphql-transformer-common' | ||
import { ResourceConstants, ModelResourceIDs, DEFAULT_SCALARS, NONE_VALUE } from 'graphql-transformer-common' | ||
import { InvalidDirectiveError } from 'graphql-transformer-core'; | ||
@@ -39,2 +39,3 @@ | ||
connectionAttributeName: string, | ||
sortField: { name: string, type: string } = null | ||
): Table { | ||
@@ -52,10 +53,9 @@ const gsis = table.Properties.GlobalSecondaryIndexes || [] as GlobalSecondaryIndex[] | ||
if (!existingGSI) { | ||
const keySchema = [new KeySchema({ AttributeName: connectionAttributeName, KeyType: 'HASH' })] | ||
if (sortField) { | ||
keySchema.push(new KeySchema({ AttributeName: sortField.name, KeyType: 'RANGE' })) | ||
} | ||
gsis.push(new GlobalSecondaryIndex({ | ||
IndexName: connectionGSIName, | ||
KeySchema: [ | ||
new KeySchema({ | ||
AttributeName: connectionAttributeName, | ||
KeyType: 'HASH' | ||
}) | ||
], | ||
KeySchema: keySchema, | ||
Projection: new Projection({ | ||
@@ -80,2 +80,13 @@ ProjectionType: 'ALL' | ||
} | ||
// If the attribute definition does not exist yet, add it. | ||
if (sortField) { | ||
const existingSortAttribute = attributeDefinitions.find(attr => attr.AttributeName === sortField.name) | ||
if (!existingSortAttribute) { | ||
const scalarType = DEFAULT_SCALARS[sortField.type] | ||
const attributeType = scalarType === 'String' ? 'S' : 'N' | ||
attributeDefinitions.push(new AttributeDefinition({ AttributeName: sortField.name, AttributeType: attributeType })) | ||
} | ||
} | ||
table.Properties.GlobalSecondaryIndexes = gsis | ||
@@ -102,3 +113,3 @@ table.Properties.AttributeDefinitions = attributeDefinitions | ||
key: obj({ | ||
id: ref(`util.dynamodb.toDynamoDBJson($ctx.source.${connectionAttribute})`) | ||
id: ref(`util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.source.${connectionAttribute}, "${NONE_VALUE}"))`) | ||
}) | ||
@@ -105,0 +116,0 @@ }) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
143556
1917