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

feat: generate api code #269

Merged
merged 6 commits into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
7 changes: 7 additions & 0 deletions .changeset/fuzzy-socks-begin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@aws-amplify/model-generator': minor
---

Add wrapper to around types, documents, and model generation (`generateAPICode`).

Change `createGraphqlDocumentGenerator` and `createGraphqlTypesGenerator` to use backendIdentifier and credentialProvider.
3 changes: 3 additions & 0 deletions .eslint_dictionary.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,14 @@ export default [
'inheritdoc',
'invokable',
'invoker',
'javascript',
'jsdoc',
'lang',
'lsof',
'lstat',
'mfas',
'mkdtemp',
'modelgen',
'multifactor',
'nodejs',
'npmrc',
Expand All @@ -65,6 +67,7 @@ export default [
'repo',
'resolvers',
'saml',
'scala',
'schema',
'schemas',
'searchable',
Expand Down
2 changes: 2 additions & 0 deletions package-lock.json

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

64 changes: 62 additions & 2 deletions packages/model-generator/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,71 @@

```ts

import { AwsCredentialIdentityProvider } from '@aws-sdk/types';
import { BackendIdentifier } from '@aws-amplify/deployed-backend-client';
import { ModelsTarget } from '@aws-amplify/graphql-generator';
import { StatementsTarget } from '@aws-amplify/graphql-generator';
import { TypesTarget } from '@aws-amplify/graphql-generator';

// @public
export const createGraphqlDocumentGenerator: ({ apiId, }: GraphqlDocumentGeneratorFactoryParams) => GraphqlDocumentGenerator;
export const createGraphqlDocumentGenerator: ({ backendIdentifier, credentialProvider, }: GraphqlDocumentGeneratorFactoryParams) => GraphqlDocumentGenerator;

// @public (undocumented)
export type DocumentGenerationParameters = {
language: TargetLanguage;
maxDepth?: number;
typenameIntrospection?: boolean;
dpilch marked this conversation as resolved.
Show resolved Hide resolved
};

// @public
export const generateApiCode: (props: GenerateApiCodeProps) => Promise<GeneratedOutput>;

// @public (undocumented)
export type GenerateApiCodeProps = GenerateOptions & BackendIdentifier & {
credentialProvider: AwsCredentialIdentityProvider;
};

// @public (undocumented)
export type GeneratedOutput = {
[filename: string]: string;
};
Copy link
Member

Choose a reason for hiding this comment

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

this seems to be unused. please hide.

Copy link
Member Author

Choose a reason for hiding this comment

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

The is the return type of generateApiCode.

Copy link
Member

Choose a reason for hiding this comment

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

hmm.
In that case shouldn't generateApiCode just return GenerationResult like the wrapped operations do?
Do we have use case where in-memory representation would be consumed?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll update to GenerationResult. The in-memory functionality was a requirement from Rene and is how Al's PR is built https://github.com/aws-amplify/samsara-cli/pull/265/files#diff-dcea9a2f178bf1deb339fd2cc2aa95b9cf136b2f32a660c1c3501f8bd62c399dR30-R57

Copy link
Member

Choose a reason for hiding this comment

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

Al's PR can use GenerationResult.writeToDirectory() API. Which was created for that purpose.

@alharris-at do you know why PMs want in-memory representation? Can this be descoped for now until we know what is the use case?

Regardless of whether it's a must or not the GenerationResult was a place to add Record<string, string> prop to provide that. But if we don't know use case we should hold.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm fine chopping this out for now.


// @public (undocumented)
export type GenerateGraphqlCodegenOptions = {
format: 'graphql-codegen';
statementTarget: 'javascript' | 'graphql' | 'flow' | 'typescript' | 'angular';
maxDepth?: number;
typeNameIntrospection?: boolean;
typeTarget?: 'json' | 'swift' | 'typescript' | 'flow' | 'scala' | 'flow-modern' | 'angular';
multipleSwiftFiles?: boolean;
};

// @public (undocumented)
export type GenerateIntrospectionOptions = {
format: 'introspection';
};

// @public (undocumented)
export type GenerateModelsOptions = {
format: 'modelgen';
modelTarget: 'java' | 'swift' | 'javascript' | 'typescript' | 'dart';
generateIndexRules?: boolean;
emitAuthProvider?: boolean;
useExperimentalPipelinedTransformer?: boolean;
transformerVersion?: boolean;
respectPrimaryKeyAttributesOnConnectionField?: boolean;
generateModelsForLazyLoadAndCustomSelectionSet?: boolean;
addTimestampFields?: boolean;
handleListNullabilityTransparently?: boolean;
};

// @public (undocumented)
export type GenerateOptions = GenerateGraphqlCodegenOptions | GenerateModelsOptions | GenerateIntrospectionOptions;

// @public (undocumented)
export type GenerationResult = {
writeToDirectory: (directoryPath: string) => Promise<void>;
operations: Record<string, string>;
Copy link
Member

Choose a reason for hiding this comment

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

Do we have a use case for this?
If not please hide.

};

// @public (undocumented)
Expand All @@ -28,7 +78,8 @@ export type GraphqlDocumentGenerator = {

// @public (undocumented)
export type GraphqlDocumentGeneratorFactoryParams = {
apiId: string;
backendIdentifier: BackendIdentifier;
credentialProvider: AwsCredentialIdentityProvider;
};

// @public (undocumented)
Expand All @@ -44,6 +95,14 @@ export type GraphqlTypesGenerator = {
// @public (undocumented)
export type ModelsGenerationParameters = {
target: ModelsTarget;
generateIndexRules?: boolean;
emitAuthProvider?: boolean;
useExperimentalPipelinedTransformer?: boolean;
transformerVersion?: boolean;
respectPrimaryKeyAttributesOnConnectionField?: boolean;
generateModelsForLazyLoadAndCustomSelectionSet?: boolean;
addTimestampFields?: boolean;
handleListNullabilityTransparently?: boolean;
};

// @public (undocumented)
Expand All @@ -52,6 +111,7 @@ export type TargetLanguage = StatementsTarget;
// @public (undocumented)
export type TypesGenerationParameters = {
target: TypesTarget;
multipleSwiftFiles?: boolean;
};

// (No @packageDocumentation comment for this package)
Expand Down
1 change: 1 addition & 0 deletions packages/model-generator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"@aws-amplify/graphql-generator": "^0.1.0",
"@aws-sdk/client-appsync": "^3.398.0",
"@aws-sdk/client-s3": "^3.414.0",
"@aws-sdk/credential-providers": "^3.414.0",
"@aws-sdk/types": "^3.413.0"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export class AppsyncGraphqlGenerationResult implements GenerationResult {
* @param operations A record of FileName to FileContent
* in the format of Record<string,string>
*/
constructor(private operations: ClientOperations) {}
constructor(public operations: ClientOperations) {}
Copy link
Member

Choose a reason for hiding this comment

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

why ?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is so that generateApiCode can return the generation results without writing to the file system.

private writeSchemaToFile = async (
basePath: string,
filePath: string,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
import assert from 'node:assert';
import { describe, it } from 'node:test';
import { AwsCredentialIdentityProvider } from '@aws-sdk/types';
import { BackendIdentifier } from '@aws-amplify/deployed-backend-client';
import { createGraphqlDocumentGenerator } from './create_graphql_document_generator.js';

describe('model generator factory', () => {
it('throws an error if a null apiId is passed in', async () => {
it('throws an error if a null backendIdentifier is passed in', async () => {
assert.throws(() =>
createGraphqlDocumentGenerator({ apiId: null as unknown as string })
createGraphqlDocumentGenerator({
backendIdentifier: null as unknown as BackendIdentifier,
credentialProvider: null as unknown as AwsCredentialIdentityProvider,
})
);
});

it('throws an error if a null backendIdentifier is passed in', async () => {
assert.throws(() =>
createGraphqlDocumentGenerator({
backendIdentifier: { stackName: 'foo' },
credentialProvider: null as unknown as AwsCredentialIdentityProvider,
})
);
});
});
Original file line number Diff line number Diff line change
@@ -1,25 +1,51 @@
import { AppSyncClient } from '@aws-sdk/client-appsync';
import {
BackendIdentifier,
BackendOutputClient,
} from '@aws-amplify/deployed-backend-client';
import { graphqlOutputKey } from '@aws-amplify/backend-output-schemas';
import { AwsCredentialIdentityProvider } from '@aws-sdk/types';
import { AppsyncGraphqlGenerationResult } from './appsync_graphql_generation_result.js';
import { AppSyncIntrospectionSchemaFetcher } from './appsync_schema_fetcher.js';
import { AppSyncGraphqlDocumentGenerator } from './graphql_document_generator.js';
import { GraphqlDocumentGenerator } from './model_generator.js';

export type GraphqlDocumentGeneratorFactoryParams = {
apiId: string;
backendIdentifier: BackendIdentifier;
credentialProvider: AwsCredentialIdentityProvider;
};

/**
* Factory function to compose a model generator
*/
export const createGraphqlDocumentGenerator = ({
apiId,
backendIdentifier,
credentialProvider,
}: GraphqlDocumentGeneratorFactoryParams): GraphqlDocumentGenerator => {
if (!apiId) {
throw new Error('`apiId` must be defined');
if (!backendIdentifier) {
throw new Error('`backendIdentifier` must be defined');
}
if (!credentialProvider) {
throw new Error('`credentialProvider` must be defined');
}

const fetchSchema = async () => {
const backendOutputClient = new BackendOutputClient(
credentialProvider,
backendIdentifier
);
const output = await backendOutputClient.getOutput();
const apiId = output[graphqlOutputKey]?.payload.awsAppsyncApiId;
if (!apiId) {
throw new Error(`Unable to determine AppSync API ID.`);
}

return new AppSyncIntrospectionSchemaFetcher(new AppSyncClient()).fetch(
apiId
);
};
return new AppSyncGraphqlDocumentGenerator(
() =>
new AppSyncIntrospectionSchemaFetcher(new AppSyncClient()).fetch(apiId),
fetchSchema,
(fileMap) => new AppsyncGraphqlGenerationResult(fileMap)
);
};
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
import assert from 'node:assert';
import { describe, it } from 'node:test';
import { AwsCredentialIdentityProvider } from '@aws-sdk/types';
import { BackendIdentifier } from '@aws-amplify/deployed-backend-client';
import { createGraphqlTypesGenerator } from './create_graphql_types_generator.js';

describe('types generator factory', () => {
it('throws an error if a null apiId is passed in', async () => {
it('throws an error if a null backendIdentifier is passed in', async () => {
assert.throws(() =>
createGraphqlTypesGenerator({ apiId: null as unknown as string })
createGraphqlTypesGenerator({
backendIdentifier: null as unknown as BackendIdentifier,
credentialProvider: null as unknown as AwsCredentialIdentityProvider,
})
);
});

it('throws an error if a null backendIdentifier is passed in', async () => {
assert.throws(() =>
createGraphqlTypesGenerator({
backendIdentifier: { stackName: 'foo' },
credentialProvider: null as unknown as AwsCredentialIdentityProvider,
})
);
});
});
38 changes: 32 additions & 6 deletions packages/model-generator/src/create_graphql_types_generator.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,51 @@
import { AppSyncClient } from '@aws-sdk/client-appsync';
import {
BackendIdentifier,
BackendOutputClient,
} from '@aws-amplify/deployed-backend-client';
import { graphqlOutputKey } from '@aws-amplify/backend-output-schemas';
import { AwsCredentialIdentityProvider } from '@aws-sdk/types';
import { AppsyncGraphqlGenerationResult } from './appsync_graphql_generation_result.js';
import { AppSyncIntrospectionSchemaFetcher } from './appsync_schema_fetcher.js';
import { AppSyncGraphqlTypesGenerator } from './graphql_types_generator.js';
import { GraphqlTypesGenerator } from './model_generator.js';

export type GraphqlTypesGeneratorFactoryParams = {
apiId: string;
backendIdentifier: BackendIdentifier;
credentialProvider: AwsCredentialIdentityProvider;
};

/**
* Factory function to compose a model generator
*/
export const createGraphqlTypesGenerator = ({
apiId,
backendIdentifier,
credentialProvider,
}: GraphqlTypesGeneratorFactoryParams): GraphqlTypesGenerator => {
if (!apiId) {
throw new Error('`apiId` must be defined');
if (!backendIdentifier) {
throw new Error('`backendIdentifier` must be defined');
}
if (!credentialProvider) {
throw new Error('`credentialProvider` must be defined');
}

const fetchSchema = async () => {
const backendOutputClient = new BackendOutputClient(
credentialProvider,
backendIdentifier
);
const output = await backendOutputClient.getOutput();
const apiId = output[graphqlOutputKey]?.payload.awsAppsyncApiId;
if (!apiId) {
throw new Error(`Unable to determine AppSync API ID.`);
}

return new AppSyncIntrospectionSchemaFetcher(new AppSyncClient()).fetch(
apiId
);
};
return new AppSyncGraphqlTypesGenerator(
() =>
new AppSyncIntrospectionSchemaFetcher(new AppSyncClient()).fetch(apiId),
fetchSchema,
(fileMap) => new AppsyncGraphqlGenerationResult(fileMap)
);
};
17 changes: 17 additions & 0 deletions packages/model-generator/src/generate_api_code.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import assert from 'assert';
import { describe, it } from 'node:test';
import { GenerateApiCodeProps, generateApiCode } from './generate_api_code.js';

describe('generateAPICode', () => {
describe('graphql-codegen', () => {});
describe('modelgen', () => {});
describe('introspection', () => {});

it('throws error on unknown format', async () => {
const props = {
format: 'unsupported',
stackName: 'stack_name',
} as unknown as GenerateApiCodeProps;
await assert.rejects(() => generateApiCode(props));
});
});
Loading