New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

graphql-key-transformer

Package Overview
Dependencies
Maintainers
1
Versions
826
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

graphql-key-transformer - npm Package Compare versions

Comparing version 1.20.0 to 1.20.1-beta.0

24

lib/__tests__/KeyTransformer.test.js

@@ -8,5 +8,3 @@ "use strict";

var transformer = new graphql_transformer_core_1.default({
transformers: [
new KeyTransformer_1.default()
]
transformers: [new KeyTransformer_1.default()],
});

@@ -18,5 +16,3 @@ expect(function () { return transformer.transform(validSchema); }).toThrowError(graphql_transformer_core_1.InvalidDirectiveError);

var transformer = new graphql_transformer_core_1.default({
transformers: [
new KeyTransformer_1.default()
]
transformers: [new KeyTransformer_1.default()],
});

@@ -28,5 +24,3 @@ expect(function () { return transformer.transform(validSchema); }).toThrowError(graphql_transformer_core_1.InvalidDirectiveError);

var transformer = new graphql_transformer_core_1.default({
transformers: [
new KeyTransformer_1.default()
]
transformers: [new KeyTransformer_1.default()],
});

@@ -38,5 +32,3 @@ expect(function () { return transformer.transform(validSchema); }).toThrowError(graphql_transformer_core_1.InvalidDirectiveError);

var transformer = new graphql_transformer_core_1.default({
transformers: [
new KeyTransformer_1.default()
]
transformers: [new KeyTransformer_1.default()],
});

@@ -48,5 +40,3 @@ expect(function () { return transformer.transform(validSchema); }).toThrowError(graphql_transformer_core_1.InvalidDirectiveError);

var transformer = new graphql_transformer_core_1.default({
transformers: [
new KeyTransformer_1.default()
]
transformers: [new KeyTransformer_1.default()],
});

@@ -58,5 +48,3 @@ expect(function () { return transformer.transform(validSchema); }).toThrowError(graphql_transformer_core_1.InvalidDirectiveError);

var transformer = new graphql_transformer_core_1.default({
transformers: [
new KeyTransformer_1.default()
]
transformers: [new KeyTransformer_1.default()],
});

@@ -63,0 +51,0 @@ expect(function () { return transformer.transform(validSchema); }).toThrowError(graphql_transformer_core_1.InvalidDirectiveError);

@@ -47,3 +47,3 @@ "use strict";

