Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apollo zip from input schema file #76

Merged
merged 1 commit into from
Feb 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,15 +275,24 @@ See CDK end to end example [here](https://github.com/aws/amazon-neptune-for-grap
# Generate Apollo Server Artifacts
If you prefer to use Apollo Server instead of AWS App Sync, the utility can generate a ZIP file of Apollo Server artifacts with the CLI option `--create-update-apollo-server` for a standalone server or `--create-update-apollo-server-subgraph` for federated systems.

For example:
Example of starting with a neptune database with data from which to determine the graphQL schema:
```
neptune-for-graphql \
--input-graphdb-schema-neptune-endpoint abc.us-west-2.neptune.amazonaws.com:8182 \
--create-update-apollo-server \
--output-resolver-query-https
```

The command above will generate an `apollo-server-<identifier>-<timestamp>.zip` file which can then be deployed locally by following these steps:
Example of starting with a graphQL schema file:
```
neptune-for-graphql \
--input-schema-file airports.source.schema.graphql \
--create-update-apollo-server-neptune-endpoint abc.us-west-2.neptune.amazonaws.com:8182 \
--create-update-apollo-server \
--output-resolver-query-https
```

The example commands above will generate the file `apollo-server-<identifier>-<timestamp>.zip`, which can then be deployed locally by following these steps:
1. unzip `apollo-server-<identifier>-<timestamp>.zip`
2. change directory into the unzipped folder
3. execute `npm install` to install required dependencies
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@aws/neptune-for-graphql",
"version": "1.1.1",
"version": "1.2.0-beta.1",
"description": "CLI utility to create and maintain a GraphQL API for Amazon Neptune",
"keywords": [
"Amazon Neptune",
Expand Down
2 changes: 2 additions & 0 deletions src/help.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ Parameters

[--create-update-apollo-server -asvr]
[--create-update-apollo-server-subgraph -asub]
[--create-update-apollo-server-neptune-endpoint -ase ] default: --input-graphdb-schema-neptune-endpoint if exists
[ ... more apollo options to come in future roadmap ]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know we accidentally missed a spot with the help text last time. Just double checking if we got them all this time.

I don't know what all of them are, so I'm just posing the question to you.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I got it all this time!


[--output-aws-pipeline-cdk -c ]
[--output-aws-pipeline-cdk-name <value> -cn ] default: Neptune DB name from --input-graphdb-schema-neptune-endpoint if exists
Expand Down
19 changes: 17 additions & 2 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ let queryClient = 'sdk'; // or 'http'
let isNeptuneIAMAuth = false;
let createUpdateApolloServer = false;
let createUpdateApolloServerSubgraph = false;
let createUpdateApolloServerEndpoint = '';
let createUpdatePipeline = false;
let createUpdatePipelineName = '';
let createUpdatePipelineEndpoint = '';
Expand Down Expand Up @@ -197,6 +198,10 @@ function processArgs() {
createUpdatePipeline = false;
inputCDKpipeline = false;
break;
case '-ase':
case '--create-update-apollo-server-neptune-endpoint':
createUpdateApolloServerEndpoint = array[index + 1];
break;
case '-p':
case '--create-update-aws-pipeline':
createUpdatePipeline = true;
Expand Down Expand Up @@ -314,10 +319,15 @@ function createOutputFolder() {

function validateArgs() {
// TODO more argument validation
if (queryClient !== 'http' && (createUpdateApolloServerSubgraph || createUpdateApolloServer)) {
const createApollo = createUpdateApolloServerSubgraph || createUpdateApolloServer;
if (queryClient !== 'http' && createApollo) {
console.error(`Neptune querying using ${queryClient} is not yet supported for Apollo Server. Please use option --output-resolver-query-https.`);
process.exit(1);
}
if (createApollo && !inputGraphDBSchemaNeptuneEndpoint && !createUpdateApolloServerEndpoint) {
console.error(`Apollo artifact creation requires a neptune endpoint. Please specify option --input-graphdb-schema-neptune-endpoint or --create-update-apollo-server-neptune-endpoint.`);
process.exit(1);
}
}

async function main() {
Expand Down Expand Up @@ -351,7 +361,12 @@ async function main() {
let neptuneInfo;
// Check if any of the Neptune endpoints are a neptune analytic endpoint and if so, set the neptuneType and IAM to required
// only one of these endpoints are expected to be non-empty at the same time
const nonEmptyEndpoints = [inputGraphDBSchemaNeptuneEndpoint, createUpdatePipelineEndpoint, inputCDKpipelineEndpoint].filter(endpoint => endpoint !== '');
const nonEmptyEndpoints = [
inputGraphDBSchemaNeptuneEndpoint,
createUpdatePipelineEndpoint,
inputCDKpipelineEndpoint,
createUpdateApolloServerEndpoint
].filter(endpoint => endpoint !== '');
if (nonEmptyEndpoints.length > 0) {
neptuneInfo = parseNeptuneEndpoint(nonEmptyEndpoints[0]);
neptuneType = neptuneInfo.neptuneType;
Expand Down
2 changes: 1 addition & 1 deletion src/zipPackage.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export async function createApolloDeploymentPackage({zipFilePath, resolverFilePa
includePaths: [
{source: path.join(modulePath, '/../templates/ApolloServer')},
{source: resolverFilePath, target: 'output.resolver.graphql.js'},
{source: schemaFilePath, target: 'schema.graphql'},
{source: schemaFilePath, target: 'output.schema.graphql'},
// querying neptune using SDK not yet supported
{source: path.join(modulePath, '/../templates/queryHttpNeptune.mjs')}
],
Expand Down
2 changes: 1 addition & 1 deletion templates/ApolloServer/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import dotenv from 'dotenv';

dotenv.config();

const typeDefs = gql(readFileSync('./schema.graphql', 'utf-8'));
const typeDefs = gql(readFileSync('./output.schema.graphql', 'utf-8'));
const queryDefinition = typeDefs.definitions.find(
definition => definition.kind === 'ObjectTypeDefinition' && definition.name.value === 'Query'
);
Expand Down
2 changes: 1 addition & 1 deletion templates/ApolloServer/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "neptune-apollo-server",
"version": "1.0.0",
"version": "1.2.0-beta.1",
"description": "",
"license": "Apache-2.0",
"author": "",
Expand Down
24 changes: 13 additions & 11 deletions templates/JSResolverOCHTTPSTemplate.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ express or implied. See the License for the specific language governing
permissions and limitations under the License.
*/

import { astFromValue, buildASTSchema, typeFromAST, GraphQLID } from 'graphql';
import { astFromValue, buildASTSchema, typeFromAST, GraphQLID, GraphQLInputObjectType } from 'graphql';
import { gql } from 'graphql-tag'; // GraphQL library to parse the GraphQL query

const useCallSubquery = false;
Expand All @@ -36,7 +36,7 @@ export function resolveGraphDBQueryFromAppSyncEvent(event) {
return resolveGraphDBQueryFromEvent({
field: event.field,
arguments: event.arguments,
selectionSet: gql`${event.selectionSetGraphQL}`.definitions[0].selectionSet
selectionSet: event.selectionSetGraphQL ? gql`${event.selectionSetGraphQL}`.definitions[0].selectionSet : {}
});
}

Expand All @@ -58,15 +58,17 @@ export function resolveGraphDBQueryFromEvent(event) {
if (value) {
const inputType = typeFromAST(schema, inputDef.type);
const astValue = astFromValue(value, inputType);
// retrieve an ID field which may not necessarily be named 'id'
const idField = Object.values(inputType.getFields()).find(field => field.type.name === GraphQLID.name);
if (idField) {
// check if id was an input arg
const idValue = astValue.fields.find(f => f.name.value === idField.name);
if (idValue?.value?.kind === 'IntValue') {
// graphql astFromValue function can convert ID integer strings into integer type
// if input args contain an id and the graphql library has interpreted the value as an int, change it to treat the value as a string
idValue.value.kind = 'StringValue';
if (inputType instanceof GraphQLInputObjectType) {
// retrieve an ID field which may not necessarily be named 'id'
const idField = Object.values(inputType.getFields()).find(field => field.type.name === GraphQLID.name);
if (idField) {
// check if id was an input arg
const idValue = astValue.fields.find(f => f.name.value === idField.name);
if (idValue?.value?.kind === 'IntValue') {
// graphql astFromValue function can convert ID integer strings into integer type
// if input args contain an id and the graphql library has interpreted the value as an int, change it to treat the value as a string
idValue.value.kind = 'StringValue';
}
}
}
args.push({
Expand Down
4 changes: 2 additions & 2 deletions templates/Lambda4AppSyncGraphSDK/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion templates/Lambda4AppSyncGraphSDK/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "lambda4appsyncgraphsdk",
"version": "1.1.1",
"version": "1.2.0-beta.1",
"description": "AWS Lambda function to bridge AppSync to Neptune Analytics",
"main": "index.mjs",
"type": "module",
Expand Down
4 changes: 2 additions & 2 deletions templates/Lambda4AppSyncHTTP/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion templates/Lambda4AppSyncHTTP/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "lambda4appsynchttp",
"version": "1.1.1",
"version": "1.2.0-beta.1",
"description": "AWS Lambda function to bridge AppSync to Neptune or Neptune Analytics",
"main": "index.mjs",
"type": "module",
Expand Down
4 changes: 2 additions & 2 deletions templates/Lambda4AppSyncSDK/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion templates/Lambda4AppSyncSDK/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "lambda4appsyncsdk",
"version": "1.1.1",
"version": "1.2.0-beta.1",
"description": "AWS Lambda function to bridge AppSync to Neptune",
"main": "index.mjs",
"type": "module",
Expand Down
30 changes: 30 additions & 0 deletions test/TestCases/Case01/Case01.05.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,36 @@ describe('AppSync resolver', () => {
});
});

test('should resolve gremlin query with argument', () => {
const result = resolve({
field: 'getAirportWithGremlin',
arguments: { code: 'YVR' },
selectionSetGraphQL: '{ city }'
});

expect(result).toMatchObject({
query: "g.V().has('airport', 'code', 'YVR').elementMap()",
parameters: {},
language: 'gremlin',
refactorOutput: null
});
});

test('should resolve gremlin query without arguments or selection set', () => {
const result = resolve({
field: 'getCountriesCount',
arguments: { },
selectionSetGraphQL: ''
});

expect(result).toMatchObject({
query: "g.V().hasLabel('country').count()",
parameters: {},
language: 'gremlin',
refactorOutput: null
});
});

function resolve(event) {
return resolverModule.resolveGraphDBQueryFromAppSyncEvent(event);
}
Expand Down
4 changes: 3 additions & 1 deletion test/TestCases/Case01/input/changesAirport.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
[
{ "type": "Airport", "field": "outboundRoutesCountAdd", "action": "add", "value":"outboundRoutesCountAdd: Int @graphQuery(statement: \"MATCH (this)-[r:route]->(a) RETURN count(r)\")"},
{ "type": "Mutation", "field": "deleteNodeVersion", "action": "remove", "value": "" },
{ "type": "Mutation", "field": "createNodeVersion", "action": "remove", "value": "" }
{ "type": "Mutation", "field": "createNodeVersion", "action": "remove", "value": "" },
{ "type": "Query", "field": "getAirportWithGremlin", "action": "add", "value":"getAirportWithGremlin(code:String): Airport @graphQuery(statement: \"g.V().has('airport', 'code', '$code').elementMap()\")"},
{ "type": "Query", "field": "getCountriesCount", "action": "add", "value":"getCountriesCount: Int @graphQuery(statement: \"g.V().hasLabel('country').count()\")"}
]
Loading