From e33eb4a52aec37caac93164f7af575fda66c2fae Mon Sep 17 00:00:00 2001 From: Edward Foyle Date: Tue, 26 Sep 2023 12:41:28 -0700 Subject: [PATCH 1/2] chore: enable no-extraneous-dependencies lint rule and fix offenders (#719) --- .eslintrc.js | 2 +- packages/amplify-codegen-e2e-core/package.json | 4 ++++ packages/amplify-codegen-e2e-tests/package.json | 3 +++ packages/amplify-codegen/package.json | 2 ++ packages/appsync-modelgen-plugin/package.json | 10 +++++++--- packages/graphql-generator/package.json | 3 +++ packages/graphql-types-generator/package.json | 5 ++++- 7 files changed, 24 insertions(+), 5 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 955c70e00..a91367dba 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -190,7 +190,7 @@ module.exports = { '@typescript-eslint/interface-name-prefix': 'off', 'no-throw-literal': 'off', 'react/static-property-placement': 'off', - 'import/no-extraneous-dependencies': 'off', + 'import/no-extraneous-dependencies': ['error', {devDependencies: true}], 'spaced-comment': 'off', '@typescript-eslint/no-array-constructor': 'off', 'prefer-rest-params': 'off', diff --git a/packages/amplify-codegen-e2e-core/package.json b/packages/amplify-codegen-e2e-core/package.json index 180609a3f..43379dd52 100644 --- a/packages/amplify-codegen-e2e-core/package.json +++ b/packages/amplify-codegen-e2e-core/package.json @@ -22,10 +22,14 @@ "clean": "rimraf ./lib" }, "dependencies": { + "aws-sdk": "^2.1465.0", "chalk": "^3.0.0", + "dotenv": "^8.6.0", "execa": "^4.1.0", "fs-extra": "^8.1.0", + "glob": "^10.3.9", "ini": "^3.0.1", + "jest-circus": "^27.5.1", "jest-environment-node": "^26.6.2", "lodash": "^4.17.19", "node-pty": "beta", diff --git a/packages/amplify-codegen-e2e-tests/package.json b/packages/amplify-codegen-e2e-tests/package.json index 078218555..d9290b06a 100644 --- a/packages/amplify-codegen-e2e-tests/package.json +++ b/packages/amplify-codegen-e2e-tests/package.json @@ -24,6 +24,7 @@ "dependencies": { "@aws-amplify/amplify-codegen-e2e-core": "1.6.0", "@aws-amplify/graphql-schema-test-library": "^1.1.18", + "amazon-cognito-identity-js": "^6.3.6", "aws-amplify": "^5.3.3", "aws-appsync": "^4.1.9", "aws-sdk": "^2.1413.0", @@ -33,7 +34,9 @@ "graphql-tag": "^2.10.1", "js-yaml": "^4.0.0", "lodash": "^4.17.19", + "node-fetch": "^3.3.2", "uuid": "^3.4.0", + "ws": "^8.14.2", "yargs": "^15.1.0" }, "devDependencies": { diff --git a/packages/amplify-codegen/package.json b/packages/amplify-codegen/package.json index 568acc99d..01a0a07c3 100644 --- a/packages/amplify-codegen/package.json +++ b/packages/amplify-codegen/package.json @@ -23,7 +23,9 @@ "dependencies": { "@aws-amplify/graphql-generator": "0.1.2", "@aws-amplify/graphql-types-generator": "3.4.1", + "@aws-amplify/graphql-docs-generator": "4.2.0", "@graphql-codegen/core": "2.6.6", + "aws-sdk": "^2.1465.0", "chalk": "^3.0.0", "fs-extra": "^8.1.0", "glob-all": "^3.1.0", diff --git a/packages/appsync-modelgen-plugin/package.json b/packages/appsync-modelgen-plugin/package.json index e4fc1cd67..5855859c2 100644 --- a/packages/appsync-modelgen-plugin/package.json +++ b/packages/appsync-modelgen-plugin/package.json @@ -29,21 +29,25 @@ "@graphql-codegen/plugin-helpers": "^1.18.8", "@graphql-codegen/visitor-plugin-common": "^1.22.0", "@graphql-tools/utils": "^6.0.18", + "ajv": "^6.10.0", "chalk": "^3.0.0", "change-case": "^4.1.1", + "graphql-transformer-common": "^4.25.1", "lower-case-first": "^2.0.1", "pluralize": "^8.0.0", "strip-indent": "^3.0.0", - "ts-dedent": "^1.1.0" + "ts-dedent": "^1.1.0", + "ts-json-schema-generator": "1.0.0" }, "devDependencies": { "@graphql-codegen/testing": "^1.17.7", "@graphql-codegen/typescript": "^2.8.3", + "@types/fs-extra": "^8.1.2", "@types/node": "^12.12.6", "@types/pluralize": "0.0.29", + "graphql": "^15.5.0", - "java-ast": "^0.3.0", - "ts-json-schema-generator": "1.0.0" + "java-ast": "^0.3.0" }, "peerDependencies": { "graphql": "^15.5.0" diff --git a/packages/graphql-generator/package.json b/packages/graphql-generator/package.json index 10a63a00b..d354456fe 100644 --- a/packages/graphql-generator/package.json +++ b/packages/graphql-generator/package.json @@ -31,6 +31,9 @@ "@graphql-tools/apollo-engine-loader": "^8.0.0", "graphql": "^15.5.0" }, + "devDependencies": { + "@types/prettier": "^1.0.0" + }, "typescript": { "definition": "lib/index.d.ts" }, diff --git a/packages/graphql-types-generator/package.json b/packages/graphql-types-generator/package.json index d9e821f72..8500b934d 100644 --- a/packages/graphql-types-generator/package.json +++ b/packages/graphql-types-generator/package.json @@ -35,8 +35,10 @@ "extract-api": "ts-node ../../scripts/extract-api.ts" }, "dependencies": { + "@aws-amplify/api": "^5.4.5", "@babel/generator": "7.0.0-beta.4", "@babel/types": "7.0.0-beta.4", + "aws-amplify": "^5.3.11", "babel-generator": "^6.26.1", "babel-types": "^6.26.0", "change-case": "^4.1.1", @@ -60,7 +62,8 @@ "@types/node": "^10.17.13", "@types/prettier": "^1.19.0", "@types/rimraf": "^3.0.0", - "@types/yargs": "^15.0.1" + "@types/yargs": "^15.0.1", + "@types/zen-observable": "^0.8.4" }, "publishConfig": { "access": "public" From 1e484afe39a76ac633208698e3f780214819e44e Mon Sep 17 00:00:00 2001 From: Dane Pilcher Date: Tue, 26 Sep 2023 14:02:28 -0600 Subject: [PATCH 2/2] fix: generate multiple swift files in graphql-generator (#718) --- .../amplify-codegen/src/commands/types.js | 49 +- .../tests/commands/types.test.js | 21 +- packages/graphql-generator/API.md | 3 +- .../__snapshots__/types.test.ts.snap | 897 +++++------------- .../src/__tests__/types.test.ts | 35 +- packages/graphql-generator/src/types.ts | 2 +- packages/graphql-generator/src/typescript.ts | 3 +- packages/graphql-types-generator/API.md | 4 +- .../graphql-types-generator/src/generate.ts | 8 +- 9 files changed, 306 insertions(+), 716 deletions(-) diff --git a/packages/amplify-codegen/src/commands/types.js b/packages/amplify-codegen/src/commands/types.js index ee28bf6ff..c1bd220af 100644 --- a/packages/amplify-codegen/src/commands/types.js +++ b/packages/amplify-codegen/src/commands/types.js @@ -2,12 +2,13 @@ const path = require('path'); const fs = require('fs-extra'); const Ora = require('ora'); const glob = require('glob-all'); +const { Source } = require('graphql'); const constants = require('../constants'); const { loadConfig } = require('../codegen-config'); const { ensureIntrospectionSchema, getFrontEndHandler, getAppSyncAPIDetails, getAppSyncAPIInfoFromProject } = require('../utils'); const { generateTypes: generateTypesHelper } = require('@aws-amplify/graphql-generator'); -const { generate, extractDocumentFromJavascript } = require('@aws-amplify/graphql-types-generator'); +const { extractDocumentFromJavascript } = require('@aws-amplify/graphql-types-generator'); async function generateTypes(context, forceDownloadSchema, withoutInit = false, decoupleFrontend = '') { let frontend = decoupleFrontend; @@ -61,7 +62,7 @@ async function generateTypes(context, forceDownloadSchema, withoutInit = false, cwd: projectPath, absolute: true, }); - const queryFiles = queryFilePaths.map(queryFilePath => { + const queries = queryFilePaths.map(queryFilePath => { const fileContents = fs.readFileSync(queryFilePath, 'utf8'); if ( queryFilePath.endsWith('.jsx') || @@ -71,12 +72,11 @@ async function generateTypes(context, forceDownloadSchema, withoutInit = false, ) { return extractDocumentFromJavascript(fileContents, ''); } - return fileContents; + return new Source(fileContents, queryFilePath); }); - if (queryFiles.length === 0) { + if (queries.length === 0) { throw new Error("No queries found to generate types for, you may need to run 'codegen statements' first"); } - const queries = queryFiles.join('\n'); const schemaPath = path.join(projectPath, cfg.schema); @@ -93,32 +93,25 @@ async function generateTypes(context, forceDownloadSchema, withoutInit = false, codeGenSpinner.start(); const schema = fs.readFileSync(schemaPath, 'utf8'); const introspection = path.extname(schemaPath) === '.json'; - + const multipleSwiftFiles = target === 'swift' && fs.existsSync(outputPath) && fs.statSync(outputPath).isDirectory(); try { - if (target === 'swift' && fs.existsSync(outputPath) && fs.statSync(outputPath).isDirectory()) { - generate(queryFilePaths, schemaPath, outputPath, '', target, '', { - addTypename: true, - complexObjectSupport: 'auto', - }); + const output = await generateTypesHelper({ + schema, + queries, + target, + introspection, + multipleSwiftFiles, + }); + const outputs = Object.entries(output); + + const outputPath = path.join(projectPath, generatedFileName); + if (outputs.length === 1) { + const [[, contents]] = outputs; + fs.outputFileSync(path.resolve(outputPath), contents); } else { - const output = await generateTypesHelper({ - schema, - queries, - target, - introspection, - multipleSwiftFiles: false, + outputs.forEach(([filepath, contents]) => { + fs.outputFileSync(path.resolve(path.join(outputPath, filepath)), contents); }); - const outputs = Object.entries(output); - - const outputPath = path.join(projectPath, generatedFileName); - if (outputs.length === 1) { - const [[, contents]] = outputs; - fs.outputFileSync(path.resolve(outputPath), contents); - } else { - outputs.forEach(([filepath, contents]) => { - fs.outputFileSync(path.resolve(path.join(outputPath, filepath)), contents); - }); - } } codeGenSpinner.succeed(`${constants.INFO_MESSAGE_CODEGEN_GENERATE_SUCCESS} ${path.relative(path.resolve('.'), outputPath)}`); } catch (err) { diff --git a/packages/amplify-codegen/tests/commands/types.test.js b/packages/amplify-codegen/tests/commands/types.test.js index b492e0d55..c885209c1 100644 --- a/packages/amplify-codegen/tests/commands/types.test.js +++ b/packages/amplify-codegen/tests/commands/types.test.js @@ -1,8 +1,8 @@ const { sync } = require('glob-all'); const path = require('path'); const { generateTypes: generateTypesHelper } = require('@aws-amplify/graphql-generator'); -const { generate: legacyGenerate } = require('@aws-amplify/graphql-types-generator'); const fs = require('fs-extra'); +const { Source } = require('graphql'); const { loadConfig } = require('../../src/codegen-config'); const generateTypes = require('../../src/commands/types'); @@ -60,6 +60,9 @@ describe('command - types', () => { beforeEach(() => { jest.clearAllMocks(); fs.existsSync.mockReturnValue(true); + fs.statSync.mockReturnValue({ + isDirectory: jest.fn().mockReturnValue(false), + }); getFrontEndHandler.mockReturnValue('javascript'); loadConfig.mockReturnValue({ getProjects: jest.fn().mockReturnValue([MOCK_PROJECT]), @@ -79,7 +82,7 @@ describe('command - types', () => { expect(loadConfig).toHaveBeenCalledWith(MOCK_CONTEXT, false); expect(sync).toHaveBeenCalledWith([MOCK_INCLUDE_PATH, `!${MOCK_EXCLUDE_PATH}`], { cwd: MOCK_PROJECT_ROOT, absolute: true }); expect(generateTypesHelper).toHaveBeenCalledWith({ - queries: 'query 1\nquery 2', + queries: [new Source('query 1', 'q1.gql'), new Source('query 2', 'q2.gql')], schema: 'schema', target: 'TYPE_SCRIPT_OR_FLOW_OR_ANY_OTHER_LANGUAGE', introspection: false, @@ -87,7 +90,7 @@ describe('command - types', () => { }); }); - it('should use legacy types generation when generating multiple swift files', async () => { + it('should use generate multiple swift files', async () => { MOCK_PROJECT.amplifyExtension.codeGenTarget = 'swift'; MOCK_PROJECT.amplifyExtension.generatedFileName = 'typesDirectory'; const forceDownload = false; @@ -100,16 +103,6 @@ describe('command - types', () => { isDirectory: jest.fn().mockReturnValue(true), }); await generateTypes(MOCK_CONTEXT, forceDownload); - expect(generateTypesHelper).not.toHaveBeenCalled(); - expect(legacyGenerate).toHaveBeenCalledWith( - ['q1.gql', 'q2.gql'], - 'MOCK_PROJECT_ROOT/INTROSPECTION_SCHEMA.JSON', - 'MOCK_PROJECT_ROOT/typesDirectory', - '', - 'swift', - '', - { addTypename: true, complexObjectSupport: 'auto' }, - ); }); it('should not generate type if the frontend is android', async () => { @@ -121,6 +114,7 @@ describe('command - types', () => { it('should download the schema if forceDownload flag is passed', async () => { const forceDownload = true; + fs.readFileSync.mockReturnValueOnce('query 1').mockReturnValueOnce('query 2'); await generateTypes(MOCK_CONTEXT, forceDownload); expect(ensureIntrospectionSchema).toHaveBeenCalledWith( MOCK_CONTEXT, @@ -134,6 +128,7 @@ describe('command - types', () => { it('should download the schema if the schema file is missing', async () => { fs.existsSync.mockReturnValue(false); const forceDownload = false; + fs.readFileSync.mockReturnValueOnce('query 1').mockReturnValueOnce('query 2'); await generateTypes(MOCK_CONTEXT, forceDownload); expect(ensureIntrospectionSchema).toHaveBeenCalledWith( MOCK_CONTEXT, diff --git a/packages/graphql-generator/API.md b/packages/graphql-generator/API.md index 75571257c..5af155a79 100644 --- a/packages/graphql-generator/API.md +++ b/packages/graphql-generator/API.md @@ -4,6 +4,7 @@ ```ts +import { Source } from 'graphql'; import { Target } from '@aws-amplify/appsync-modelgen-plugin'; import { Target as Target_2 } from '@aws-amplify/graphql-types-generator'; @@ -49,7 +50,7 @@ export function generateTypes(options: GenerateTypesOptions): Promise { - value: GraphQLResult; -} + public var id: GraphQLID -export type Blog = { - __typename: \\"Blog\\"; - id: string; - name: string; - posts?: ModelPostConnection | null; - createdAt: string; - updatedAt: string; -}; + public init(id: GraphQLID) { + self.id = id + } -export type ModelPostConnection = { - __typename: \\"ModelPostConnection\\"; - items: Array; - nextToken?: string | null; -}; + public var variables: GraphQLMap? { + return [\\"id\\": id] + } -export type Post = { - __typename: \\"Post\\"; - id: string; - title: string; - blog?: Blog | null; - comments?: ModelCommentConnection | null; - createdAt: string; - updatedAt: string; - blogPostsId?: string | null; -}; + public struct Data: GraphQLSelectionSet { + public static let possibleTypes = [\\"Query\\"] -export type ModelCommentConnection = { - __typename: \\"ModelCommentConnection\\"; - items: Array; - nextToken?: string | null; -}; + public static let selections: [GraphQLSelection] = [ + GraphQLField(\\"getBlog\\", arguments: [\\"id\\": GraphQLVariable(\\"id\\")], type: .object(GetBlog.selections)), + ] -export type Comment = { - __typename: \\"Comment\\"; - id: string; - post?: Post | null; - content: string; - createdAt: string; - updatedAt: string; - postCommentsId?: string | null; -}; + public var snapshot: Snapshot -export type GetBlogQuery = { - __typename: \\"Blog\\"; - id: string; - name: string; - posts?: { - __typename: \\"ModelPostConnection\\"; - nextToken?: string | null; - } | null; - createdAt: string; - updatedAt: string; -}; + public init(snapshot: Snapshot) { + self.snapshot = snapshot + } -@Injectable({ - providedIn: \\"root\\" -}) -export class APIService { - async GetBlog(id: string): Promise { - const statement = \`query GetBlog($id: ID!) { - getBlog(id: $id) { - __typename - id - name - posts { - __typename - nextToken - } + public init(getBlog: GetBlog? = nil) { + self.init(snapshot: [\\"__typename\\": \\"Query\\", \\"getBlog\\": getBlog.flatMap { $0.snapshot }]) + } + + public var getBlog: GetBlog? { + get { + return (snapshot[\\"getBlog\\"] as? Snapshot).flatMap { GetBlog(snapshot: $0) } + } + set { + snapshot.updateValue(newValue?.snapshot, forKey: \\"getBlog\\") + } + } + + public struct GetBlog: GraphQLSelectionSet { + public static let possibleTypes = [\\"Blog\\"] + + public static let selections: [GraphQLSelection] = [ + GraphQLField(\\"__typename\\", type: .nonNull(.scalar(String.self))), + GraphQLField(\\"id\\", type: .nonNull(.scalar(GraphQLID.self))), + GraphQLField(\\"name\\", type: .nonNull(.scalar(String.self))), + GraphQLField(\\"posts\\", type: .object(Post.selections)), + GraphQLField(\\"createdAt\\", type: .nonNull(.scalar(String.self))), + GraphQLField(\\"updatedAt\\", type: .nonNull(.scalar(String.self))), + ] + + public var snapshot: Snapshot + + public init(snapshot: Snapshot) { + self.snapshot = snapshot + } + + public init(id: GraphQLID, name: String, posts: Post? = nil, createdAt: String, updatedAt: String) { + self.init(snapshot: [\\"__typename\\": \\"Blog\\", \\"id\\": id, \\"name\\": name, \\"posts\\": posts.flatMap { $0.snapshot }, \\"createdAt\\": createdAt, \\"updatedAt\\": updatedAt]) + } + + public var __typename: String { + get { + return snapshot[\\"__typename\\"]! as! String + } + set { + snapshot.updateValue(newValue, forKey: \\"__typename\\") + } + } + + public var id: GraphQLID { + get { + return snapshot[\\"id\\"]! as! GraphQLID + } + set { + snapshot.updateValue(newValue, forKey: \\"id\\") + } + } + + public var name: String { + get { + return snapshot[\\"name\\"]! as! String + } + set { + snapshot.updateValue(newValue, forKey: \\"name\\") + } + } + + public var posts: Post? { + get { + return (snapshot[\\"posts\\"] as? Snapshot).flatMap { Post(snapshot: $0) } + } + set { + snapshot.updateValue(newValue?.snapshot, forKey: \\"posts\\") + } + } + + public var createdAt: String { + get { + return snapshot[\\"createdAt\\"]! as! String + } + set { + snapshot.updateValue(newValue, forKey: \\"createdAt\\") + } + } + + public var updatedAt: String { + get { + return snapshot[\\"updatedAt\\"]! as! String + } + set { + snapshot.updateValue(newValue, forKey: \\"updatedAt\\") + } + } + + public struct Post: GraphQLSelectionSet { + public static let possibleTypes = [\\"ModelPostConnection\\"] + + public static let selections: [GraphQLSelection] = [ + GraphQLField(\\"__typename\\", type: .nonNull(.scalar(String.self))), + GraphQLField(\\"nextToken\\", type: .scalar(String.self)), + ] + + public var snapshot: Snapshot + + public init(snapshot: Snapshot) { + self.snapshot = snapshot + } + + public init(nextToken: String? = nil) { + self.init(snapshot: [\\"__typename\\": \\"ModelPostConnection\\", \\"nextToken\\": nextToken]) + } + + public var __typename: String { + get { + return snapshot[\\"__typename\\"]! as! String + } + set { + snapshot.updateValue(newValue, forKey: \\"__typename\\") + } + } + + public var nextToken: String? { + get { + return snapshot[\\"nextToken\\"] as? String + } + set { + snapshot.updateValue(newValue, forKey: \\"nextToken\\") + } + } + } + } + } +}", +} +`; + +exports[`generateTypes targets basic angular 1`] = ` +Object { + "api.service.ts": "/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. +import { Injectable } from \\"@angular/core\\"; +import API, { graphqlOperation, GraphQLResult } from \\"@aws-amplify/api-graphql\\"; +import { Observable } from \\"zen-observable-ts\\"; + +export interface SubscriptionResponse { + value: GraphQLResult; +} + +export type Blog = { + __typename: \\"Blog\\"; + id: string; + name: string; + posts?: ModelPostConnection | null; + createdAt: string; + updatedAt: string; +}; + +export type ModelPostConnection = { + __typename: \\"ModelPostConnection\\"; + items: Array; + nextToken?: string | null; +}; + +export type Post = { + __typename: \\"Post\\"; + id: string; + title: string; + blog?: Blog | null; + comments?: ModelCommentConnection | null; + createdAt: string; + updatedAt: string; + blogPostsId?: string | null; +}; + +export type ModelCommentConnection = { + __typename: \\"ModelCommentConnection\\"; + items: Array; + nextToken?: string | null; +}; + +export type Comment = { + __typename: \\"Comment\\"; + id: string; + post?: Post | null; + content: string; + createdAt: string; + updatedAt: string; + postCommentsId?: string | null; +}; + +export type GetBlogQuery = { + __typename: \\"Blog\\"; + id: string; + name: string; + posts?: { + __typename: \\"ModelPostConnection\\"; + nextToken?: string | null; + } | null; + createdAt: string; + updatedAt: string; +}; + +@Injectable({ + providedIn: \\"root\\" +}) +export class APIService { + async GetBlog(id: string): Promise { + const statement = \`query GetBlog($id: ID!) { + getBlog(id: $id) { + __typename + id + name + posts { + __typename + nextToken + } createdAt updatedAt } @@ -1733,7 +1733,7 @@ object GetBlogQuery extends com.apollographql.scalajs.GraphQLQuery { exports[`generateTypes targets basic swift 1`] = ` Object { - "GraphQL request.swift": "// This file was automatically generated and should not be edited. + "API.swift": "// This file was automatically generated and should not be edited. #if canImport(AWSAPIPlugin) import Foundation @@ -2312,429 +2312,6 @@ public final class GetBlogQuery: GraphQLQuery { } } }", - "Types.graphql.swift": "// This file was automatically generated and should not be edited. - -#if canImport(AWSAPIPlugin) -import Foundation - -public protocol GraphQLInputValue { -} - -public struct GraphQLVariable { - let name: String - - public init(_ name: String) { - self.name = name - } -} - -extension GraphQLVariable: GraphQLInputValue { -} - -extension JSONEncodable { - public func evaluate(with variables: [String: JSONEncodable]?) throws -> Any { - return jsonValue - } -} - -public typealias GraphQLMap = [String: JSONEncodable?] - -extension Dictionary where Key == String, Value == JSONEncodable? { - public var withNilValuesRemoved: Dictionary { - var filtered = Dictionary(minimumCapacity: count) - for (key, value) in self { - if value != nil { - filtered[key] = value - } - } - return filtered - } -} - -public protocol GraphQLMapConvertible: JSONEncodable { - var graphQLMap: GraphQLMap { get } -} - -public extension GraphQLMapConvertible { - var jsonValue: Any { - return graphQLMap.withNilValuesRemoved.jsonValue - } -} - -public typealias GraphQLID = String - -public protocol APISwiftGraphQLOperation: AnyObject { - - static var operationString: String { get } - static var requestString: String { get } - static var operationIdentifier: String? { get } - - var variables: GraphQLMap? { get } - - associatedtype Data: GraphQLSelectionSet -} - -public extension APISwiftGraphQLOperation { - static var requestString: String { - return operationString - } - - static var operationIdentifier: String? { - return nil - } - - var variables: GraphQLMap? { - return nil - } -} - -public protocol GraphQLQuery: APISwiftGraphQLOperation {} - -public protocol GraphQLMutation: APISwiftGraphQLOperation {} - -public protocol GraphQLSubscription: APISwiftGraphQLOperation {} - -public protocol GraphQLFragment: GraphQLSelectionSet { - static var possibleTypes: [String] { get } -} - -public typealias Snapshot = [String: Any?] - -public protocol GraphQLSelectionSet: Decodable { - static var selections: [GraphQLSelection] { get } - - var snapshot: Snapshot { get } - init(snapshot: Snapshot) -} - -extension GraphQLSelectionSet { - public init(from decoder: Decoder) throws { - if let jsonObject = try? APISwiftJSONValue(from: decoder) { - let encoder = JSONEncoder() - let jsonData = try encoder.encode(jsonObject) - let decodedDictionary = try JSONSerialization.jsonObject(with: jsonData, options: []) as! [String: Any] - let optionalDictionary = decodedDictionary.mapValues { $0 as Any? } - - self.init(snapshot: optionalDictionary) - } else { - self.init(snapshot: [:]) - } - } -} - -enum APISwiftJSONValue: Codable { - case array([APISwiftJSONValue]) - case boolean(Bool) - case number(Double) - case object([String: APISwiftJSONValue]) - case string(String) - case null - - init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - - if let value = try? container.decode([String: APISwiftJSONValue].self) { - self = .object(value) - } else if let value = try? container.decode([APISwiftJSONValue].self) { - self = .array(value) - } else if let value = try? container.decode(Double.self) { - self = .number(value) - } else if let value = try? container.decode(Bool.self) { - self = .boolean(value) - } else if let value = try? container.decode(String.self) { - self = .string(value) - } else { - self = .null - } - } - - func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - - switch self { - case .array(let value): - try container.encode(value) - case .boolean(let value): - try container.encode(value) - case .number(let value): - try container.encode(value) - case .object(let value): - try container.encode(value) - case .string(let value): - try container.encode(value) - case .null: - try container.encodeNil() - } - } -} - -public protocol GraphQLSelection { -} - -public struct GraphQLField: GraphQLSelection { - let name: String - let alias: String? - let arguments: [String: GraphQLInputValue]? - - var responseKey: String { - return alias ?? name - } - - let type: GraphQLOutputType - - public init(_ name: String, alias: String? = nil, arguments: [String: GraphQLInputValue]? = nil, type: GraphQLOutputType) { - self.name = name - self.alias = alias - - self.arguments = arguments - - self.type = type - } -} - -public indirect enum GraphQLOutputType { - case scalar(JSONDecodable.Type) - case object([GraphQLSelection]) - case nonNull(GraphQLOutputType) - case list(GraphQLOutputType) - - var namedType: GraphQLOutputType { - switch self { - case .nonNull(let innerType), .list(let innerType): - return innerType.namedType - case .scalar, .object: - return self - } - } -} - -public struct GraphQLBooleanCondition: GraphQLSelection { - let variableName: String - let inverted: Bool - let selections: [GraphQLSelection] - - public init(variableName: String, inverted: Bool, selections: [GraphQLSelection]) { - self.variableName = variableName - self.inverted = inverted; - self.selections = selections; - } -} - -public struct GraphQLTypeCondition: GraphQLSelection { - let possibleTypes: [String] - let selections: [GraphQLSelection] - - public init(possibleTypes: [String], selections: [GraphQLSelection]) { - self.possibleTypes = possibleTypes - self.selections = selections; - } -} - -public struct GraphQLFragmentSpread: GraphQLSelection { - let fragment: GraphQLFragment.Type - - public init(_ fragment: GraphQLFragment.Type) { - self.fragment = fragment - } -} - -public struct GraphQLTypeCase: GraphQLSelection { - let variants: [String: [GraphQLSelection]] - let \`default\`: [GraphQLSelection] - - public init(variants: [String: [GraphQLSelection]], default: [GraphQLSelection]) { - self.variants = variants - self.default = \`default\`; - } -} - -public typealias JSONObject = [String: Any] - -public protocol JSONDecodable { - init(jsonValue value: Any) throws -} - -public protocol JSONEncodable: GraphQLInputValue { - var jsonValue: Any { get } -} - -public enum JSONDecodingError: Error, LocalizedError { - case missingValue - case nullValue - case wrongType - case couldNotConvert(value: Any, to: Any.Type) - - public var errorDescription: String? { - switch self { - case .missingValue: - return \\"Missing value\\" - case .nullValue: - return \\"Unexpected null value\\" - case .wrongType: - return \\"Wrong type\\" - case .couldNotConvert(let value, let expectedType): - return \\"Could not convert \\\\\\"\\\\(value)\\\\\\" to \\\\(expectedType)\\" - } - } -} - -extension String: JSONDecodable, JSONEncodable { - public init(jsonValue value: Any) throws { - guard let string = value as? String else { - throw JSONDecodingError.couldNotConvert(value: value, to: String.self) - } - self = string - } - - public var jsonValue: Any { - return self - } -} - -extension Int: JSONDecodable, JSONEncodable { - public init(jsonValue value: Any) throws { - guard let number = value as? NSNumber else { - throw JSONDecodingError.couldNotConvert(value: value, to: Int.self) - } - self = number.intValue - } - - public var jsonValue: Any { - return self - } -} - -extension Float: JSONDecodable, JSONEncodable { - public init(jsonValue value: Any) throws { - guard let number = value as? NSNumber else { - throw JSONDecodingError.couldNotConvert(value: value, to: Float.self) - } - self = number.floatValue - } - - public var jsonValue: Any { - return self - } -} - -extension Double: JSONDecodable, JSONEncodable { - public init(jsonValue value: Any) throws { - guard let number = value as? NSNumber else { - throw JSONDecodingError.couldNotConvert(value: value, to: Double.self) - } - self = number.doubleValue - } - - public var jsonValue: Any { - return self - } -} - -extension Bool: JSONDecodable, JSONEncodable { - public init(jsonValue value: Any) throws { - guard let bool = value as? Bool else { - throw JSONDecodingError.couldNotConvert(value: value, to: Bool.self) - } - self = bool - } - - public var jsonValue: Any { - return self - } -} - -extension RawRepresentable where RawValue: JSONDecodable { - public init(jsonValue value: Any) throws { - let rawValue = try RawValue(jsonValue: value) - if let tempSelf = Self(rawValue: rawValue) { - self = tempSelf - } else { - throw JSONDecodingError.couldNotConvert(value: value, to: Self.self) - } - } -} - -extension RawRepresentable where RawValue: JSONEncodable { - public var jsonValue: Any { - return rawValue.jsonValue - } -} - -extension Optional where Wrapped: JSONDecodable { - public init(jsonValue value: Any) throws { - if value is NSNull { - self = .none - } else { - self = .some(try Wrapped(jsonValue: value)) - } - } -} - -extension Optional: JSONEncodable { - public var jsonValue: Any { - switch self { - case .none: - return NSNull() - case .some(let wrapped as JSONEncodable): - return wrapped.jsonValue - default: - fatalError(\\"Optional is only JSONEncodable if Wrapped is\\") - } - } -} - -extension Dictionary: JSONEncodable { - public var jsonValue: Any { - return jsonObject - } - - public var jsonObject: JSONObject { - var jsonObject = JSONObject(minimumCapacity: count) - for (key, value) in self { - if case let (key as String, value as JSONEncodable) = (key, value) { - jsonObject[key] = value.jsonValue - } else { - fatalError(\\"Dictionary is only JSONEncodable if Value is (and if Key is String)\\") - } - } - return jsonObject - } -} - -extension Array: JSONEncodable { - public var jsonValue: Any { - return map() { element -> (Any) in - if case let element as JSONEncodable = element { - return element.jsonValue - } else { - fatalError(\\"Array is only JSONEncodable if Element is\\") - } - } - } -} - -extension URL: JSONDecodable, JSONEncodable { - public init(jsonValue value: Any) throws { - guard let string = value as? String else { - throw JSONDecodingError.couldNotConvert(value: value, to: URL.self) - } - self.init(string: string)! - } - - public var jsonValue: Any { - return self.absoluteString - } -} - -extension Dictionary { - static func += (lhs: inout Dictionary, rhs: Dictionary) { - lhs.merge(rhs) { (_, new) in new } - } -} - -#elseif canImport(AWSAppSync) -import AWSAppSync -#endif", } `; diff --git a/packages/graphql-generator/src/__tests__/types.test.ts b/packages/graphql-generator/src/__tests__/types.test.ts index 6c668d276..63ecf6f23 100644 --- a/packages/graphql-generator/src/__tests__/types.test.ts +++ b/packages/graphql-generator/src/__tests__/types.test.ts @@ -1,3 +1,4 @@ +import { Source } from 'graphql'; import { generateTypes, GenerateTypesOptions, TypesTarget } from '..'; import { readSchema } from './utils'; @@ -37,15 +38,31 @@ describe('generateTypes', () => { }); }); - test('multipleSwiftFiles', async () => { - const options: GenerateTypesOptions = { - schema: sdlSchema, - queries, - target: 'swift', - multipleSwiftFiles: true, - }; + describe('multipleSwiftFiles', () => { + test('generates multiple files', async () => { + const filename = 'queries.graphql'; + const options: GenerateTypesOptions = { + schema: sdlSchema, + queries: [new Source(queries, filename)], + target: 'swift', + multipleSwiftFiles: true, + }; - const types = await generateTypes(options); - expect(types).toMatchSnapshot(); + const types = await generateTypes(options); + expect(Object.keys(types)).toEqual(['Types.graphql.swift', `${filename}.swift`]); + expect(types).toMatchSnapshot(); + }); + + test('throws error if not using Source', async () => { + const filename = 'queries.graphql'; + const options: GenerateTypesOptions = { + schema: sdlSchema, + queries: queries, + target: 'swift', + multipleSwiftFiles: true, + }; + + expect(generateTypes(options)).rejects.toThrow('Query documents must be of type Source[] when generating multiple Swift files.'); + }); }); }); diff --git a/packages/graphql-generator/src/types.ts b/packages/graphql-generator/src/types.ts index e7da18f92..bbae55687 100644 --- a/packages/graphql-generator/src/types.ts +++ b/packages/graphql-generator/src/types.ts @@ -3,7 +3,7 @@ import { generate, generateFromString } from '@aws-amplify/graphql-types-generat import { GenerateTypesOptions, GeneratedOutput } from './typescript'; export async function generateTypes(options: GenerateTypesOptions): Promise { - const { schema, target, queries, multipleSwiftFiles = true, introspection = false } = options; + const { schema, target, queries, multipleSwiftFiles = false, introspection = false } = options; const generatedOutput = await generateFromString(schema, introspection, queries, target, multipleSwiftFiles, { addTypename: true, diff --git a/packages/graphql-generator/src/typescript.ts b/packages/graphql-generator/src/typescript.ts index 035b44bc6..1d83ee922 100644 --- a/packages/graphql-generator/src/typescript.ts +++ b/packages/graphql-generator/src/typescript.ts @@ -1,3 +1,4 @@ +import { Source } from 'graphql'; import { Target as GraphqlTypesGeneratorTarget } from '@aws-amplify/graphql-types-generator'; import { Target as AppsyncModelgenPluginTarget } from '@aws-amplify/appsync-modelgen-plugin'; @@ -10,7 +11,7 @@ export type FileExtension = 'js' | 'graphql' | 'ts'; export type GenerateTypesOptions = { schema: string; target: TypesTarget; - queries: string; + queries: string | Source[]; introspection?: boolean; multipleSwiftFiles?: boolean; // only used when target is swift }; diff --git a/packages/graphql-types-generator/API.md b/packages/graphql-types-generator/API.md index f37264a4b..70845b871 100644 --- a/packages/graphql-types-generator/API.md +++ b/packages/graphql-types-generator/API.md @@ -4,6 +4,8 @@ ```ts +import { Source } from 'graphql'; + // @public (undocumented) export function extractDocumentFromJavascript(content: string, tagName?: string): string | null; @@ -11,7 +13,7 @@ export function extractDocumentFromJavascript(content: string, tagName?: string) export function generate(inputPaths: string[], schemaPath: string, outputPath: string, only: string, target: Target, tagName: string, options: any): void; // @public (undocumented) -export function generateFromString(schema: string, introspection: boolean, queryDocuments: string, target: Target, multipleSwiftFiles: boolean, options: any): { +export function generateFromString(schema: string, introspection: boolean, queryDocuments: string | Source[], target: Target, multipleSwiftFiles: boolean, options: any): { [filepath: string]: string; }; diff --git a/packages/graphql-types-generator/src/generate.ts b/packages/graphql-types-generator/src/generate.ts index 26c7474eb..b60d8d589 100644 --- a/packages/graphql-types-generator/src/generate.ts +++ b/packages/graphql-types-generator/src/generate.ts @@ -84,13 +84,17 @@ function generateFromFile( export function generateFromString( schema: string, introspection: boolean, - queryDocuments: string, + queryDocuments: string | Source[], target: Target, multipleSwiftFiles: boolean, options: any, ): { [filepath: string]: string } { + if (typeof queryDocuments === 'string' && multipleSwiftFiles) { + throw new Error('Query documents must be of type Source[] when generating multiple Swift files.'); + } const graphqlSchema = parseSchema(schema, introspection); - const document = parseAndMergeQueryDocuments([new Source(queryDocuments)]); + const queryDocumentSources = typeof queryDocuments === 'string' ? [new Source(queryDocuments)] : queryDocuments; + const document = parseAndMergeQueryDocuments(queryDocumentSources); validateQueryDocument(graphqlSchema, document); const output = generateForTarget(graphqlSchema, document, '', target, multipleSwiftFiles, options);