function KeyTransformer() {
var _this = _super.call(this, 'KeyTransformer', graphql_transformer_core_1.gql(templateObject_1 || (templateObject_1 = __makeTemplateObject(["directive @key(name: String, fields: [String!]!, queryField: String) on OBJECT"], ["directive @key(name: String, fields: [String!]!, queryField: String) on OBJECT"])))) || this;
var _this = _super.call(this, 'KeyTransformer', graphql_transformer_core_1.gql(templateObject_1 || (templateObject_1 = __makeTemplateObject(["\n directive @key(name: String, fields: [String!]!, queryField: String) on OBJECT\n "], ["\n directive @key(name: String, fields: [String!]!, queryField: String) on OBJECT\n "])))) || this;
/**

@@ -105,3 +105,3 @@ * Augment the table key structures based on the @key.

_this.setKeySnippet(directive),
getResolver.Properties.RequestMappingTemplate
getResolver.Properties.RequestMappingTemplate,
]);

@@ -112,3 +112,3 @@ }

graphql_mapping_template_1.print(setQuerySnippet(definition, directive, ctx)),
listResolver.Properties.RequestMappingTemplate
listResolver.Properties.RequestMappingTemplate,
]);

@@ -120,3 +120,3 @@ }

ensureCompositeKeySnippet(directive),
createResolver.Properties.RequestMappingTemplate
createResolver.Properties.RequestMappingTemplate,
]);

@@ -128,3 +128,3 @@ }

ensureCompositeKeySnippet(directive),
updateResolver.Properties.RequestMappingTemplate
updateResolver.Properties.RequestMappingTemplate,
]);

@@ -135,3 +135,3 @@ }

_this.setKeySnippet(directive, true),
deleteResolver.Properties.RequestMappingTemplate
deleteResolver.Properties.RequestMappingTemplate,
]);

@@ -146,3 +146,3 @@ }

ensureCompositeKeySnippet(directive),
createResolver.Properties.RequestMappingTemplate
createResolver.Properties.RequestMappingTemplate,
]);

@@ -154,3 +154,3 @@ }

ensureCompositeKeySnippet(directive),
updateResolver.Properties.RequestMappingTemplate
updateResolver.Properties.RequestMappingTemplate,
]);

@@ -161,3 +161,3 @@ }

ensureCompositeKeySnippet(directive),
deleteResolver.Properties.RequestMappingTemplate
deleteResolver.Properties.RequestMappingTemplate,
]);

@@ -213,3 +213,3 @@ }

if (!sortKeyConditionInput) {
var checkedKeyName = args.name ? args.name : "<unnamed>";
var checkedKeyName = args.name ? args.name : '<unnamed>';
throw new graphql_transformer_core_1.InvalidDirectiveError("Cannot resolve type for field '" + finalSortKeyFieldName_1 + "' in @key '" + checkedKeyName + "' on type '" + definition.name.value + "'.");

@@ -248,3 +248,3 @@ }

getField_1 = __assign(__assign({}, getField_1), { arguments: getArguments });
query = __assign(__assign({}, query), { fields: query.fields.map(function (field) { return field.name.value === getField_1.name.value ? getField_1 : field; }) });
query = __assign(__assign({}, query), { fields: query.fields.map(function (field) { return (field.name.value === getField_1.name.value ? getField_1 : field); }) });
ctx.putType(query);

@@ -276,3 +276,3 @@ }

listField_1 = __assign(__assign({}, listField_1), { arguments: listArguments });
query = __assign(__assign({}, query), { fields: query.fields.map(function (field) { return field.name.value === listField_1.name.value ? listField_1 : field; }) });
query = __assign(__assign({}, query), { fields: query.fields.map(function (field) { return (field.name.value === listField_1.name.value ? listField_1 : field); }) });
ctx.putType(query);

@@ -340,8 +340,8 @@ }

graphql_mapping_template_1.forEach(graphql_mapping_template_1.ref('keyFieldName'), graphql_mapping_template_1.ref('keyFieldNames'), [
graphql_mapping_template_1.iff(graphql_mapping_template_1.raw("$ctx.args.input.containsKey(\"$keyFieldName\")"), graphql_mapping_template_1.set(graphql_mapping_template_1.ref('hasSeenSomeKeyArg'), graphql_mapping_template_1.bool(true)), true)
graphql_mapping_template_1.iff(graphql_mapping_template_1.raw("$ctx.args.input.containsKey(\"$keyFieldName\")"), graphql_mapping_template_1.set(graphql_mapping_template_1.ref('hasSeenSomeKeyArg'), graphql_mapping_template_1.bool(true)), true),
]),
graphql_mapping_template_1.forEach(graphql_mapping_template_1.ref('keyFieldName'), graphql_mapping_template_1.ref('keyFieldNames'), [
graphql_mapping_template_1.iff(graphql_mapping_template_1.raw("$hasSeenSomeKeyArg && !$ctx.args.input.containsKey(\"$keyFieldName\")"), graphql_mapping_template_1.raw("$util.error(\"When updating any part of the composite sort key for @key '" + directiveArgs.name + "'," +
" you must provide all fields for the key. Missing key: '$keyFieldName'.\")"))
])
" you must provide all fields for the key. Missing key: '$keyFieldName'.\")")),
]),
]));

@@ -410,3 +410,3 @@ }

if (!fieldMap.has(fieldName)) {
var checkedKeyName = directiveArgs.name ? directiveArgs.name : "<unnamed>";
var checkedKeyName = directiveArgs.name ? directiveArgs.name : '<unnamed>';
throw new graphql_transformer_core_1.InvalidDirectiveError("You cannot specify a non-existant field '" + fieldName + "' in @key '" + checkedKeyName + "' on type '" + definition.name.value + "'.");

@@ -494,4 +494,4 @@ }

Projection: new table_1.Projection({
ProjectionType: 'ALL'
})
ProjectionType: 'ALL',
}),
};

@@ -508,3 +508,3 @@ if (primaryPartitionKeyName === ks[0].AttributeName) {

ReadCapacityUnits: cloudform_types_1.Fn.Ref(graphql_transformer_common_1.ResourceConstants.PARAMETERS.DynamoDBModelTableReadIOPS),
WriteCapacityUnits: cloudform_types_1.Fn.Ref(graphql_transformer_common_1.ResourceConstants.PARAMETERS.DynamoDBModelTableWriteIOPS)
WriteCapacityUnits: cloudform_types_1.Fn.Ref(graphql_transformer_common_1.ResourceConstants.PARAMETERS.DynamoDBModelTableWriteIOPS),
}) })));

@@ -533,6 +533,3 @@ }

var condensedSortKey = condenseRangeKey(args.fields.slice(1));
return [
{ AttributeName: args.fields[0], KeyType: 'HASH' },
{ AttributeName: condensedSortKey, KeyType: 'RANGE' },
];
return [{ AttributeName: args.fields[0], KeyType: 'HASH' }, { AttributeName: condensedSortKey, KeyType: 'RANGE' }];
}

@@ -613,3 +610,2 @@ else {

}
;
// Key fields are non-nullable, non-key fields are not non-nullable.

@@ -626,3 +622,2 @@ function replaceUpdateInput(definition, input, keyFields) {

}
;
// Key fields are non-nullable, non-key fields are not non-nullable.

@@ -632,3 +627,2 @@ function replaceDeleteInput(definition, input, keyFields) {

}
;
/**

@@ -640,5 +634,3 @@ * Return a VTL object containing the compressed key information.

var _a, _b, _c;
var argsPrefix = isMutation ?
'ctx.args.input' :
'ctx.args';
var argsPrefix = isMutation ? 'ctx.args.input' : 'ctx.args';
if (args.fields.length > 2) {

@@ -679,3 +671,3 @@ var rangeKeyFields = args.fields.slice(1);

_a))), graphql_mapping_template_1.qref("$" + graphql_transformer_common_1.ResourceConstants.SNIPPETS.DynamoDBNameOverrideMap + ".put(\"" + condensedSortKey + "\", \"" + dynamoDBFriendlySortKeyName + "\")")),
graphql_mapping_template_1.qref("$ctx.args.input.put(\"" + condensedSortKey + "\",\"" + condensedSortKeyValue + "\")")
graphql_mapping_template_1.qref("$ctx.args.input.put(\"" + condensedSortKey + "\",\"" + condensedSortKeyValue + "\")"),
]));

@@ -709,3 +701,3 @@ }

query: graphql_mapping_template_1.ref(graphql_transformer_common_1.ResourceConstants.SNIPPETS.ModelQueryExpression),
index: graphql_mapping_template_1.str(index)
index: graphql_mapping_template_1.str(index),
})),

@@ -715,5 +707,5 @@ graphql_mapping_template_1.ifElse(graphql_mapping_template_1.raw("!$util.isNull($ctx.args.sortDirection)\n && $ctx.args.sortDirection == \"DESC\""), graphql_mapping_template_1.set(graphql_mapping_template_1.ref(requestVariable + ".scanIndexForward"), graphql_mapping_template_1.bool(false)), graphql_mapping_template_1.set(graphql_mapping_template_1.ref(requestVariable + ".scanIndexForward"), graphql_mapping_template_1.bool(true))),

graphql_mapping_template_1.iff(graphql_mapping_template_1.ref('context.args.filter'), graphql_mapping_template_1.set(graphql_mapping_template_1.ref(requestVariable + ".filter"), graphql_mapping_template_1.ref('util.parseJson("$util.transform.toDynamoDBFilterExpression($ctx.args.filter)")')), true),
graphql_mapping_template_1.raw("$util.toJson($" + requestVariable + ")")
graphql_mapping_template_1.raw("$util.toJson($" + requestVariable + ")"),
])),
ResponseMappingTemplate: graphql_mapping_template_1.print(graphql_mapping_template_1.raw('$util.toJson($ctx.result)'))
ResponseMappingTemplate: graphql_mapping_template_1.print(graphql_mapping_template_1.raw('$util.toJson($ctx.result)')),
});

@@ -730,3 +722,3 @@ }

graphql_mapping_template_1.set(graphql_mapping_template_1.ref(graphql_transformer_common_1.ResourceConstants.SNIPPETS.ModelQueryExpression), graphql_mapping_template_1.obj({})),
graphql_transformer_common_1.applyKeyExpressionForCompositeKey(keys, keyTypes, graphql_transformer_common_1.ResourceConstants.SNIPPETS.ModelQueryExpression)
graphql_transformer_common_1.applyKeyExpressionForCompositeKey(keys, keyTypes, graphql_transformer_common_1.ResourceConstants.SNIPPETS.ModelQueryExpression),
]);

@@ -733,0 +725,0 @@ }

{
"name": "graphql-key-transformer",
"version": "1.20.0",
"version": "1.20.1-beta.0",
"description": "Implements the @key directive.",

@@ -17,5 +17,5 @@ "main": "lib/index.js",

"graphql": "^0.13.2",
"graphql-mapping-template": "3.20.0",
"graphql-transformer-common": "3.27.0",
"graphql-transformer-core": "5.13.0"
"graphql-mapping-template": "3.20.1-beta.0",
"graphql-transformer-common": "3.27.1-beta.0",
"graphql-transformer-core": "5.13.1-beta.0"
},

@@ -46,3 +46,3 @@ "devDependencies": {

},
"gitHead": "49b0d769dbde9b9b389e87da87f20d9423bd9597"
"gitHead": "81b51c2d0caa0f1bb965640f7c8c8312401b002d"
}

@@ -1,6 +0,6 @@

import GraphQLTransform, { Transformer, InvalidDirectiveError } from 'graphql-transformer-core'
import KeyTransformer from '../KeyTransformer'
import GraphQLTransform, { Transformer, InvalidDirectiveError } from 'graphql-transformer-core';
import KeyTransformer from '../KeyTransformer';
test('KeyTransformer should fail if more than 1 @key is provided without a name.', () => {
const validSchema = `
const validSchema = `
type Test @key(fields: ["id"]) @key(fields: ["email"]) {

@@ -10,15 +10,13 @@ id: ID!

}
`
`;
const transformer = new GraphQLTransform({
transformers: [
new KeyTransformer()
]
})
const transformer = new GraphQLTransform({
transformers: [new KeyTransformer()],
});
expect(() => transformer.transform(validSchema)).toThrowError(InvalidDirectiveError);
})
expect(() => transformer.transform(validSchema)).toThrowError(InvalidDirectiveError);
});
test('KeyTransformer should fail if more than 1 @key is provided with the same name.', () => {
const validSchema = `
const validSchema = `
type Test @key(name: "Test", fields: ["id"]) @key(name: "Test", fields: ["email"]) {

@@ -28,16 +26,13 @@ id: ID!

}
`
`;
const transformer = new GraphQLTransform({
transformers: [
new KeyTransformer()
]
})
const transformer = new GraphQLTransform({
transformers: [new KeyTransformer()],
});
expect(() => transformer.transform(validSchema)).toThrowError(InvalidDirectiveError);
})
expect(() => transformer.transform(validSchema)).toThrowError(InvalidDirectiveError);
});
test('KeyTransformer should fail if referencing a field that does not exist.', () => {
const validSchema = `
const validSchema = `
type Test @key(fields: ["someWeirdId"]) {

@@ -47,15 +42,13 @@ id: ID!

}
`
`;
const transformer = new GraphQLTransform({
transformers: [
new KeyTransformer()
]
})
const transformer = new GraphQLTransform({
transformers: [new KeyTransformer()],
});
expect(() => transformer.transform(validSchema)).toThrowError(InvalidDirectiveError);
})
expect(() => transformer.transform(validSchema)).toThrowError(InvalidDirectiveError);
});
test('Test that a primary @key fails if pointing to nullable fields.', () => {
const validSchema = `
const validSchema = `
type Test @key(fields: ["email"]) {

@@ -65,15 +58,13 @@ id: ID!

}
`
`;
const transformer = new GraphQLTransform({
transformers: [
new KeyTransformer()
]
})
const transformer = new GraphQLTransform({
transformers: [new KeyTransformer()],
});
expect(() => transformer.transform(validSchema)).toThrowError(InvalidDirectiveError);
})
expect(() => transformer.transform(validSchema)).toThrowError(InvalidDirectiveError);
});
test('Test that model with an LSI but no primary sort key will fail.', () => {
const validSchema = `
const validSchema = `
type Test @key(fields: ["id"]) @key(name: "SomeLSI", fields: ["id", "email"]) {

@@ -83,14 +74,12 @@ id: ID!

}
`
`;
const transformer = new GraphQLTransform({
transformers: [
new KeyTransformer()
]
})
expect(() => transformer.transform(validSchema)).toThrowError(InvalidDirectiveError);
})
const transformer = new GraphQLTransform({
transformers: [new KeyTransformer()],
});
expect(() => transformer.transform(validSchema)).toThrowError(InvalidDirectiveError);
});
test('KeyTransformer should fail if a non-existing type field is defined as key field.', () => {
const validSchema = `
const validSchema = `
type Test @key(name: "Test", fields: ["one"]) {

@@ -100,11 +89,9 @@ id: ID!

}
`
`;
const transformer = new GraphQLTransform({
transformers: [
new KeyTransformer()
]
})
const transformer = new GraphQLTransform({
transformers: [new KeyTransformer()],
});
expect(() => transformer.transform(validSchema)).toThrowError(InvalidDirectiveError);
})
expect(() => transformer.transform(validSchema)).toThrowError(InvalidDirectiveError);
});
import KeyTransformer from './KeyTransformer';
export default KeyTransformer;
export default KeyTransformer;
import {
Transformer, gql, TransformerContext, getDirectiveArguments, TransformerContractError, InvalidDirectiveError
Transformer,
gql,
TransformerContext,
getDirectiveArguments,
TransformerContractError,
InvalidDirectiveError,
} from 'graphql-transformer-core';
import {
obj, str, ref, printBlock, compoundExpression, newline, raw, qref, set, Expression, print,
ifElse, iff, block, bool, forEach, list
obj,
str,
ref,
printBlock,
compoundExpression,
newline,
raw,
qref,
set,
Expression,
print,
ifElse,
iff,
block,
bool,
forEach,
list,
} from 'graphql-mapping-template';
import {
ResolverResourceIDs, ResourceConstants, isNonNullType,
attributeTypeFromScalar, ModelResourceIDs, makeInputValueDefinition,
wrapNonNull, withNamedNodeNamed,
makeNonNullType, makeNamedType, getBaseType,
makeConnectionField,
makeScalarKeyConditionForType, applyKeyExpressionForCompositeKey,
makeCompositeKeyConditionInputForKey, makeCompositeKeyInputForKey, toCamelCase, graphqlName, toUpper
ResolverResourceIDs,
ResourceConstants,
isNonNullType,
attributeTypeFromScalar,
ModelResourceIDs,
makeInputValueDefinition,
wrapNonNull,
withNamedNodeNamed,
makeNonNullType,
makeNamedType,
getBaseType,
makeConnectionField,
makeScalarKeyConditionForType,
applyKeyExpressionForCompositeKey,
makeCompositeKeyConditionInputForKey,
makeCompositeKeyInputForKey,
toCamelCase,
graphqlName,
toUpper,
} from 'graphql-transformer-common';
import {
ObjectTypeDefinitionNode, FieldDefinitionNode, DirectiveNode,
InputObjectTypeDefinitionNode, TypeNode, Kind, InputValueDefinitionNode, EnumTypeDefinitionNode
ObjectTypeDefinitionNode,
FieldDefinitionNode,
DirectiveNode,
InputObjectTypeDefinitionNode,
TypeNode,
Kind,
InputValueDefinitionNode,
EnumTypeDefinitionNode,
} from 'graphql';
import { AppSync, IAM, Fn, DynamoDB, Refs } from 'cloudform-types'
import { AppSync, IAM, Fn, DynamoDB, Refs } from 'cloudform-types';
import { Projection, GlobalSecondaryIndex, LocalSecondaryIndex } from 'cloudform-types/types/dynamoDb/table';
interface KeyArguments {
name?: string;
fields: string[];
queryField?: string;
name?: string;
fields: string[];
queryField?: string;
}
export default class KeyTransformer extends Transformer {
constructor() {
super(
'KeyTransformer',
gql`
directive @key(name: String, fields: [String!]!, queryField: String) on OBJECT
`
);
}
constructor() {
super(
'KeyTransformer',
gql`directive @key(name: String, fields: [String!]!, queryField: String) on OBJECT`
)
/**
* Augment the table key structures based on the @key.
*/
object = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerContext) => {
this.validate(definition, directive, ctx);
this.updateIndexStructures(definition, directive, ctx);
this.updateSchema(definition, directive, ctx);
this.updateResolvers(definition, directive, ctx);
this.addKeyConditionInputs(definition, directive, ctx);
};
/**
* Update the existing @model table's index structures. Includes primary key, GSI, and LSIs.
* @param definition The object type definition node.
* @param directive The @key directive
* @param ctx The transformer context
*/
private updateIndexStructures = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerContext) => {
if (this.isPrimaryKey(directive)) {
// Set the table's primary key using the @key definition.
this.replacePrimaryKey(definition, directive, ctx);
} else {
// Append a GSI/LSI to the table configuration.
this.appendSecondaryIndex(definition, directive, ctx);
}
};
/**
* Augment the table key structures based on the @key.
*/
object = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerContext) => {
this.validate(definition, directive, ctx);
this.updateIndexStructures(definition, directive, ctx);
this.updateSchema(definition, directive, ctx);
this.updateResolvers(definition, directive, ctx);
this.addKeyConditionInputs(definition, directive, ctx);
};
/**
* Update the structural components of the schema that are relevant to the new index structures.
*
* Updates:
* 1. getX with new primary key information.
* 2. listX with new primary key information.
*
* Creates:
* 1. A query field for each secondary index.
*/
private updateSchema = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerContext) => {
this.updateQueryFields(definition, directive, ctx);
this.updateInputObjects(definition, directive, ctx);
};
/**
* Update the existing @model table's index structures. Includes primary key, GSI, and LSIs.
* @param definition The object type definition node.
* @param directive The @key directive
* @param ctx The transformer context
*/
private updateIndexStructures = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerContext) => {
if (this.isPrimaryKey(directive)) {
// Set the table's primary key using the @key definition.
this.replacePrimaryKey(definition, directive, ctx);
} else {
// Append a GSI/LSI to the table configuration.
this.appendSecondaryIndex(definition, directive, ctx);
}
/**
* Update the get, list, create, update, and delete resolvers with updated key information.
*/
private updateResolvers = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerContext) => {
const directiveArgs: KeyArguments = getDirectiveArguments(directive);
const getResolver = ctx.getResource(ResolverResourceIDs.DynamoDBGetResolverResourceID(definition.name.value));
const listResolver = ctx.getResource(ResolverResourceIDs.DynamoDBListResolverResourceID(definition.name.value));
const createResolver = ctx.getResource(ResolverResourceIDs.DynamoDBCreateResolverResourceID(definition.name.value));
const updateResolver = ctx.getResource(ResolverResourceIDs.DynamoDBUpdateResolverResourceID(definition.name.value));
const deleteResolver = ctx.getResource(ResolverResourceIDs.DynamoDBDeleteResolverResourceID(definition.name.value));
if (this.isPrimaryKey(directive)) {
// When looking at a primary key we update the primary paths for writing/reading data.
// and ensure any composite sort keys for the primary index.
if (getResolver) {
getResolver.Properties.RequestMappingTemplate = joinSnippets([
this.setKeySnippet(directive),
getResolver.Properties.RequestMappingTemplate,
]);
}
if (listResolver) {
listResolver.Properties.RequestMappingTemplate = joinSnippets([
print(setQuerySnippet(definition, directive, ctx)),
listResolver.Properties.RequestMappingTemplate,
]);
}
if (createResolver) {
createResolver.Properties.RequestMappingTemplate = joinSnippets([
this.setKeySnippet(directive, true),
ensureCompositeKeySnippet(directive),
createResolver.Properties.RequestMappingTemplate,
]);
}
if (updateResolver) {
updateResolver.Properties.RequestMappingTemplate = joinSnippets([
this.setKeySnippet(directive, true),
ensureCompositeKeySnippet(directive),
updateResolver.Properties.RequestMappingTemplate,
]);
}
if (deleteResolver) {
deleteResolver.Properties.RequestMappingTemplate = joinSnippets([
this.setKeySnippet(directive, true),
deleteResolver.Properties.RequestMappingTemplate,
]);
}
} else {
// When looking at a secondary key we need to ensure any composite sort key values
// and validate update operations to protect the integrity of composite sort keys.
if (createResolver) {
createResolver.Properties.RequestMappingTemplate = joinSnippets([
ensureCompositeKeySnippet(directive),
createResolver.Properties.RequestMappingTemplate,
]);
}
if (updateResolver) {
updateResolver.Properties.RequestMappingTemplate = joinSnippets([
this.validateKeyUpdateArgumentsSnippet(directive),
ensureCompositeKeySnippet(directive),
updateResolver.Properties.RequestMappingTemplate,
]);
}
if (deleteResolver) {
deleteResolver.Properties.RequestMappingTemplate = joinSnippets([
ensureCompositeKeySnippet(directive),
deleteResolver.Properties.RequestMappingTemplate,
]);
}
if (directiveArgs.queryField) {
const queryTypeName = ctx.getQueryTypeName();
const queryResolverId = ResolverResourceIDs.ResolverResourceID(queryTypeName, directiveArgs.queryField);
const queryResolver = makeQueryResolver(definition, directive, ctx);
ctx.mapResourceToStack(definition.name.value, queryResolverId);
ctx.setResource(queryResolverId, queryResolver);
}
}
};
/**
* Update the structural components of the schema that are relevant to the new index structures.
*
* Updates:
* 1. getX with new primary key information.
* 2. listX with new primary key information.
*
* Creates:
* 1. A query field for each secondary index.
*/
private updateSchema = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerContext) => {
this.updateQueryFields(definition, directive, ctx);
this.updateInputObjects(definition, directive, ctx);
}
/**
* Update the get, list, create, update, and delete resolvers with updated key information.
*/
private updateResolvers = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerContext) => {
const directiveArgs: KeyArguments = getDirectiveArguments(directive);
const getResolver = ctx.getResource(ResolverResourceIDs.DynamoDBGetResolverResourceID(definition.name.value));
const listResolver = ctx.getResource(ResolverResourceIDs.DynamoDBListResolverResourceID(definition.name.value));
const createResolver = ctx.getResource(ResolverResourceIDs.DynamoDBCreateResolverResourceID(definition.name.value));
const updateResolver = ctx.getResource(ResolverResourceIDs.DynamoDBUpdateResolverResourceID(definition.name.value));
const deleteResolver = ctx.getResource(ResolverResourceIDs.DynamoDBDeleteResolverResourceID(definition.name.value));
if (this.isPrimaryKey(directive)) {
// When looking at a primary key we update the primary paths for writing/reading data.
// and ensure any composite sort keys for the primary index.
if (getResolver) {
getResolver.Properties.RequestMappingTemplate = joinSnippets([
this.setKeySnippet(directive),
getResolver.Properties.RequestMappingTemplate
]);
}
if (listResolver) {
listResolver.Properties.RequestMappingTemplate = joinSnippets([
print(setQuerySnippet(definition, directive, ctx)),
listResolver.Properties.RequestMappingTemplate
]);
}
if (createResolver) {
createResolver.Properties.RequestMappingTemplate = joinSnippets([
this.setKeySnippet(directive, true),
ensureCompositeKeySnippet(directive),
createResolver.Properties.RequestMappingTemplate
]);
}
if (updateResolver) {
updateResolver.Properties.RequestMappingTemplate = joinSnippets([
this.setKeySnippet(directive, true),
ensureCompositeKeySnippet(directive),
updateResolver.Properties.RequestMappingTemplate
]);
}
if (deleteResolver) {
deleteResolver.Properties.RequestMappingTemplate = joinSnippets([
this.setKeySnippet(directive, true),
deleteResolver.Properties.RequestMappingTemplate
]);
}
private addKeyConditionInputs = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerContext) => {
const args: KeyArguments = getDirectiveArguments(directive);
if (args.fields.length > 2) {
const compositeKeyFieldNames = args.fields.slice(1);
// To make sure we get the intended behavior and type conversion we have to keep the order of the fields
// as it is in the key field list
const compositeKeyFields = [];
for (const compositeKeyFieldName of compositeKeyFieldNames) {
const field = definition.fields.find(field => field.name.value === compositeKeyFieldName);
if (!field) {
throw new InvalidDirectiveError(
`Can't find field: ${compositeKeyFieldName} in ${definition.name.value}, but it was specified in the @key definition.`
);
} else {
// When looking at a secondary key we need to ensure any composite sort key values
// and validate update operations to protect the integrity of composite sort keys.
if (createResolver) {
createResolver.Properties.RequestMappingTemplate = joinSnippets([
ensureCompositeKeySnippet(directive),
createResolver.Properties.RequestMappingTemplate
]);
}
if (updateResolver) {
updateResolver.Properties.RequestMappingTemplate = joinSnippets([
this.validateKeyUpdateArgumentsSnippet(directive),
ensureCompositeKeySnippet(directive),
updateResolver.Properties.RequestMappingTemplate
]);
}
if (deleteResolver) {
deleteResolver.Properties.RequestMappingTemplate = joinSnippets([
ensureCompositeKeySnippet(directive),
deleteResolver.Properties.RequestMappingTemplate
]);
}
if (directiveArgs.queryField) {
const queryTypeName = ctx.getQueryTypeName();
const queryResolverId = ResolverResourceIDs.ResolverResourceID(queryTypeName, directiveArgs.queryField);
const queryResolver = makeQueryResolver(definition, directive, ctx);
ctx.mapResourceToStack(definition.name.value, queryResolverId);
ctx.setResource(queryResolverId, queryResolver);
}
compositeKeyFields.push(field);
}
}
}
const keyName = toUpper(args.name || 'Primary');
const keyConditionInput = makeCompositeKeyConditionInputForKey(definition.name.value, keyName, compositeKeyFields);
if (!ctx.getType(keyConditionInput.name.value)) {
ctx.addInput(keyConditionInput);
}
const compositeKeyInput = makeCompositeKeyInputForKey(definition.name.value, keyName, compositeKeyFields);
if (!ctx.getType(compositeKeyInput.name.value)) {
ctx.addInput(compositeKeyInput);
}
} else if (args.fields.length === 2) {
const finalSortKeyFieldName = args.fields[1];
const finalSortKeyField = definition.fields.find(f => f.name.value === finalSortKeyFieldName);
const typeResolver = (baseType: string) => {
const resolvedEnumType = ctx.getType(baseType) as EnumTypeDefinitionNode;
return resolvedEnumType ? 'String' : undefined;
};
const sortKeyConditionInput = makeScalarKeyConditionForType(finalSortKeyField.type, typeResolver);
private addKeyConditionInputs = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerContext) => {
const args: KeyArguments = getDirectiveArguments(directive);
if (args.fields.length > 2) {
const compositeKeyFieldNames = args.fields.slice(1);
// To make sure we get the intended behavior and type conversion we have to keep the order of the fields
// as it is in the key field list
const compositeKeyFields = [];
for (const compositeKeyFieldName of compositeKeyFieldNames) {
const field = definition.fields.find(field => field.name.value === compositeKeyFieldName);
if (!field) {
throw new InvalidDirectiveError(`Can't find field: ${compositeKeyFieldName} in ${definition.name.value}, but it was specified in the @key definition.`);
} else {
compositeKeyFields.push(field);
}
}
const keyName = toUpper(args.name || 'Primary');
const keyConditionInput = makeCompositeKeyConditionInputForKey(definition.name.value, keyName, compositeKeyFields);
if (!ctx.getType(keyConditionInput.name.value)) {
ctx.addInput(keyConditionInput);
}
const compositeKeyInput = makeCompositeKeyInputForKey(definition.name.value, keyName, compositeKeyFields);
if (!ctx.getType(compositeKeyInput.name.value)) {
ctx.addInput(compositeKeyInput);
}
} else if (args.fields.length === 2) {
const finalSortKeyFieldName = args.fields[1];
const finalSortKeyField = definition.fields.find(f => f.name.value === finalSortKeyFieldName);
const typeResolver = (baseType: string) => {
const resolvedEnumType = ctx.getType(baseType) as EnumTypeDefinitionNode;
return resolvedEnumType ? 'String' : undefined;
};
const sortKeyConditionInput = makeScalarKeyConditionForType(finalSortKeyField.type, typeResolver);
if (!sortKeyConditionInput) {
const checkedKeyName = args.name ? args.name : '<unnamed>';
throw new InvalidDirectiveError(
`Cannot resolve type for field '${finalSortKeyFieldName}' in @key '${checkedKeyName}' on type '${definition.name.value}'.`
);
}
if (!sortKeyConditionInput) {
const checkedKeyName = args.name ? args.name : "<unnamed>";
throw new InvalidDirectiveError(`Cannot resolve type for field '${finalSortKeyFieldName}' in @key '${checkedKeyName}' on type '${definition.name.value}'.`);
}
if (!ctx.getType(sortKeyConditionInput.name.value)) {
ctx.addInput(sortKeyConditionInput);
}
}
if (!ctx.getType(sortKeyConditionInput.name.value)) {
ctx.addInput(sortKeyConditionInput);
}
}
};
/**
* Updates query fields to include any arguments required by the key structures.
* @param definition The object type definition node.
* @param directive The @key directive
* @param ctx The transformer context
*/
private updateQueryFields = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerContext) => {
this.updateGetField(definition, directive, ctx);
this.updateListField(definition, directive, ctx);
this.ensureQueryField(definition, directive, ctx);
}
/**
* Updates query fields to include any arguments required by the key structures.
* @param definition The object type definition node.
* @param directive The @key directive
* @param ctx The transformer context
*/
private updateQueryFields = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerContext) => {
this.updateGetField(definition, directive, ctx);
this.updateListField(definition, directive, ctx);
this.ensureQueryField(definition, directive, ctx);
};
// If the get field exists, update its arguments with primary key information.
private updateGetField = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerContext) => {
let query = ctx.getQuery();
const getResourceID = ResolverResourceIDs.DynamoDBGetResolverResourceID(definition.name.value);
const getResolverResource = ctx.getResource(getResourceID);
if (getResolverResource && this.isPrimaryKey(directive)) {
// By default takes a single argument named 'id'. Replace it with the updated primary key structure.
let getField: FieldDefinitionNode = query.fields.find(field => field.name.value === getResolverResource.Properties.FieldName) as FieldDefinitionNode;
const args: KeyArguments = getDirectiveArguments(directive);
const getArguments = args.fields.map(keyAttributeName => {
const keyField = definition.fields.find(field => field.name.value === keyAttributeName);
const keyArgument = makeInputValueDefinition(keyAttributeName, makeNonNullType(makeNamedType(getBaseType(keyField.type))));
return keyArgument;
})
getField = { ...getField, arguments: getArguments };
query = { ...query, fields: query.fields.map(field => field.name.value === getField.name.value ? getField : field)}
ctx.putType(query);
}
// If the get field exists, update its arguments with primary key information.
private updateGetField = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerContext) => {
let query = ctx.getQuery();
const getResourceID = ResolverResourceIDs.DynamoDBGetResolverResourceID(definition.name.value);
const getResolverResource = ctx.getResource(getResourceID);
if (getResolverResource && this.isPrimaryKey(directive)) {
// By default takes a single argument named 'id'. Replace it with the updated primary key structure.
let getField: FieldDefinitionNode = query.fields.find(
field => field.name.value === getResolverResource.Properties.FieldName
) as FieldDefinitionNode;
const args: KeyArguments = getDirectiveArguments(directive);
const getArguments = args.fields.map(keyAttributeName => {
const keyField = definition.fields.find(field => field.name.value === keyAttributeName);
const keyArgument = makeInputValueDefinition(keyAttributeName, makeNonNullType(makeNamedType(getBaseType(keyField.type))));
return keyArgument;
});
getField = { ...getField, arguments: getArguments };
query = { ...query, fields: query.fields.map(field => (field.name.value === getField.name.value ? getField : field)) };
ctx.putType(query);
}
};
// If the list field exists, update its arguments with primary key information.
private updateListField = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerContext) => {
const listResourceID = ResolverResourceIDs.DynamoDBListResolverResourceID(definition.name.value);
const listResolverResource = ctx.getResource(listResourceID);
if (listResolverResource && this.isPrimaryKey(directive)) {
// By default takes a single argument named 'id'. Replace it with the updated primary key structure.
let query = ctx.getQuery();
let listField: FieldDefinitionNode = query.fields.find(field => field.name.value === listResolverResource.Properties.FieldName) as FieldDefinitionNode;
let listArguments: InputValueDefinitionNode[] = [ ...listField.arguments ];
const args: KeyArguments = getDirectiveArguments(directive);
if (args.fields.length > 2) {
listArguments = addCompositeSortKey(definition, args, listArguments);
listArguments = addHashField(definition, args, listArguments);
} else if (args.fields.length === 2) {
listArguments = addSimpleSortKey(ctx, definition, args, listArguments);
listArguments = addHashField(definition, args, listArguments);
} else {
listArguments = addHashField(definition, args, listArguments);
}
listArguments.push(makeInputValueDefinition('sortDirection', makeNamedType('ModelSortDirection')));
listField = { ...listField, arguments: listArguments };
query = { ...query, fields: query.fields.map(field => field.name.value === listField.name.value ? listField : field)}
ctx.putType(query);
}
// If the list field exists, update its arguments with primary key information.
private updateListField = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerContext) => {
const listResourceID = ResolverResourceIDs.DynamoDBListResolverResourceID(definition.name.value);
const listResolverResource = ctx.getResource(listResourceID);
if (listResolverResource && this.isPrimaryKey(directive)) {
// By default takes a single argument named 'id'. Replace it with the updated primary key structure.
let query = ctx.getQuery();
let listField: FieldDefinitionNode = query.fields.find(
field => field.name.value === listResolverResource.Properties.FieldName
) as FieldDefinitionNode;
let listArguments: InputValueDefinitionNode[] = [...listField.arguments];
const args: KeyArguments = getDirectiveArguments(directive);
if (args.fields.length > 2) {
listArguments = addCompositeSortKey(definition, args, listArguments);
listArguments = addHashField(definition, args, listArguments);
} else if (args.fields.length === 2) {
listArguments = addSimpleSortKey(ctx, definition, args, listArguments);
listArguments = addHashField(definition, args, listArguments);
} else {
listArguments = addHashField(definition, args, listArguments);
}
listArguments.push(makeInputValueDefinition('sortDirection', makeNamedType('ModelSortDirection')));
listField = { ...listField, arguments: listArguments };
query = { ...query, fields: query.fields.map(field => (field.name.value === listField.name.value ? listField : field)) };
ctx.putType(query);
}
};
// If this is a secondary key and a queryField has been provided, create the query field.
private ensureQueryField = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerContext) => {
const args: KeyArguments = getDirectiveArguments(directive);
if (args.queryField && !this.isPrimaryKey(directive)) {
let queryType = ctx.getQuery();
let queryArguments = [];
if (args.fields.length > 2) {
queryArguments = addCompositeSortKey(definition, args, queryArguments);
queryArguments = addHashField(definition, args, queryArguments);
} else if (args.fields.length === 2) {
queryArguments = addSimpleSortKey(ctx, definition, args, queryArguments);
queryArguments = addHashField(definition, args, queryArguments);
} else {
queryArguments = addHashField(definition, args, queryArguments);
}
queryArguments.push(makeInputValueDefinition('sortDirection', makeNamedType('ModelSortDirection')));
const queryField = makeConnectionField(args.queryField, definition.name.value, queryArguments);
queryType = {
...queryType,
fields: [...queryType.fields, queryField]
};
ctx.putType(queryType);
}
// If this is a secondary key and a queryField has been provided, create the query field.
private ensureQueryField = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerContext) => {
const args: KeyArguments = getDirectiveArguments(directive);
if (args.queryField && !this.isPrimaryKey(directive)) {
let queryType = ctx.getQuery();
let queryArguments = [];
if (args.fields.length > 2) {
queryArguments = addCompositeSortKey(definition, args, queryArguments);
queryArguments = addHashField(definition, args, queryArguments);
} else if (args.fields.length === 2) {
queryArguments = addSimpleSortKey(ctx, definition, args, queryArguments);
queryArguments = addHashField(definition, args, queryArguments);
} else {
queryArguments = addHashField(definition, args, queryArguments);
}
queryArguments.push(makeInputValueDefinition('sortDirection', makeNamedType('ModelSortDirection')));
const queryField = makeConnectionField(args.queryField, definition.name.value, queryArguments);
queryType = {
...queryType,
fields: [...queryType.fields, queryField],
};
ctx.putType(queryType);
}
};
// Update the create, update, and delete input objects to account for any changes to the primary key.
private updateInputObjects = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerContext) => {
if (this.isPrimaryKey(directive)) {
const directiveArgs: KeyArguments = getDirectiveArguments(directive);
const createInput = ctx.getType(ModelResourceIDs.ModelCreateInputObjectName(definition.name.value)) as InputObjectTypeDefinitionNode;
if (createInput) {
ctx.putType(replaceCreateInput(definition, createInput, directiveArgs.fields));
}
const updateInput = ctx.getType(ModelResourceIDs.ModelUpdateInputObjectName(definition.name.value)) as InputObjectTypeDefinitionNode;
if (updateInput) {
ctx.putType(replaceUpdateInput(definition, updateInput, directiveArgs.fields));
}
const deleteInput = ctx.getType(ModelResourceIDs.ModelDeleteInputObjectName(definition.name.value)) as InputObjectTypeDefinitionNode;
if (deleteInput) {
ctx.putType(replaceDeleteInput(definition, deleteInput, directiveArgs.fields));
}
}
// Update the create, update, and delete input objects to account for any changes to the primary key.
private updateInputObjects = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerContext) => {
if (this.isPrimaryKey(directive)) {
const directiveArgs: KeyArguments = getDirectiveArguments(directive);
const createInput = ctx.getType(ModelResourceIDs.ModelCreateInputObjectName(definition.name.value)) as InputObjectTypeDefinitionNode;
if (createInput) {
ctx.putType(replaceCreateInput(definition, createInput, directiveArgs.fields));
}
const updateInput = ctx.getType(ModelResourceIDs.ModelUpdateInputObjectName(definition.name.value)) as InputObjectTypeDefinitionNode;
if (updateInput) {
ctx.putType(replaceUpdateInput(definition, updateInput, directiveArgs.fields));
}
const deleteInput = ctx.getType(ModelResourceIDs.ModelDeleteInputObjectName(definition.name.value)) as InputObjectTypeDefinitionNode;
if (deleteInput) {
ctx.putType(replaceDeleteInput(definition, deleteInput, directiveArgs.fields));
}
}
};
// Return a VTL snippet that sets the key for key for get, update, and delete operations.
private setKeySnippet = (directive: DirectiveNode, isMutation: boolean = false) => {
const directiveArgs = getDirectiveArguments(directive);
const cmds: Expression[] = [set(
ref(ResourceConstants.SNIPPETS.ModelObjectKey),
modelObjectKey(directiveArgs, isMutation)
)];
return printBlock(`Set the primary @key`)(compoundExpression(cmds));
}
// Return a VTL snippet that sets the key for key for get, update, and delete operations.
private setKeySnippet = (directive: DirectiveNode, isMutation: boolean = false) => {
const directiveArgs = getDirectiveArguments(directive);
const cmds: Expression[] = [set(ref(ResourceConstants.SNIPPETS.ModelObjectKey), modelObjectKey(directiveArgs, isMutation))];
return printBlock(`Set the primary @key`)(compoundExpression(cmds));
};
// When issuing an update mutation that changes one part of a composite sort key,
// you must supply the entire key so that the underlying composite key can be resaved
// in the update operation. We only need to update for composite sort keys on secondary indexes.
private validateKeyUpdateArgumentsSnippet = (directive: DirectiveNode): string => {
const directiveArgs: KeyArguments = getDirectiveArguments(directive);
if (!this.isPrimaryKey(directive) && directiveArgs.fields.length > 2) {
const sortKeyFields = directiveArgs.fields.slice(1);
return printBlock(`Validate update mutation for @key '${directiveArgs.name}'`)(compoundExpression([
set(ref('hasSeenSomeKeyArg'), bool(false)),
set(ref('keyFieldNames'), list(sortKeyFields.map(f => str(f)))),
forEach(ref('keyFieldName'), ref('keyFieldNames'), [
iff(
raw(`$ctx.args.input.containsKey("$keyFieldName")`),
set(ref('hasSeenSomeKeyArg'), bool(true)),
true
)
]),
forEach(ref('keyFieldName'), ref('keyFieldNames'), [
iff(
raw(`$hasSeenSomeKeyArg && !$ctx.args.input.containsKey("$keyFieldName")`),
raw(`$util.error("When updating any part of the composite sort key for @key '${directiveArgs.name}',` +
` you must provide all fields for the key. Missing key: '$keyFieldName'.")`)
)
])
]));
}
return '';
// When issuing an update mutation that changes one part of a composite sort key,
// you must supply the entire key so that the underlying composite key can be resaved
// in the update operation. We only need to update for composite sort keys on secondary indexes.
private validateKeyUpdateArgumentsSnippet = (directive: DirectiveNode): string => {
const directiveArgs: KeyArguments = getDirectiveArguments(directive);
if (!this.isPrimaryKey(directive) && directiveArgs.fields.length > 2) {
const sortKeyFields = directiveArgs.fields.slice(1);
return printBlock(`Validate update mutation for @key '${directiveArgs.name}'`)(
compoundExpression([
set(ref('hasSeenSomeKeyArg'), bool(false)),
set(ref('keyFieldNames'), list(sortKeyFields.map(f => str(f)))),
forEach(ref('keyFieldName'), ref('keyFieldNames'), [
iff(raw(`$ctx.args.input.containsKey("$keyFieldName")`), set(ref('hasSeenSomeKeyArg'), bool(true)), true),
]),
forEach(ref('keyFieldName'), ref('keyFieldNames'), [
iff(
raw(`$hasSeenSomeKeyArg && !$ctx.args.input.containsKey("$keyFieldName")`),
raw(
`$util.error("When updating any part of the composite sort key for @key '${directiveArgs.name}',` +
` you must provide all fields for the key. Missing key: '$keyFieldName'.")`
)
),
]),
])
);
}
return '';
};
/**
* Validates the directive usage is semantically valid.
*
* 1. There may only be 1 @key without a name (specifying the primary key)
* 2. There may only be 1 @key with a given name.
* 3. @key must only reference existing scalar fields that map to DynamoDB S, N, or B.
* 4. A primary key must not include a 'queryField'.
* 5. If there is no primary sort key, make sure there are no more LSIs.
* @param definition The object type definition node.
* @param directive The @key directive
* @param ctx The transformer context
*/
private validate = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerContext) => {
const directiveArgs = getDirectiveArguments(directive);
if (!directiveArgs.name) {
// 1. Make sure there are no more directives without a name.
for (const otherDirective of definition.directives.filter(d => d.name.value === 'key')) {
const otherArgs = getDirectiveArguments(otherDirective);
if (otherDirective !== directive && !otherArgs.name) {
throw new InvalidDirectiveError(`You may only supply one primary @key on type '${definition.name.value}'.`);
}
// 5. If there is no primary sort key, make sure there are no more LSIs.
const hasPrimarySortKey = directiveArgs.fields.length > 1;
const primaryHashField = directiveArgs.fields[0];
const otherHashField = otherArgs.fields[0];
if (
otherDirective !== directive &&
!hasPrimarySortKey &&
// If the primary key and other key share the first field and are not the same directive it is an LSI.
primaryHashField === otherHashField
) {
throw new InvalidDirectiveError(
`Invalid @key "${otherArgs.name}". You may not create a @key where the first field in 'fields' ` +
`is the same as that of the primary @key unless the primary @key has multiple 'fields'. ` +
`You cannot have a local secondary index without a sort key in the primary index.`
);
}
}
// 4. Make sure that a 'queryField' is not included on a primary @key.
if (directiveArgs.queryField) {
throw new InvalidDirectiveError(`You cannot pass 'queryField' to the primary @key on type '${definition.name.value}'.`);
}
} else {
// 2. Make sure there are no more directives with the same name.
for (const otherDirective of definition.directives.filter(d => d.name.value === 'key')) {
const otherArgs = getDirectiveArguments(otherDirective);
if (otherDirective !== directive && otherArgs.name === directiveArgs.name) {
throw new InvalidDirectiveError(`You may only supply one @key with the name '${directiveArgs.name}' on type '${definition.name.value}'.`);
}
}
/**
* Validates the directive usage is semantically valid.
*
* 1. There may only be 1 @key without a name (specifying the primary key)
* 2. There may only be 1 @key with a given name.
* 3. @key must only reference existing scalar fields that map to DynamoDB S, N, or B.
* 4. A primary key must not include a 'queryField'.
* 5. If there is no primary sort key, make sure there are no more LSIs.
* @param definition The object type definition node.
* @param directive The @key directive
* @param ctx The transformer context
*/
private validate = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerContext) => {
const directiveArgs = getDirectiveArguments(directive);
if (!directiveArgs.name) {
// 1. Make sure there are no more directives without a name.
for (const otherDirective of definition.directives.filter(d => d.name.value === 'key')) {
const otherArgs = getDirectiveArguments(otherDirective);
if (otherDirective !== directive && !otherArgs.name) {
throw new InvalidDirectiveError(`You may only supply one primary @key on type '${definition.name.value}'.`);
}
// 3. Check that fields exists and are valid key types.
const fieldMap = new Map();
for (const field of definition.fields) {
fieldMap.set(field.name.value, field);
// 5. If there is no primary sort key, make sure there are no more LSIs.
const hasPrimarySortKey = directiveArgs.fields.length > 1;
const primaryHashField = directiveArgs.fields[0];
const otherHashField = otherArgs.fields[0];
if (
otherDirective !== directive &&
!hasPrimarySortKey &&
// If the primary key and other key share the first field and are not the same directive it is an LSI.
primaryHashField === otherHashField
) {
throw new InvalidDirectiveError(
`Invalid @key "${otherArgs.name}". You may not create a @key where the first field in 'fields' ` +
`is the same as that of the primary @key unless the primary @key has multiple 'fields'. ` +
`You cannot have a local secondary index without a sort key in the primary index.`
);
}
for (const fieldName of directiveArgs.fields) {
if (!fieldMap.has(fieldName)) {
const checkedKeyName = directiveArgs.name ? directiveArgs.name : "<unnamed>";
throw new InvalidDirectiveError(`You cannot specify a non-existant field '${fieldName}' in @key '${checkedKeyName}' on type '${definition.name.value}'.`);
} else {
const existingField = fieldMap.get(fieldName);
const ddbKeyType = attributeTypeFromType(existingField.type, ctx);
if (this.isPrimaryKey(directive) && !isNonNullType(existingField.type)) {
throw new InvalidDirectiveError(`The primary @key on type '${definition.name.value}' must reference non-null fields.`);
} else if (ddbKeyType !== 'S' && ddbKeyType !== 'N' && ddbKeyType !== 'B') {
throw new InvalidDirectiveError(`A @key on type '${definition.name.value}' cannot reference non-scalar field ${fieldName}.`);
}
}
}
// 4. Make sure that a 'queryField' is not included on a primary @key.
if (directiveArgs.queryField) {
throw new InvalidDirectiveError(`You cannot pass 'queryField' to the primary @key on type '${definition.name.value}'.`);
}
} else {
// 2. Make sure there are no more directives with the same name.
for (const otherDirective of definition.directives.filter(d => d.name.value === 'key')) {
const otherArgs = getDirectiveArguments(otherDirective);
if (otherDirective !== directive && otherArgs.name === directiveArgs.name) {
throw new InvalidDirectiveError(
`You may only supply one @key with the name '${directiveArgs.name}' on type '${definition.name.value}'.`
);
}
}
}
/**
* Returns true if the directive specifies a primary key.
* @param directive The directive node.
*/
isPrimaryKey = (directive: DirectiveNode) => {
const directiveArgs = getDirectiveArguments(directive);
return !Boolean(directiveArgs.name);
// 3. Check that fields exists and are valid key types.
const fieldMap = new Map();
for (const field of definition.fields) {
fieldMap.set(field.name.value, field);
}
for (const fieldName of directiveArgs.fields) {
if (!fieldMap.has(fieldName)) {
const checkedKeyName = directiveArgs.name ? directiveArgs.name : '<unnamed>';
throw new InvalidDirectiveError(
`You cannot specify a non-existant field '${fieldName}' in @key '${checkedKeyName}' on type '${definition.name.value}'.`
);
} else {
const existingField = fieldMap.get(fieldName);
const ddbKeyType = attributeTypeFromType(existingField.type, ctx);
if (this.isPrimaryKey(directive) && !isNonNullType(existingField.type)) {
throw new InvalidDirectiveError(`The primary @key on type '${definition.name.value}' must reference non-null fields.`);
} else if (ddbKeyType !== 'S' && ddbKeyType !== 'N' && ddbKeyType !== 'B') {
throw new InvalidDirectiveError(`A @key on type '${definition.name.value}' cannot reference non-scalar field ${fieldName}.`);
}
}
}
};
/**
* Replace the primary key schema with one defined by a @key.
* @param definition The object type definition node.
* @param directive The @key directive
* @param ctx The transformer context
*/
replacePrimaryKey = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerContext) => {
const args: KeyArguments = getDirectiveArguments(directive);
const ks = keySchema(args);
const attrDefs = attributeDefinitions(args, definition, ctx);
const tableLogicalID = ModelResourceIDs.ModelTableResourceID(definition.name.value);
const tableResource = ctx.getResource(tableLogicalID);
if (!tableResource) {
throw new InvalidDirectiveError(`The @key directive may only be added to object definitions annotated with @model.`);
} else {
// First remove any attribute definitions in the current primary key.
const existingAttrDefSet = new Set(tableResource.Properties.AttributeDefinitions.map(ad => ad.AttributeName));
for (const existingKey of tableResource.Properties.KeySchema) {
if (existingAttrDefSet.has(existingKey.AttributeName)) {
tableResource.Properties.AttributeDefinitions = tableResource.Properties.AttributeDefinitions.filter(ad => ad.AttributeName !== existingKey.AttributeName);
existingAttrDefSet.delete(existingKey.AttributeName);
}
}
// Then replace the KeySchema and add any new attribute definitions back.
tableResource.Properties.KeySchema = ks;
for (const attr of attrDefs) {
if (!existingAttrDefSet.has(attr.AttributeName)) {
tableResource.Properties.AttributeDefinitions.push(attr);
}
}
/**
* Returns true if the directive specifies a primary key.
* @param directive The directive node.
*/
isPrimaryKey = (directive: DirectiveNode) => {
const directiveArgs = getDirectiveArguments(directive);
return !Boolean(directiveArgs.name);
};
/**
* Replace the primary key schema with one defined by a @key.
* @param definition The object type definition node.
* @param directive The @key directive
* @param ctx The transformer context
*/
replacePrimaryKey = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerContext) => {
const args: KeyArguments = getDirectiveArguments(directive);
const ks = keySchema(args);
const attrDefs = attributeDefinitions(args, definition, ctx);
const tableLogicalID = ModelResourceIDs.ModelTableResourceID(definition.name.value);
const tableResource = ctx.getResource(tableLogicalID);
if (!tableResource) {
throw new InvalidDirectiveError(`The @key directive may only be added to object definitions annotated with @model.`);
} else {
// First remove any attribute definitions in the current primary key.
const existingAttrDefSet = new Set(tableResource.Properties.AttributeDefinitions.map(ad => ad.AttributeName));
for (const existingKey of tableResource.Properties.KeySchema) {
if (existingAttrDefSet.has(existingKey.AttributeName)) {
tableResource.Properties.AttributeDefinitions = tableResource.Properties.AttributeDefinitions.filter(
ad => ad.AttributeName !== existingKey.AttributeName
);
existingAttrDefSet.delete(existingKey.AttributeName);
}
}
// Then replace the KeySchema and add any new attribute definitions back.
tableResource.Properties.KeySchema = ks;
for (const attr of attrDefs) {
if (!existingAttrDefSet.has(attr.AttributeName)) {
tableResource.Properties.AttributeDefinitions.push(attr);
}
}
}
};
/**
* Add a LSI or GSI to the table as defined by a @key.
* @param definition The object type definition node.
* @param directive The @key directive
* @param ctx The transformer context
*/
appendSecondaryIndex = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerContext) => {
const args: KeyArguments = getDirectiveArguments(directive);
const ks = keySchema(args);
const attrDefs = attributeDefinitions(args, definition, ctx);
const tableLogicalID = ModelResourceIDs.ModelTableResourceID(definition.name.value);
const tableResource = ctx.getResource(tableLogicalID);
const primaryKeyDirective = getPrimaryKey(definition);
const primaryPartitionKeyName = primaryKeyDirective ? getDirectiveArguments(primaryKeyDirective).fields[0] : 'id';
if (!tableResource) {
throw new InvalidDirectiveError(`The @key directive may only be added to object definitions annotated with @model.`);
} else {
const baseIndexProperties = {
IndexName: args.name,
KeySchema: ks,
Projection: new Projection({
ProjectionType: 'ALL'
})
};
if (primaryPartitionKeyName === ks[0].AttributeName) {
// This is an LSI.
// Add the new secondary index and update the table's attribute definitions.
tableResource.Properties.LocalSecondaryIndexes = append(
tableResource.Properties.LocalSecondaryIndexes,
new LocalSecondaryIndex(baseIndexProperties)
)
} else {
// This is a GSI.
// Add the new secondary index and update the table's attribute definitions.
tableResource.Properties.GlobalSecondaryIndexes = append(
tableResource.Properties.GlobalSecondaryIndexes,
new GlobalSecondaryIndex({
...baseIndexProperties,
ProvisionedThroughput: Fn.If(
ResourceConstants.CONDITIONS.ShouldUsePayPerRequestBilling,
Refs.NoValue,
{
ReadCapacityUnits: Fn.Ref(ResourceConstants.PARAMETERS.DynamoDBModelTableReadIOPS),
WriteCapacityUnits: Fn.Ref(ResourceConstants.PARAMETERS.DynamoDBModelTableWriteIOPS)
}
) as any,
})
)
}
const existingAttrDefSet = new Set(tableResource.Properties.AttributeDefinitions.map(ad => ad.AttributeName));
for (const attr of attrDefs) {
if (!existingAttrDefSet.has(attr.AttributeName)) {
tableResource.Properties.AttributeDefinitions.push(attr);
}
}
/**
* Add a LSI or GSI to the table as defined by a @key.
* @param definition The object type definition node.
* @param directive The @key directive
* @param ctx The transformer context
*/
appendSecondaryIndex = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerContext) => {
const args: KeyArguments = getDirectiveArguments(directive);
const ks = keySchema(args);
const attrDefs = attributeDefinitions(args, definition, ctx);
const tableLogicalID = ModelResourceIDs.ModelTableResourceID(definition.name.value);
const tableResource = ctx.getResource(tableLogicalID);
const primaryKeyDirective = getPrimaryKey(definition);
const primaryPartitionKeyName = primaryKeyDirective ? getDirectiveArguments(primaryKeyDirective).fields[0] : 'id';
if (!tableResource) {
throw new InvalidDirectiveError(`The @key directive may only be added to object definitions annotated with @model.`);
} else {
const baseIndexProperties = {
IndexName: args.name,
KeySchema: ks,
Projection: new Projection({
ProjectionType: 'ALL',
}),
};
if (primaryPartitionKeyName === ks[0].AttributeName) {
// This is an LSI.
// Add the new secondary index and update the table's attribute definitions.
tableResource.Properties.LocalSecondaryIndexes = append(
tableResource.Properties.LocalSecondaryIndexes,
new LocalSecondaryIndex(baseIndexProperties)
);
} else {
// This is a GSI.
// Add the new secondary index and update the table's attribute definitions.
tableResource.Properties.GlobalSecondaryIndexes = append(
tableResource.Properties.GlobalSecondaryIndexes,
new GlobalSecondaryIndex({
...baseIndexProperties,
ProvisionedThroughput: Fn.If(ResourceConstants.CONDITIONS.ShouldUsePayPerRequestBilling, Refs.NoValue, {
ReadCapacityUnits: Fn.Ref(ResourceConstants.PARAMETERS.DynamoDBModelTableReadIOPS),
WriteCapacityUnits: Fn.Ref(ResourceConstants.PARAMETERS.DynamoDBModelTableWriteIOPS),
}) as any,
})
);
}
const existingAttrDefSet = new Set(tableResource.Properties.AttributeDefinitions.map(ad => ad.AttributeName));
for (const attr of attrDefs) {
if (!existingAttrDefSet.has(attr.AttributeName)) {
tableResource.Properties.AttributeDefinitions.push(attr);
}
}
}
};
}

@@ -521,20 +567,17 @@

function keySchema(args: KeyArguments) {
if (args.fields.length > 1) {
const condensedSortKey = condenseRangeKey(args.fields.slice(1));
return [
{ AttributeName: args.fields[0], KeyType: 'HASH' },
{ AttributeName: condensedSortKey, KeyType: 'RANGE' },
];
} else {
return [{ AttributeName: args.fields[0], KeyType: 'HASH' }];
}
if (args.fields.length > 1) {
const condensedSortKey = condenseRangeKey(args.fields.slice(1));
return [{ AttributeName: args.fields[0], KeyType: 'HASH' }, { AttributeName: condensedSortKey, KeyType: 'RANGE' }];
} else {
return [{ AttributeName: args.fields[0], KeyType: 'HASH' }];
}
}
function attributeTypeFromType(type: TypeNode, ctx: TransformerContext) {
const baseTypeName = getBaseType(type);
const ofType = ctx.getType(baseTypeName);
if (ofType && ofType.kind === Kind.ENUM_TYPE_DEFINITION) {
return 'S';
}
return attributeTypeFromScalar(type);
const baseTypeName = getBaseType(type);
const ofType = ctx.getType(baseTypeName);
if (ofType && ofType.kind === Kind.ENUM_TYPE_DEFINITION) {
return 'S';
}
return attributeTypeFromScalar(type);
}

@@ -548,85 +591,95 @@

function attributeDefinitions(args: KeyArguments, def: ObjectTypeDefinitionNode, ctx: TransformerContext) {
const fieldMap = new Map();
for (const field of def.fields) {
fieldMap.set(field.name.value, field);
}
if (args.fields.length > 2) {
const hashName = args.fields[0];
const condensedSortKey = condenseRangeKey(args.fields.slice(1));
return [
{ AttributeName: hashName, AttributeType: attributeTypeFromType(fieldMap.get(hashName).type, ctx) },
{ AttributeName: condensedSortKey, AttributeType: 'S' },
];
} else if (args.fields.length === 2) {
const hashName = args.fields[0];
const sortName = args.fields[1];
return [
{ AttributeName: hashName, AttributeType: attributeTypeFromType(fieldMap.get(hashName).type, ctx) },
{ AttributeName: sortName, AttributeType: attributeTypeFromType(fieldMap.get(sortName).type, ctx) },
];
} else {
const fieldName = args.fields[0];
return [{ AttributeName: fieldName, AttributeType: attributeTypeFromType(fieldMap.get(fieldName).type, ctx) }];
}
const fieldMap = new Map();
for (const field of def.fields) {
fieldMap.set(field.name.value, field);
}
if (args.fields.length > 2) {
const hashName = args.fields[0];
const condensedSortKey = condenseRangeKey(args.fields.slice(1));
return [
{ AttributeName: hashName, AttributeType: attributeTypeFromType(fieldMap.get(hashName).type, ctx) },
{ AttributeName: condensedSortKey, AttributeType: 'S' },
];
} else if (args.fields.length === 2) {
const hashName = args.fields[0];
const sortName = args.fields[1];
return [
{ AttributeName: hashName, AttributeType: attributeTypeFromType(fieldMap.get(hashName).type, ctx) },
{ AttributeName: sortName, AttributeType: attributeTypeFromType(fieldMap.get(sortName).type, ctx) },
];
} else {
const fieldName = args.fields[0];
return [{ AttributeName: fieldName, AttributeType: attributeTypeFromType(fieldMap.get(fieldName).type, ctx) }];
}
}
function append<T>(maybeList: T[] | undefined, item: T) {
if (maybeList) {
return [...maybeList, item];
}
return [item];
if (maybeList) {
return [...maybeList, item];
}
return [item];
}
function getPrimaryKey(obj: ObjectTypeDefinitionNode): DirectiveNode | undefined {
for (const directive of obj.directives) {
if (directive.name.value === 'key' && !getDirectiveArguments(directive).name) {
return directive;
}
for (const directive of obj.directives) {
if (directive.name.value === 'key' && !getDirectiveArguments(directive).name) {
return directive;
}
}
}
function primaryIdFields(definition: ObjectTypeDefinitionNode, keyFields: string[]): InputValueDefinitionNode[] {
return keyFields.map(keyFieldName => {
const keyField: FieldDefinitionNode = definition.fields.find(field => field.name.value === keyFieldName);
return makeInputValueDefinition(keyFieldName, makeNonNullType(makeNamedType(getBaseType(keyField.type))));
})
return keyFields.map(keyFieldName => {
const keyField: FieldDefinitionNode = definition.fields.find(field => field.name.value === keyFieldName);
return makeInputValueDefinition(keyFieldName, makeNonNullType(makeNamedType(getBaseType(keyField.type))));
});
}
// Key fields are non-nullable, non-key fields follow what their @model declaration makes.
function replaceCreateInput(definition: ObjectTypeDefinitionNode, input: InputObjectTypeDefinitionNode, keyFields: string[]): InputObjectTypeDefinitionNode {
return {
...input,
fields: input.fields.reduce((acc, f) => {
// If the field is a key, make it non-null.
if (keyFields.find(k => k === f.name.value)) {
return [...acc, makeInputValueDefinition(f.name.value, makeNonNullType(makeNamedType(getBaseType(f.type))))];
}
return [...acc, f];
}, [])
};
};
function replaceCreateInput(
definition: ObjectTypeDefinitionNode,
input: InputObjectTypeDefinitionNode,
keyFields: string[]
): InputObjectTypeDefinitionNode {
return {
...input,
fields: input.fields.reduce((acc, f) => {
// If the field is a key, make it non-null.
if (keyFields.find(k => k === f.name.value)) {
return [...acc, makeInputValueDefinition(f.name.value, makeNonNullType(makeNamedType(getBaseType(f.type))))];
}
return [...acc, f];
}, []),
};
}
// Key fields are non-nullable, non-key fields are not non-nullable.
function replaceUpdateInput(definition: ObjectTypeDefinitionNode, input: InputObjectTypeDefinitionNode, keyFields: string[]): InputObjectTypeDefinitionNode {
return {
...input,
fields: input.fields.map(
f => {
if (keyFields.find(k => k === f.name.value)) {
return makeInputValueDefinition(f.name.value, wrapNonNull(withNamedNodeNamed(f.type, getBaseType(f.type))));
} else {
return f;
}
}
)
};
};
function replaceUpdateInput(
definition: ObjectTypeDefinitionNode,
input: InputObjectTypeDefinitionNode,
keyFields: string[]
): InputObjectTypeDefinitionNode {
return {
...input,
fields: input.fields.map(f => {
if (keyFields.find(k => k === f.name.value)) {
return makeInputValueDefinition(f.name.value, wrapNonNull(withNamedNodeNamed(f.type, getBaseType(f.type))));
} else {
return f;
}
}),
};
}
// Key fields are non-nullable, non-key fields are not non-nullable.
function replaceDeleteInput(definition: ObjectTypeDefinitionNode, input: InputObjectTypeDefinitionNode, keyFields: string[]): InputObjectTypeDefinitionNode {
return {
...input,
fields: primaryIdFields(definition, keyFields)
};
};
function replaceDeleteInput(
definition: ObjectTypeDefinitionNode,
input: InputObjectTypeDefinitionNode,
keyFields: string[]
): InputObjectTypeDefinitionNode {
return {
...input,
fields: primaryIdFields(definition, keyFields),
};
}

@@ -638,151 +691,153 @@ /**

function modelObjectKey(args: KeyArguments, isMutation: boolean) {
const argsPrefix = isMutation ?
'ctx.args.input' :
'ctx.args';
if (args.fields.length > 2) {
const rangeKeyFields = args.fields.slice(1);
const condensedSortKey = condenseRangeKey(rangeKeyFields);
const condensedSortKeyValue = condenseRangeKey(
rangeKeyFields.map(keyField => `\${${argsPrefix}.${keyField}}`)
);
return obj({
[args.fields[0]]: ref(`util.dynamodb.toDynamoDB($${argsPrefix}.${args.fields[0]})`),
[condensedSortKey]: ref(`util.dynamodb.toDynamoDB("${condensedSortKeyValue}")`)
});
} else if (args.fields.length === 2) {
return obj({
[args.fields[0]]: ref(`util.dynamodb.toDynamoDB($${argsPrefix}.${args.fields[0]})`),
[args.fields[1]]: ref(`util.dynamodb.toDynamoDB($${argsPrefix}.${args.fields[1]})`)
});
} else if (args.fields.length === 1) {
return obj({
[args.fields[0]]: ref(`util.dynamodb.toDynamoDB($${argsPrefix}.${args.fields[0]})`),
});
}
throw new InvalidDirectiveError('@key directives must include at least one field.');
const argsPrefix = isMutation ? 'ctx.args.input' : 'ctx.args';
if (args.fields.length > 2) {
const rangeKeyFields = args.fields.slice(1);
const condensedSortKey = condenseRangeKey(rangeKeyFields);
const condensedSortKeyValue = condenseRangeKey(rangeKeyFields.map(keyField => `\${${argsPrefix}.${keyField}}`));
return obj({
[args.fields[0]]: ref(`util.dynamodb.toDynamoDB($${argsPrefix}.${args.fields[0]})`),
[condensedSortKey]: ref(`util.dynamodb.toDynamoDB("${condensedSortKeyValue}")`),
});
} else if (args.fields.length === 2) {
return obj({
[args.fields[0]]: ref(`util.dynamodb.toDynamoDB($${argsPrefix}.${args.fields[0]})`),
[args.fields[1]]: ref(`util.dynamodb.toDynamoDB($${argsPrefix}.${args.fields[1]})`),
});
} else if (args.fields.length === 1) {
return obj({
[args.fields[0]]: ref(`util.dynamodb.toDynamoDB($${argsPrefix}.${args.fields[0]})`),
});
}
throw new InvalidDirectiveError('@key directives must include at least one field.');
}
function ensureCompositeKeySnippet(dir: DirectiveNode): string {
const args: KeyArguments = getDirectiveArguments(dir);
const argsPrefix = 'ctx.args.input';
if (args.fields.length > 2) {
const rangeKeyFields = args.fields.slice(1);
const condensedSortKey = condenseRangeKey(rangeKeyFields);
const dynamoDBFriendlySortKeyName = toCamelCase(rangeKeyFields.map(f => graphqlName(f)));
const condensedSortKeyValue = condenseRangeKey(
rangeKeyFields.map(keyField => `\${${argsPrefix}.${keyField}}`)
);
return print(compoundExpression([
ifElse(
raw(`$util.isNull($${ResourceConstants.SNIPPETS.DynamoDBNameOverrideMap})`),
set(ref(ResourceConstants.SNIPPETS.DynamoDBNameOverrideMap), obj({
[condensedSortKey]: str(dynamoDBFriendlySortKeyName)
})),
qref(`$${ResourceConstants.SNIPPETS.DynamoDBNameOverrideMap}.put("${condensedSortKey}", "${dynamoDBFriendlySortKeyName}")`)
),
qref(`$ctx.args.input.put("${condensedSortKey}","${condensedSortKeyValue}")`)
]));
}
return '';
const args: KeyArguments = getDirectiveArguments(dir);
const argsPrefix = 'ctx.args.input';
if (args.fields.length > 2) {
const rangeKeyFields = args.fields.slice(1);
const condensedSortKey = condenseRangeKey(rangeKeyFields);
const dynamoDBFriendlySortKeyName = toCamelCase(rangeKeyFields.map(f => graphqlName(f)));
const condensedSortKeyValue = condenseRangeKey(rangeKeyFields.map(keyField => `\${${argsPrefix}.${keyField}}`));
return print(
compoundExpression([
ifElse(
raw(`$util.isNull($${ResourceConstants.SNIPPETS.DynamoDBNameOverrideMap})`),
set(
ref(ResourceConstants.SNIPPETS.DynamoDBNameOverrideMap),
obj({
[condensedSortKey]: str(dynamoDBFriendlySortKeyName),
})
),
qref(`$${ResourceConstants.SNIPPETS.DynamoDBNameOverrideMap}.put("${condensedSortKey}", "${dynamoDBFriendlySortKeyName}")`)
),
qref(`$ctx.args.input.put("${condensedSortKey}","${condensedSortKeyValue}")`),
])
);
}
return '';
}
function condenseRangeKey(fields: string[]) {
return fields.join(ModelResourceIDs.ModelCompositeKeySeparator());
return fields.join(ModelResourceIDs.ModelCompositeKeySeparator());
}
function makeQueryResolver(definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerContext) {
const type = definition.name.value;
const directiveArgs: KeyArguments = getDirectiveArguments(directive);
const index = directiveArgs.name;
const fieldName = directiveArgs.queryField;
const queryTypeName = ctx.getQueryTypeName();
const defaultPageLimit = 10
const requestVariable = 'QueryRequest';
return new AppSync.Resolver({
ApiId: Fn.GetAtt(ResourceConstants.RESOURCES.GraphQLAPILogicalID, 'ApiId'),
DataSourceName: Fn.GetAtt(ModelResourceIDs.ModelTableDataSourceID(type), 'Name'),
FieldName: fieldName,
TypeName: queryTypeName,
RequestMappingTemplate: print(
compoundExpression([
setQuerySnippet(definition, directive, ctx),
set(ref('limit'),
ref(`util.defaultIfNull($context.args.limit, ${defaultPageLimit})`)),
set(
ref(requestVariable),
obj({
version: str('2017-02-28'),
operation: str('Query'),
limit: ref('limit'),
query: ref(ResourceConstants.SNIPPETS.ModelQueryExpression),
index: str(index)
})
),
ifElse(
raw(`!$util.isNull($ctx.args.sortDirection)
const type = definition.name.value;
const directiveArgs: KeyArguments = getDirectiveArguments(directive);
const index = directiveArgs.name;
const fieldName = directiveArgs.queryField;
const queryTypeName = ctx.getQueryTypeName();
const defaultPageLimit = 10;
const requestVariable = 'QueryRequest';
return new AppSync.Resolver({
ApiId: Fn.GetAtt(ResourceConstants.RESOURCES.GraphQLAPILogicalID, 'ApiId'),
DataSourceName: Fn.GetAtt(ModelResourceIDs.ModelTableDataSourceID(type), 'Name'),
FieldName: fieldName,
TypeName: queryTypeName,
RequestMappingTemplate: print(
compoundExpression([
setQuerySnippet(definition, directive, ctx),
set(ref('limit'), ref(`util.defaultIfNull($context.args.limit, ${defaultPageLimit})`)),
set(
ref(requestVariable),
obj({
version: str('2017-02-28'),
operation: str('Query'),
limit: ref('limit'),
query: ref(ResourceConstants.SNIPPETS.ModelQueryExpression),
index: str(index),
})
),
ifElse(
raw(`!$util.isNull($ctx.args.sortDirection)
&& $ctx.args.sortDirection == "DESC"`),
set(ref(`${requestVariable}.scanIndexForward`), bool(false)),
set(ref(`${requestVariable}.scanIndexForward`), bool(true)),
),
iff(
ref('context.args.nextToken'),
set(
ref(`${requestVariable}.nextToken`),
str('$context.args.nextToken')
),
true
),
iff(
ref('context.args.filter'),
set(
ref(`${requestVariable}.filter`),
ref('util.parseJson("$util.transform.toDynamoDBFilterExpression($ctx.args.filter)")')
),
true
),
raw(`$util.toJson($${requestVariable})`)
])
set(ref(`${requestVariable}.scanIndexForward`), bool(false)),
set(ref(`${requestVariable}.scanIndexForward`), bool(true))
),
ResponseMappingTemplate: print(
raw('$util.toJson($ctx.result)')
)
})
iff(ref('context.args.nextToken'), set(ref(`${requestVariable}.nextToken`), str('$context.args.nextToken')), true),
iff(
ref('context.args.filter'),
set(ref(`${requestVariable}.filter`), ref('util.parseJson("$util.transform.toDynamoDBFilterExpression($ctx.args.filter)")')),
true
),
raw(`$util.toJson($${requestVariable})`),
])
),
ResponseMappingTemplate: print(raw('$util.toJson($ctx.result)')),
});
}
function setQuerySnippet(definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerContext) {
const args: KeyArguments = getDirectiveArguments(directive);
const keys = args.fields;
const keyTypes = keys.map(k => {
const field = definition.fields.find(f => f.name.value === k);
return attributeTypeFromType(field.type, ctx);
})
return block(`Set query expression for @key`, [
set(ref(ResourceConstants.SNIPPETS.ModelQueryExpression), obj({})),
applyKeyExpressionForCompositeKey(keys, keyTypes, ResourceConstants.SNIPPETS.ModelQueryExpression)
])
const args: KeyArguments = getDirectiveArguments(directive);
const keys = args.fields;
const keyTypes = keys.map(k => {
const field = definition.fields.find(f => f.name.value === k);
return attributeTypeFromType(field.type, ctx);
});
return block(`Set query expression for @key`, [
set(ref(ResourceConstants.SNIPPETS.ModelQueryExpression), obj({})),
applyKeyExpressionForCompositeKey(keys, keyTypes, ResourceConstants.SNIPPETS.ModelQueryExpression),
]);
}
function addHashField(definition: ObjectTypeDefinitionNode, args: KeyArguments, elems: InputValueDefinitionNode[]): InputValueDefinitionNode[] {
let hashFieldName = args.fields[0];
const hashField = definition.fields.find(field => field.name.value === hashFieldName);
const hashKey = makeInputValueDefinition(hashFieldName, makeNamedType(getBaseType(hashField.type)));
return [hashKey, ...elems];
function addHashField(
definition: ObjectTypeDefinitionNode,
args: KeyArguments,
elems: InputValueDefinitionNode[]
): InputValueDefinitionNode[] {
let hashFieldName = args.fields[0];
const hashField = definition.fields.find(field => field.name.value === hashFieldName);
const hashKey = makeInputValueDefinition(hashFieldName, makeNamedType(getBaseType(hashField.type)));
return [hashKey, ...elems];
}
function addSimpleSortKey(ctx: TransformerContext, definition: ObjectTypeDefinitionNode, args: KeyArguments, elems: InputValueDefinitionNode[]): InputValueDefinitionNode[] {
let sortKeyName = args.fields[1];
const sortField = definition.fields.find(field => field.name.value === sortKeyName);
const baseType = getBaseType(sortField.type);
const resolvedTypeIfEnum = ctx.getType(baseType) as EnumTypeDefinitionNode ? 'String' : undefined;
const resolvedType = resolvedTypeIfEnum ? resolvedTypeIfEnum : baseType;
const hashKey = makeInputValueDefinition(sortKeyName, makeNamedType(ModelResourceIDs.ModelKeyConditionInputTypeName(resolvedType)));
return [hashKey, ...elems];
function addSimpleSortKey(
ctx: TransformerContext,
definition: ObjectTypeDefinitionNode,
args: KeyArguments,
elems: InputValueDefinitionNode[]
): InputValueDefinitionNode[] {
let sortKeyName = args.fields[1];
const sortField = definition.fields.find(field => field.name.value === sortKeyName);
const baseType = getBaseType(sortField.type);
const resolvedTypeIfEnum = (ctx.getType(baseType) as EnumTypeDefinitionNode) ? 'String' : undefined;
const resolvedType = resolvedTypeIfEnum ? resolvedTypeIfEnum : baseType;
const hashKey = makeInputValueDefinition(sortKeyName, makeNamedType(ModelResourceIDs.ModelKeyConditionInputTypeName(resolvedType)));
return [hashKey, ...elems];
}
function addCompositeSortKey(definition: ObjectTypeDefinitionNode, args: KeyArguments, elems: InputValueDefinitionNode[]): InputValueDefinitionNode[] {
let sortKeyNames = args.fields.slice(1);
const compositeSortKeyName = toCamelCase(sortKeyNames);
const hashKey = makeInputValueDefinition(compositeSortKeyName, makeNamedType(ModelResourceIDs.ModelCompositeKeyConditionInputTypeName(definition.name.value, toUpper(args.name || 'Primary'))));
return [hashKey, ...elems];
function addCompositeSortKey(
definition: ObjectTypeDefinitionNode,
args: KeyArguments,
elems: InputValueDefinitionNode[]
): InputValueDefinitionNode[] {
let sortKeyNames = args.fields.slice(1);
const compositeSortKeyName = toCamelCase(sortKeyNames);
const hashKey = makeInputValueDefinition(
compositeSortKeyName,
makeNamedType(ModelResourceIDs.ModelCompositeKeyConditionInputTypeName(definition.name.value, toUpper(args.name || 'Primary')))
);
return [hashKey, ...elems];
}
function joinSnippets(lines: string[]): string {
return lines.join('\n');
return lines.join('\n');
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc