Skip to content

Commit d986592

Browse files
feat: add projection support for @index directive (#3359)
1 parent 042bfad commit d986592

14 files changed

Lines changed: 595 additions & 129 deletions

File tree

codebuild_specs/e2e_workflow.yml

Lines changed: 44 additions & 44 deletions
Large diffs are not rendered by default.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/usr/bin/env node
2+
import 'source-map-support/register';
3+
import { App, Stack, Duration } from 'aws-cdk-lib';
4+
// @ts-ignore
5+
import { AmplifyGraphqlApi, AmplifyGraphqlDefinition } from '@aws-amplify/graphql-api-construct';
6+
7+
const packageJson = require('../package.json');
8+
9+
const app = new App();
10+
const stack = new Stack(app, packageJson.name.replace(/_/g, '-'), {
11+
env: { region: process.env.CLI_REGION || 'us-west-2' },
12+
});
13+
14+
new AmplifyGraphqlApi(stack, 'GraphqlApi', {
15+
apiName: 'GSIProjectionAll',
16+
definition: AmplifyGraphqlDefinition.fromString(
17+
/* GraphQL */ `
18+
type Product @model @auth(rules: [{ allow: public }]) {
19+
id: ID!
20+
name: String!
21+
category: String! @index(name: "byCategory", queryField: "productsByCategory", projection: { type: ALL })
22+
price: Float!
23+
inStock: Boolean!
24+
}
25+
`,
26+
{
27+
dbType: 'DYNAMODB',
28+
provisionStrategy: 'AMPLIFY_TABLE',
29+
},
30+
),
31+
authorizationModes: {
32+
apiKeyConfig: { expires: Duration.days(7) },
33+
},
34+
});
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!/usr/bin/env node
2+
import 'source-map-support/register';
3+
import { App, Stack, Duration } from 'aws-cdk-lib';
4+
// @ts-ignore
5+
import { AmplifyGraphqlApi, AmplifyGraphqlDefinition } from '@aws-amplify/graphql-api-construct';
6+
7+
const packageJson = require('../package.json');
8+
9+
const app = new App();
10+
const stack = new Stack(app, packageJson.name.replace(/_/g, '-'), {
11+
env: { region: process.env.CLI_REGION || 'us-west-2' },
12+
});
13+
14+
new AmplifyGraphqlApi(stack, 'GraphqlApi', {
15+
apiName: 'GSIProjectionInclude',
16+
definition: AmplifyGraphqlDefinition.fromString(
17+
/* GraphQL */ `
18+
type Product @model @auth(rules: [{ allow: public }]) {
19+
id: ID!
20+
name: String!
21+
category: String!
22+
@index(name: "byCategory", queryField: "productsByCategory", projection: { type: INCLUDE, nonKeyAttributes: ["name", "price"] })
23+
price: Float!
24+
inStock: Boolean
25+
}
26+
`,
27+
{
28+
dbType: 'DYNAMODB',
29+
provisionStrategy: 'AMPLIFY_TABLE',
30+
},
31+
),
32+
authorizationModes: {
33+
apiKeyConfig: { expires: Duration.days(7) },
34+
},
35+
});
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/usr/bin/env node
2+
import 'source-map-support/register';
3+
import { App, Stack, Duration } from 'aws-cdk-lib';
4+
// @ts-ignore
5+
import { AmplifyGraphqlApi, AmplifyGraphqlDefinition } from '@aws-amplify/graphql-api-construct';
6+
7+
const packageJson = require('../package.json');
8+
9+
const app = new App();
10+
const stack = new Stack(app, packageJson.name.replace(/_/g, '-'), {
11+
env: { region: process.env.CLI_REGION || 'us-west-2' },
12+
});
13+
14+
new AmplifyGraphqlApi(stack, 'GraphqlApi', {
15+
apiName: 'GSIProjectionKeysOnly',
16+
definition: AmplifyGraphqlDefinition.fromString(
17+
/* GraphQL */ `
18+
type Product @model @auth(rules: [{ allow: public }]) {
19+
id: ID!
20+
name: String!
21+
category: String! @index(name: "byCategory", queryField: "productsByCategory", projection: { type: KEYS_ONLY })
22+
price: Float
23+
}
24+
`,
25+
{
26+
dbType: 'DYNAMODB',
27+
provisionStrategy: 'AMPLIFY_TABLE',
28+
},
29+
),
30+
authorizationModes: {
31+
apiKeyConfig: { expires: Duration.days(7) },
32+
},
33+
});
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import * as path from 'path';
2+
import { createNewProjectDir, deleteProjectDir, getDDBTable } from 'amplify-category-api-e2e-core';
3+
import { cdkDestroy, initCDKProject, cdkDeploy } from '../commands';
4+
import { DURATION_1_HOUR } from '../utils/duration-constants';
5+
6+
jest.setTimeout(DURATION_1_HOUR);
7+
8+
describe('GSI Projection Type', () => {
9+
let projRoot: string;
10+
11+
beforeEach(async () => {
12+
projRoot = await createNewProjectDir('gsiprojectiontype');
13+
});
14+
15+
afterEach(async () => {
16+
try {
17+
await cdkDestroy(projRoot, '--all');
18+
} catch (_) {
19+
/* No-op */
20+
}
21+
deleteProjectDir(projRoot);
22+
if (global.gc) global.gc();
23+
});
24+
25+
test.each([
26+
[
27+
'KEYS_ONLY',
28+
'keys-only',
29+
(gsi: any) => {
30+
expect(gsi.Projection.ProjectionType).toBe('KEYS_ONLY');
31+
},
32+
],
33+
[
34+
'INCLUDE',
35+
'include',
36+
(gsi: any) => {
37+
expect(gsi.Projection.ProjectionType).toBe('INCLUDE');
38+
expect(gsi.Projection.NonKeyAttributes).toEqual(expect.arrayContaining(['name', 'price']));
39+
},
40+
],
41+
[
42+
'ALL',
43+
'all',
44+
(gsi: any) => {
45+
expect(gsi.Projection.ProjectionType).toBe('ALL');
46+
},
47+
],
48+
])('creates GSI with %s projection', async (_projectionType, templateDir, validator) => {
49+
const templatePath = path.resolve(path.join(__dirname, 'backends', 'gsi-projection-type', templateDir));
50+
const name = await initCDKProject(projRoot, templatePath);
51+
const outputs = await cdkDeploy(projRoot, '--all');
52+
const { awsAppsyncApiId: apiId, awsAppsyncRegion: region } = outputs[name];
53+
const table = await getDDBTable(`Product-${apiId}-NONE`, region);
54+
const gsi = table.Table.GlobalSecondaryIndexes[0];
55+
56+
expect(table.Table.GlobalSecondaryIndexes).toBeDefined();
57+
expect(table.Table.GlobalSecondaryIndexes.length).toBe(1);
58+
expect(gsi.IndexName).toBe('byCategory');
59+
validator(gsi);
60+
});
61+
});

0 commit comments

Comments
 (0)