diff --git a/CHANGELOG.md b/CHANGELOG.md index fb5f74e8..32810f7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Moved registration of `cds.compile.to.gql` and `cds.compile.to.graphql` targets from `@sap/cds` to `@cap-js/graphql` + ### Fixed ### Removed diff --git a/cds-plugin.js b/cds-plugin.js index 17ab8fdc..0d9b4d22 100644 --- a/cds-plugin.js +++ b/cds-plugin.js @@ -1,4 +1,5 @@ const cds = require('@sap/cds') +require('./lib/api').registerCompileTargets() const protocols = cds.env.protocols ??= {} if (!protocols.graphql) protocols.graphql = { path: "/graphql", impl: "@cap-js/graphql" diff --git a/lib/api.js b/lib/api.js new file mode 100644 index 00000000..58e82a3b --- /dev/null +++ b/lib/api.js @@ -0,0 +1,23 @@ +const cds = require('@sap/cds') + +const TARGETS = ['gql', 'graphql'] + +function _lazyRegisterCompileTargets() { + const value = require('./compile') + // Lazy load all compile targets if any of them is accessed + // For example .to.gql was called -> load .to.gql and .to.graphql + TARGETS.forEach(target => Object.defineProperty(this, target, { value })) + return value +} + +// Register gql and graphql as cds.compile.to targets +const registerCompileTargets = () => { + TARGETS.forEach(target => + Object.defineProperty(cds.compile.to, target, { + get: _lazyRegisterCompileTargets, + configurable: true + }) + ) +} + +module.exports = { registerCompileTargets } diff --git a/lib/compile.js b/lib/compile.js new file mode 100644 index 00000000..2e34e54b --- /dev/null +++ b/lib/compile.js @@ -0,0 +1,17 @@ +const cds = require('@sap/cds') +const { generateSchema4 } = require('./schema') +const { lexicographicSortSchema, printSchema } = require('graphql') + +function cds_compile_to_gql(csn, options = {}) { + const m = cds.linked(csn) + const services = Object.fromEntries(m.services.map(s => [s.name, new cds.ApplicationService(s.name, m)])) + + let schema = generateSchema4(services) + + if (options.sort) schema = lexicographicSortSchema(schema) + if (/^obj|object$/i.test(options.as)) return schema + + return printSchema(schema) +} + +module.exports = cds_compile_to_gql diff --git a/lib/schema/index.js b/lib/schema/index.js index a96e4701..322b3403 100644 --- a/lib/schema/index.js +++ b/lib/schema/index.js @@ -1,16 +1,13 @@ const queryGenerator = require('./query') const mutationGenerator = require('./mutation') -const { GraphQLSchema, printSchema } = require('graphql') +const { GraphQLSchema } = require('graphql') const { createRootResolvers, registerAliasFieldResolvers } = require('../resolvers') +const { printSchema } = require('graphql') +// REVISIT: remove class with cds^8 class SchemaGenerator { generate(services) { - const resolvers = createRootResolvers(services) - const cache = new Map() - const query = queryGenerator(cache).generateQueryObjectType(services, resolvers.Query) - const mutation = mutationGenerator(cache).generateMutationObjectType(services, resolvers.Mutation) - this._schema = new GraphQLSchema({ query, mutation }) - registerAliasFieldResolvers(this._schema) + this._schema = generateSchema4(services) return this } @@ -24,7 +21,13 @@ class SchemaGenerator { } function generateSchema4(services) { - return new SchemaGenerator().generate(services).getSchema() + const resolvers = createRootResolvers(services) + const cache = new Map() + const query = queryGenerator(cache).generateQueryObjectType(services, resolvers.Query) + const mutation = mutationGenerator(cache).generateMutationObjectType(services, resolvers.Mutation) + const schema = new GraphQLSchema({ query, mutation }) + registerAliasFieldResolvers(schema) + return schema } module.exports = { SchemaGenerator, generateSchema4 } diff --git a/test/resources/bookshop-graphql/package.json b/test/resources/bookshop-graphql/package.json index 4b36951d..100137e3 100644 --- a/test/resources/bookshop-graphql/package.json +++ b/test/resources/bookshop-graphql/package.json @@ -1,4 +1,7 @@ { + "dependencies": { + "@cap-js/graphql": "../../.." + }, "cds": { "requires": { "db": { diff --git a/test/scripts/generate-schemas.js b/test/scripts/generate-schemas.js index f1b544cc..57e08a1d 100644 --- a/test/scripts/generate-schemas.js +++ b/test/scripts/generate-schemas.js @@ -1,16 +1,19 @@ #!/usr/bin/env node +const cds = require('@sap/cds') +// Load @cap-js/graphql plugin to ensure .to.gql and .to.graphql compile targets are registered +require('../../cds-plugin') const path = require('path') const fs = require('fs') -const { SCHEMAS_DIR, cdsFilesToGQLSchema, formatSchema } = require('../util') +const { SCHEMAS_DIR } = require('../util') const { models } = require('../resources') -const { printSchema } = require('graphql') ;(async () => { fs.rmSync(SCHEMAS_DIR, { recursive: true, force: true }) fs.mkdirSync(SCHEMAS_DIR) for (const model of models) { console.log(`Generating GraphQL schema "${model.name}.gql"`) - const graphQLSchema = formatSchema(printSchema(await cdsFilesToGQLSchema(model.files))) + const csn = await cds.load(model.files) + const graphQLSchema = cds.compile(csn).to.gql({ sort: true }) const schemaPath = path.join(SCHEMAS_DIR, `${model.name}.gql`) const schemaPathDir = path.parse(schemaPath).dir if (!fs.existsSync(schemaPathDir)) fs.mkdirSync(schemaPathDir) diff --git a/test/tests/enrich.test.js b/test/tests/enrich.test.js index 6ec334eb..fff63857 100644 --- a/test/tests/enrich.test.js +++ b/test/tests/enrich.test.js @@ -1,15 +1,19 @@ describe('graphql - enrich AST with parsed inline literal values', () => { + const cds = require('@sap/cds') + // Load @cap-js/graphql plugin to ensure .to.gql and .to.graphql compile targets are registered + require('../../cds-plugin') const { gql } = require('../util') const { parse } = require('graphql') const enrich = require('../../lib/resolvers/parse/ast/enrich') const { models } = require('../resources') - const { cdsFilesToGQLSchema, fakeInfoObject } = require('../util') + const { fakeInfoObject } = require('../util') let bookshopSchema beforeAll(async () => { const bookshopModel = models.find(m => m.name === 'bookshop-graphql') - bookshopSchema = await cdsFilesToGQLSchema(bookshopModel.files) + const csn = await cds.load(bookshopModel.files) + bookshopSchema = cds.compile(csn).to.gql({ as: 'object' }) }) test('parsing of literal value as top level argument', async () => { diff --git a/test/tests/schema.test.js b/test/tests/schema.test.js index 6a6ee1aa..0090bc51 100644 --- a/test/tests/schema.test.js +++ b/test/tests/schema.test.js @@ -1,24 +1,28 @@ const fs = require('fs') const path = require('path') const semver = require('semver') -const cdsVersion = require('@sap/cds').version +const { version: cdsVersion } = require('@sap/cds') +// Load @cap-js/graphql plugin to ensure .to.gql and .to.graphql compile targets are registered +require('../../cds-plugin') -let { models } = require('../resources') -models = models.filter(m => !m.requires_cds || semver.satisfies(cdsVersion, m.requires_cds)) +const models = require('../resources').models.filter( + m => !m.requires_cds || semver.satisfies(cdsVersion, m.requires_cds) +) -const { SCHEMAS_DIR, cdsFilesToGQLSchema, formatSchema } = require('../util') +const { SCHEMAS_DIR } = require('../util') const { printSchema, validateSchema } = require('graphql') describe('graphql - schema generation', () => { describe('generated schema should match saved schema', () => { models.forEach(model => { it('should process model ' + model.name, async () => { - const generatedSchemaObject = await cdsFilesToGQLSchema(model.files) + const csn = await cds.load(model.files) + const generatedSchemaObject = cds.compile(csn).to.graphql({ as: 'obj', sort: true }) const schemaValidationErrors = validateSchema(generatedSchemaObject) expect(schemaValidationErrors.length).toEqual(0) - const loadedSchema = formatSchema(fs.readFileSync(path.join(SCHEMAS_DIR, `${model.name}.gql`), 'utf-8')) - const generatedSchema = formatSchema(printSchema(generatedSchemaObject)) + const loadedSchema = fs.readFileSync(path.join(SCHEMAS_DIR, `${model.name}.gql`), 'utf-8') + const generatedSchema = printSchema(generatedSchemaObject) expect(loadedSchema).toEqual(generatedSchema) }) }) diff --git a/test/util/index.js b/test/util/index.js index 4f12fb52..59b4641e 100644 --- a/test/util/index.js +++ b/test/util/index.js @@ -1,19 +1,8 @@ const path = require('path') -const { buildSchema, lexicographicSortSchema, printSchema, Kind } = require('graphql') +const { Kind } = require('graphql') const SCHEMAS_DIR = path.join(__dirname, '../schemas') -const cdsFilesToGQLSchema = async files => { - const cds = require('@sap/cds/lib') - const { generateSchema4 } = require('../../lib/schema') - - const m = cds.linked(await cds.load(files)) - const services = Object.fromEntries(m.services.map(s => [s.name, new cds.ApplicationService(s.name, m)])) - return generateSchema4(services) -} - -const formatSchema = schemaString => printSchema(lexicographicSortSchema(buildSchema(schemaString))) - /** * Create a fake/mock object that matches the structure of the info object that is passed to resolver functions by the graphql.js library. * @@ -44,4 +33,4 @@ const fakeInfoObject = (document, schema, parentTypeName, variables) => { */ const gql = String.raw -module.exports = { SCHEMAS_DIR, cdsFilesToGQLSchema, formatSchema, fakeInfoObject, gql } +module.exports = { SCHEMAS_DIR, fakeInfoObject, gql }