Skip to content

Commit 8c4be9a

Browse files
authored
feat(appsync-modelgen-plugin): add support for generation route definitions (#869)
1 parent 1a5bf2d commit 8c4be9a

File tree

8 files changed

+146
-4
lines changed

8 files changed

+146
-4
lines changed

packages/appsync-modelgen-plugin/schemas/introspection/1/ModelIntrospectionSchema.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
},
2727
"inputs": {
2828
"$ref": "#/definitions/SchemaInputs"
29+
},
30+
"generations": {
31+
"$ref": "#/definitions/SchemaGenerations"
2932
}
3033
},
3134
"required": [
@@ -532,6 +535,9 @@
532535
],
533536
"additionalProperties": false,
534537
"description": "Input Definition"
538+
},
539+
"SchemaGenerations": {
540+
"$ref": "#/definitions/SchemaQueries"
535541
}
536542
}
537543
}

packages/appsync-modelgen-plugin/src/__tests__/visitors/__snapshots__/appsync-model-introspection-visitor.test.ts.snap

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1976,6 +1976,75 @@ exports[`Custom queries/mutations/subscriptions & input type tests should genera
19761976
}"
19771977
`;
19781978

1979+
exports[`Generation Route Introspection Visitor Metadata snapshot should generate correct model intropection file validated by JSON schema 1`] = `
1980+
"{
1981+
\\"version\\": 1,
1982+
\\"models\\": {},
1983+
\\"enums\\": {},
1984+
\\"nonModels\\": {
1985+
\\"Recipe\\": {
1986+
\\"name\\": \\"Recipe\\",
1987+
\\"fields\\": {
1988+
\\"name\\": {
1989+
\\"name\\": \\"name\\",
1990+
\\"isArray\\": false,
1991+
\\"type\\": \\"String\\",
1992+
\\"isRequired\\": false,
1993+
\\"attributes\\": []
1994+
},
1995+
\\"ingredients\\": {
1996+
\\"name\\": \\"ingredients\\",
1997+
\\"isArray\\": true,
1998+
\\"type\\": \\"String\\",
1999+
\\"isRequired\\": false,
2000+
\\"attributes\\": [],
2001+
\\"isArrayNullable\\": true
2002+
},
2003+
\\"instructions\\": {
2004+
\\"name\\": \\"instructions\\",
2005+
\\"isArray\\": false,
2006+
\\"type\\": \\"String\\",
2007+
\\"isRequired\\": false,
2008+
\\"attributes\\": []
2009+
}
2010+
}
2011+
}
2012+
},
2013+
\\"generations\\": {
2014+
\\"generateRecipe\\": {
2015+
\\"name\\": \\"generateRecipe\\",
2016+
\\"isArray\\": false,
2017+
\\"type\\": {
2018+
\\"nonModel\\": \\"Recipe\\"
2019+
},
2020+
\\"isRequired\\": false,
2021+
\\"arguments\\": {
2022+
\\"description\\": {
2023+
\\"name\\": \\"description\\",
2024+
\\"isArray\\": false,
2025+
\\"type\\": \\"String\\",
2026+
\\"isRequired\\": false
2027+
}
2028+
}
2029+
},
2030+
\\"summarize\\": {
2031+
\\"name\\": \\"summarize\\",
2032+
\\"isArray\\": false,
2033+
\\"type\\": \\"String\\",
2034+
\\"isRequired\\": false,
2035+
\\"arguments\\": {
2036+
\\"text\\": {
2037+
\\"name\\": \\"text\\",
2038+
\\"isArray\\": false,
2039+
\\"type\\": \\"String\\",
2040+
\\"isRequired\\": false
2041+
}
2042+
}
2043+
}
2044+
}
2045+
}"
2046+
`;
2047+
19792048
exports[`Model Introspection Visitor Metadata snapshot should generate correct model intropection file validated by JSON schema 1`] = `
19802049
"{
19812050
\\"version\\": 1,

packages/appsync-modelgen-plugin/src/__tests__/visitors/appsync-model-introspection-visitor.test.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { buildSchema, GraphQLSchema, parse, visit } from 'graphql';
22
import { METADATA_SCALAR_MAP } from '../../scalars';
3-
import { AppSyncDirectives, DefaultDirectives, V1Directives, DeprecatedDirective, Directive } from '@aws-amplify/graphql-directives';
3+
import { AppSyncDirectives, DefaultDirectives, V1Directives, DeprecatedDirective, Directive, V2Directives } from '@aws-amplify/graphql-directives';
44
import { scalars } from '../../scalars/supported-scalars';
55
import { AppSyncModelIntrospectionVisitor } from '../../visitors/appsync-model-introspection-visitor';
66

@@ -806,3 +806,45 @@ describe('custom references', () => {
806806
.toThrowError(`Error processing @belongsTo directive on SqlRelated.primary. @hasOne or @hasMany directive with references ["primaryId"] was not found in connected model SqlPrimary`);
807807
});
808808
});
809+
810+
describe('Generation Route Introspection Visitor', () => {
811+
const schema = /* GraphQL */ `
812+
type Recipe {
813+
name: String
814+
ingredients: [String]
815+
instructions: String
816+
}
817+
818+
type Query {
819+
generateRecipe(description: String): Recipe
820+
@generation(aiModel: "anthropic.claude-3-haiku-20240307-v1:0", systemPrompt: "You are a recipe generator.")
821+
822+
summarize(text: String): String
823+
@generation(aiModel: "anthropic.claude-3-haiku-20240307-v1:0", systemPrompt: "You are a text summarizer.")
824+
}
825+
`;
826+
827+
const generationDirective: Directive = {
828+
name: 'generation',
829+
definition: /* GraphQL */ `
830+
directive @generation(
831+
aiModel: String!
832+
systemPrompt: String!
833+
inferenceConfiguration: GenerationInferenceConfiguration
834+
) on FIELD_DEFINITION
835+
836+
input GenerationInferenceConfiguration {
837+
maxTokens: Int
838+
temperature: Float
839+
topP: Float
840+
}
841+
`,
842+
defaults: {},
843+
}
844+
const visitor: AppSyncModelIntrospectionVisitor = getVisitor(schema, {}, [...V2Directives, generationDirective]);
845+
describe('Metadata snapshot', () => {
846+
it('should generate correct model intropection file validated by JSON schema', () => {
847+
expect(visitor.generate()).toMatchSnapshot();
848+
});
849+
});
850+
});

packages/appsync-modelgen-plugin/src/interfaces/introspection/model-schema.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
mutations?: SchemaMutations;
2020
subscriptions?: SchemaSubscriptions;
2121
inputs?: SchemaInputs;
22+
generations?: SchemaGenerations;
2223
};
2324
/**
2425
* Top-level Entities on a Schema
@@ -30,6 +31,7 @@ export type SchemaQueries = Record<string, SchemaQuery>;
3031
export type SchemaMutations = Record<string, SchemaMutation>;
3132
export type SchemaSubscriptions = Record<string, SchemaSubscription>;
3233
export type SchemaInputs = Record<string, Input>;
34+
export type SchemaGenerations = SchemaQueries;
3335

3436
export type SchemaModel = {
3537
name: string;

packages/appsync-modelgen-plugin/src/utils/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const TransformerV2DirectiveName = {
88
INDEX: 'index',
99
DEFAULT: 'default',
1010
SEARCHABLE: 'searchable',
11+
GENERATION: 'generation',
1112
};
1213
export const DEFAULT_HASH_KEY_FIELD = 'id';
1314
export const DEFAULT_CREATED_TIME = 'createdAt';

packages/appsync-modelgen-plugin/src/utils/fieldUtils.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { CodeGenDirective, CodeGenField, CodeGenModel } from '../visitors/appsync-visitor';
1+
import { CodeGenDirective, CodeGenField, CodeGenModel, CodeGenQuery } from '../visitors/appsync-visitor';
22
import { TransformerV2DirectiveName } from './constants';
33

44
export function addFieldToModel(model: CodeGenModel, field: CodeGenField): void {
@@ -40,4 +40,8 @@ export function getModelPrimaryKeyComponentFields(model: CodeGenModel): CodeGenF
4040
};
4141
}
4242
return keyFields;
43+
}
44+
45+
export function containsGenerationDirective(queryField: CodeGenQuery): boolean {
46+
return queryField.directives.some((directive) => directive.name === TransformerV2DirectiveName.GENERATION);
4347
}

packages/appsync-modelgen-plugin/src/validate-cjs.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/appsync-modelgen-plugin/src/visitors/appsync-model-introspection-visitor.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Argument, AssociationType, Field, Fields, FieldType, ModelAttribute, Mo
44
import { METADATA_SCALAR_MAP } from "../scalars";
55
import { CodeGenConnectionType } from "../utils/process-connections";
66
import { RawAppSyncModelConfig, ParsedAppSyncModelConfig, AppSyncModelVisitor, CodeGenEnum, CodeGenField, CodeGenModel, CodeGenPrimaryKeyType, CodeGenQuery, CodeGenSubscription, CodeGenMutation, CodeGenInputObject } from "./appsync-visitor";
7+
import { containsGenerationDirective } from "../utils/fieldUtils";
78

89
const validateModelIntrospectionSchema = require('../validate-cjs');
910

@@ -65,12 +66,22 @@ export class AppSyncModelIntrospectionVisitor<
6566
const queries = Object.values(this.queryMap).reduce((acc, queryObj: CodeGenQuery) => {
6667
// Skip the field if the field type is union/interface
6768
// TODO: Remove this skip once these types are supported for stakeholder usages
69+
// Also skip if this query has a generation directive. These are handled separately and keyed under `generations`.
6870
const fieldType = this.getType(queryObj.type) as any;
69-
if (this.isUnionFieldType(fieldType) || this.isInterfaceFieldType(fieldType)) {
71+
if (this.isUnionFieldType(fieldType) || this.isInterfaceFieldType(fieldType) || containsGenerationDirective(queryObj)) {
7072
return acc;
7173
}
7274
return { ...acc, [queryObj.name]: this.generateGraphQLOperationMetadata<CodeGenQuery, SchemaQuery>(queryObj) };
7375
}, {})
76+
const generations = Object.values(this.queryMap).reduce((acc, queryObj: CodeGenQuery) => {
77+
// Skip the field if the field type is union/interface
78+
// TODO: Remove this skip once these types are supported for stakeholder usages
79+
const fieldType = this.getType(queryObj.type) as any;
80+
if (this.isUnionFieldType(fieldType) || this.isInterfaceFieldType(fieldType) || !containsGenerationDirective(queryObj)) {
81+
return acc;
82+
}
83+
return { ...acc, [queryObj.name]: this.generateGenerationMetadata(queryObj) };
84+
}, {});
7485
const mutations = Object.values(this.mutationMap).reduce((acc, mutationObj: CodeGenMutation) => {
7586
// Skip the field if the field type is union/interface
7687
// TODO: Remove this skip once these types are supported for stakeholder usages
@@ -95,6 +106,9 @@ export class AppSyncModelIntrospectionVisitor<
95106
if (Object.keys(queries).length > 0) {
96107
result = { ...result, queries };
97108
}
109+
if (Object.keys(generations).length > 0) {
110+
result = { ...result, generations };
111+
}
98112
if (Object.keys(mutations).length > 0) {
99113
result = { ...result, mutations };
100114
}
@@ -236,6 +250,10 @@ export class AppSyncModelIntrospectionVisitor<
236250
return operationMeta as V;
237251
}
238252

253+
private generateGenerationMetadata(generationObj: CodeGenQuery): SchemaQuery {
254+
return this.generateGraphQLOperationMetadata<CodeGenQuery, SchemaQuery>(generationObj);
255+
}
256+
239257
protected getType(gqlType: string): FieldType | InputFieldType | UnionFieldType | InterfaceFieldType {
240258
// Todo: Handle unlisted scalars
241259
if (gqlType in METADATA_SCALAR_MAP) {

0 commit comments

Comments
 (0)