From 0606a5f870832d823a0f2f4a7904b32f707493c1 Mon Sep 17 00:00:00 2001 From: Mitja Debeljak Date: Fri, 19 Jul 2019 13:33:59 +0200 Subject: [PATCH 1/4] Add module with domain-schema --- modules/core/common/generatedSchemas.js | 1 + modules/core/common/index.ts | 1 + packages/server/package.json | 3 + tools/cli.js | 10 +- tools/cli/CommandInvoker.js | 11 +- tools/cli/commands/addModule.js | 9 +- tools/cli/commands/deleteModule.js | 19 +- tools/cli/commands/updateSchema.js | 174 ++++++++++++++++++ tools/cli/helpers/util.js | 70 ++++++- .../module/client-react/package.json | 5 +- tools/templates/module/common/index.ts | 1 + tools/templates/module/common/package.json | 8 + tools/templates/module/common/schema.js | 14 ++ .../module/server-ts/migrations/T_Module.js | 6 + tools/templates/module/server-ts/package.json | 5 +- tools/templates/module/server-ts/resolvers.ts | 6 +- .../templates/module/server-ts/schema.graphql | 19 +- .../module/server-ts/seeds/.eslintrc | 5 + .../module/server-ts/seeds/T_Module.js | 7 + yarn.lock | 39 +++- 20 files changed, 385 insertions(+), 28 deletions(-) create mode 100644 modules/core/common/generatedSchemas.js create mode 100644 tools/cli/commands/updateSchema.js create mode 100644 tools/templates/module/common/index.ts create mode 100644 tools/templates/module/common/package.json create mode 100644 tools/templates/module/common/schema.js create mode 100644 tools/templates/module/server-ts/migrations/T_Module.js create mode 100644 tools/templates/module/server-ts/seeds/.eslintrc create mode 100644 tools/templates/module/server-ts/seeds/T_Module.js diff --git a/modules/core/common/generatedSchemas.js b/modules/core/common/generatedSchemas.js new file mode 100644 index 0000000000..ff8b4c5632 --- /dev/null +++ b/modules/core/common/generatedSchemas.js @@ -0,0 +1 @@ +export default {}; diff --git a/modules/core/common/index.ts b/modules/core/common/index.ts index 9eed7dfd16..6435721cc2 100644 --- a/modules/core/common/index.ts +++ b/modules/core/common/index.ts @@ -4,5 +4,6 @@ export * from './net'; export { default as log } from './log'; export { default as createApolloClient } from './createApolloClient'; export { default as createReduxStore } from './createReduxStore'; +export { default as schemas } from './generatedSchemas'; export * from './createReduxStore'; export * from './utils'; diff --git a/packages/server/package.json b/packages/server/package.json index b94a80787b..57d44e4399 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -57,6 +57,9 @@ "dependencies": { "@babel/preset-env": "^7.0.0", "@babel/register": "^7.0.0", + "@domain-schema/core": "^0.0.34", + "@domain-schema/graphql": "^0.0.33", + "@domain-schema/knex": "^0.0.35", "@gqlapp/core-common": "^0.1.0", "@gqlapp/database-server-ts": "^0.1.0", "@gqlapp/testing-server-ts": "^0.1.0", diff --git a/tools/cli.js b/tools/cli.js index 26443a0587..2a059092f2 100644 --- a/tools/cli.js +++ b/tools/cli.js @@ -12,6 +12,7 @@ const addModuleCommand = require('./cli/commands/addModule'); const deleteModuleCommand = require('./cli/commands/deleteModule'); const chooseTemplateCommand = require('./cli/commands/chooseTemplate'); const deleteStackCommand = require('./cli/commands/deleteStack'); +const updateSchemaCommand = require('./cli/commands/updateSchema'); const CommandInvoker = require('./cli/CommandInvoker'); @@ -19,7 +20,8 @@ const commandInvoker = new CommandInvoker( addModuleCommand, deleteModuleCommand, chooseTemplateCommand, - deleteStackCommand + deleteStackCommand, + updateSchemaCommand ); prog @@ -55,6 +57,10 @@ List of technologies [react, angular, vue, scala, node]` .action(({ stackList }, { list }, logger) => { commandInvoker.runDeleteStack(stackList, logger, list); - }); + }) + // Update schema + .command('updateschema', 'Update Module Schema') + .argument('', 'Module name') + .action((args, options, logger) => commandInvoker.runUpdateSchema(args, options, logger)); prog.parse(process.argv); diff --git a/tools/cli/CommandInvoker.js b/tools/cli/CommandInvoker.js index 3e61413b9e..a0977a9045 100644 --- a/tools/cli/CommandInvoker.js +++ b/tools/cli/CommandInvoker.js @@ -11,11 +11,12 @@ class CommandInvoker { * @param chooseStack - The function for choosing stack of technologies. * @param deleteStack - The function for delete stack of technologies. */ - constructor(addModule, deleteModule, chooseStack, deleteStack) { + constructor(addModule, deleteModule, chooseStack, deleteStack, updateSchema) { this.addModule = addModule; this.deleteModule = deleteModule; this.chooseStack = chooseStack; this.deleteStack = deleteStack; + this.updateSchema = updateSchema; } /** @@ -30,6 +31,7 @@ class CommandInvoker { if (location === 'both') { runFunc('client'); + runFunc('common'); runFunc('server'); } else { runFunc(location); @@ -63,6 +65,13 @@ class CommandInvoker { runDeleteStack(args, logger, isShowStackList) { this.deleteStack(args, logger, isShowStackList); } + + /** + * Runs operation (function) for updating existing module schema. + */ + runUpdateSchema(args, options, logger) { + runOperation(this.updateSchema, args, options, logger); + } } function runOperation(operation, args, options, logger) { diff --git a/tools/cli/commands/addModule.js b/tools/cli/commands/addModule.js index 29808e58e5..42cc228751 100644 --- a/tools/cli/commands/addModule.js +++ b/tools/cli/commands/addModule.js @@ -27,8 +27,11 @@ function addModule({ logger, packageName, moduleName, old }) { const params = { logger, packageName, moduleName, modulePackageName, templatesPath, old }; copyTemplates(params); - mergeWithModules(params); - if (!old) addDependency(params); + if (packageName !== 'common') { + mergeWithModules(params); + if (!old) addDependency(params); + } + addSymlink(moduleName, modulePackageName); logger.info(chalk.green(`✔ New module ${moduleName} for package ${packageName} successfully created!`)); } @@ -112,8 +115,6 @@ function addDependency({ moduleName, modulePackageName, packageName, old }) { ) ) .to(packagePath); - - addSymlink(moduleName, modulePackageName); } module.exports = addModule; diff --git a/tools/cli/commands/deleteModule.js b/tools/cli/commands/deleteModule.js index bc46882758..9ae31e6ae8 100644 --- a/tools/cli/commands/deleteModule.js +++ b/tools/cli/commands/deleteModule.js @@ -1,6 +1,7 @@ const shell = require('shelljs'); const fs = require('fs'); const chalk = require('chalk'); +const { pascalize } = require('humps'); const { getModulePackageName, computeModulePath, @@ -8,6 +9,8 @@ const { computeRootModulesPath, computePackagePath, computeModulePackageName, + computeGeneratedSchemasPath, + deleteFromFileWithExports, removeSymlink, runPrettier } = require('../helpers/util'); @@ -27,8 +30,12 @@ function deleteModule({ logger, packageName, moduleName, old }) { if (fs.existsSync(modulePath)) { deleteTemplates(params); - removeFromModules(params); - if (!old) removeDependency(params); + + if (packageName !== 'common') { + removeFromModules(params); + if (!old) removeDependency(params); + } + removeSymlink(moduleName, modulePackageName); logger.info(chalk.green(`✔ Module ${moduleName} for package ${packageName} successfully deleted!`)); } else { @@ -118,7 +125,13 @@ function removeDependency({ moduleName, packageName, modulePackageName, old }) { ) .to(packagePath); - removeSymlink(moduleName, modulePackageName); + const Module = pascalize(moduleName); + const fileName = 'generatedSchemas.js'; + const generatedSchemaPath = computeGeneratedSchemasPath(packageName, fileName, old); + if (fs.existsSync(generatedSchemaPath)) { + const schema = `${Module}Schema`; + deleteFromFileWithExports(generatedSchemaPath, schema); + } } module.exports = deleteModule; diff --git a/tools/cli/commands/updateSchema.js b/tools/cli/commands/updateSchema.js new file mode 100644 index 0000000000..9604449d42 --- /dev/null +++ b/tools/cli/commands/updateSchema.js @@ -0,0 +1,174 @@ +// const shell = require('shelljs'); +const fs = require('fs'); +const chalk = require('chalk'); +// const GraphQLGenerator = require('@domain-schema/graphql').default; +const { pascalize } = require('humps'); + +const { getModulePackageName, computeModulePath } = require('../helpers/util'); +// const schemas = require('../../../modules/core/common/generatedSchemas'); + +/** + * Update module schema. + * + * @param logger - The Logger. + * @param moduleName - The name of a new module. + * @param packageName - The location for a new module [client|server|both]. + */ +function updateModule({ logger, packageName, moduleName, old }) { + if (packageName === 'server') { + logger.info(`Updating ${moduleName} Schema…`); + + // pascalize + const Module = pascalize(moduleName); + //const modulePath = `${BASE_PATH}/packages/server/src/modules/${moduleName}`; + const modulePackageName = getModulePackageName(packageName, old); + const destinationPath = computeModulePath(modulePackageName, old, moduleName); + + if (fs.existsSync(destinationPath)) { + console.log('Module:', Module); + // get module schema + /*const schema = schemas.default[`${Module}Schema`]; + + // schema file + const file = `schema.graphql`; + + // regenerate input fields + let inputCreate = ''; + let inputUpdate = ''; + let inputFilter = ` searchText: String\n`; + let manyInput = ''; + for (const key of schema.keys()) { + const value = schema.values[key]; + const hasTypeOf = targetType => value.type === targetType || value.type.prototype instanceof targetType; + if (value.type.isSchema) { + let required = value.optional ? '' : '!'; + const id = value.noIdSuffix ? '' : 'Id'; + inputCreate += ` ${key}${id}: Int${required}\n`; + inputUpdate += ` ${key}${id}: Int\n`; + inputFilter += ` ${key}${id}: Int\n`; + inputFilter += ` ${key}${id}_in: [Int!]\n`; + inputFilter += ` ${key}${id}_contains: Int\n`; + } else if (value.type.constructor !== Array) { + if (key !== 'id') { + inputCreate += ` ${key}: ${generateField(value)}\n`; + inputUpdate += ` ${key}: ${generateField(value, true)}\n`; + } + + if (hasTypeOf(Date)) { + inputFilter += ` ${key}_lte: ${generateField(value, true)}\n`; + inputFilter += ` ${key}_gte: ${generateField(value, true)}\n`; + } else if (key === 'id' || hasTypeOf(String)) { + inputFilter += ` ${key}: ${generateField(value, true)}\n`; + inputFilter += ` ${key}_in: [${generateField(value, true)}!]\n`; + inputFilter += ` ${key}_contains: ${generateField(value, true)}\n`; + } else { + inputFilter += ` ${key}: ${generateField(value, true)}\n`; + } + } else if (value.type.constructor === Array && value.type[0].isSchema) { + inputCreate += ` ${key}: ${pascalize(key)}CreateManyInput\n`; + inputUpdate += ` ${key}: ${pascalize(key)}UpdateManyInput\n`; + inputFilter += ` ${key}: ${pascalize(value.type[0].name)}FilterInput\n`; + manyInput += ` + +input ${pascalize(key)}CreateManyInput { + create: [${pascalize(value.type[0].name)}CreateInput!] +} + +input ${pascalize(key)}UpdateManyInput { + create: [${pascalize(value.type[0].name)}CreateInput!] + delete: [${pascalize(value.type[0].name)}WhereUniqueInput!] + update: [${pascalize(value.type[0].name)}UpdateWhereInput!] +} + +input ${pascalize(value.type[0].name)}UpdateWhereInput { + where: ${pascalize(value.type[0].name)}WhereUniqueInput! + data: ${pascalize(value.type[0].name)}UpdateInput! +}`; + } + } + + shell.cd(destinationPath); + // override Module type in schema.graphql file + const replaceType = `### schema type definitions([^()]+)### end schema type definitions`; + shell + .ShellString( + shell + .cat(file) + .replace( + RegExp(replaceType, 'g'), + `### schema type definitions\n${new GraphQLGenerator().generateTypes( + schema + )}${manyInput}\n\n### end schema type definitions` + ) + ) + .to(file); + + // override ModuleCreateInput in schema.graphql file + const replaceCreate = `input ${Module}CreateInput {([^}])*\\n}`; + shell + .ShellString( + shell.cat(file).replace(RegExp(replaceCreate, 'g'), `input ${Module}CreateInput {\n${inputCreate}}`) + ) + .to(file); + + // override ModuleUpdateInput in schema.graphql file + const replaceUpdate = `input ${Module}UpdateInput {([^}])*\\n}`; + shell + .ShellString( + shell.cat(file).replace(RegExp(replaceUpdate, 'g'), `input ${Module}UpdateInput {\n${inputUpdate}}`) + ) + .to(file); + + // override ModuleFilterInput in schema.graphql file + const replaceFilter = `input ${Module}FilterInput {([^}])*\\n}`; + shell + .ShellString( + shell.cat(file).replace(RegExp(replaceFilter, 'g'), `input ${Module}FilterInput {\n${inputFilter}}`) + ) + .to(file); + + logger.info(chalk.green(`✔ Schema in ${destinationPath}${file} successfully updated!`)); + + const resolverFile = `resolvers.ts`; + let hasBatchResolvers = false; + let replace = ` ${schema.name}: { +`; + for (const key of schema.keys()) { + const value = schema.values[key]; + if (value.type.constructor === Array) { + hasBatchResolvers = true; + const remoteField = value.remoteField ? camelize(value.remoteField) : camelize(schema.name); + replace += ` ${key}: createBatchResolver((sources, args, ctx, info) => { + return ctx.${schema.name}.getByIds(sources.map(({ id }) => id), '${remoteField}', ctx.${value.type[0].name}, info); + }), +`; + } + } + replace += ` }, +`; + replace = hasBatchResolvers ? replace : ''; + + // override batch resolvers in resolvers.ts file + const replaceBatchResolvers = `// schema batch resolvers([^*]+)// end schema batch resolvers`; + shell + .ShellString( + shell + .cat(resolverFile) + .replace( + RegExp(replaceBatchResolvers, 'g'), + `// schema batch resolvers\n${replace} // end schema batch resolvers` + ) + ) + .to(resolverFile); + runPrettier(resolverFile); + + logger.info(chalk.green(`✔ Resolver in ${destinationPath}${resolverFile} successfully updated!`)); + */ + logger.info(chalk.green(`✔ Module ${moduleName} successfully updated!`)); + } else { + logger.info(chalk.red(`✘ Module ${moduleName} in path ${destinationPath} not found!`)); + } + } +} + +module.exports = updateModule; diff --git a/tools/cli/helpers/util.js b/tools/cli/helpers/util.js index 641657a5d6..3921fc8c19 100644 --- a/tools/cli/helpers/util.js +++ b/tools/cli/helpers/util.js @@ -12,7 +12,7 @@ const { MODULE_TEMPLATES, MODULE_TEMPLATES_OLD, BASE_PATH } = require('../config * @returns {string} - package name based on the command option --old ('client-react', 'server-ts' etc.) */ const getModulePackageName = (packageName, old) => { - return `${packageName}${old ? '' : packageName === 'server' ? '-ts' : '-react'}`; + return `${packageName}${old ? '' : packageName === 'common' ? '' : packageName === 'server' ? '-ts' : '-react'}`; }; /** @@ -47,6 +47,12 @@ function renameFiles(destinationPath, moduleName) { shell.cd(destinationPath); // rename files + const timestamp = new Date().getTime(); + shell.ls('-Rl', '.').forEach(entry => { + if (entry.isFile()) { + shell.mv(entry.name, entry.name.replace('T_Module', `${timestamp}_Module`)); + } + }); shell.ls('-Rl', '.').forEach(entry => { if (entry.isFile()) { shell.mv(entry.name, entry.name.replace('Module', Module)); @@ -135,6 +141,19 @@ function computePackagePath(packageName) { return `${BASE_PATH}/packages/${packageName}/package.json`; } +/** + * Gets the computed path for generated module list. + * + * @param packageName - The application package ([client|server]) + * @param fileName - File name of generated module + * @param old - The flag that describes if the command invoked for a new structure or not + * @returns {string} - Return the computed path + */ +function computeGeneratedSchemasPath(packageName, fileName, old) { + const modulePackageName = getModulePackageName(packageName, old); + return `${BASE_PATH}/modules/core/${modulePackageName}/${fileName}`; +} + /** * Adds a symlink. * @@ -228,6 +247,50 @@ function deleteStackDir(stackDirList) { }); } +/** + * + * @param pathToFileWithExports + * @param exportName + * @param importString + */ +function updateFileWithExports({ pathToFileWithExports, exportName, importString }) { + const exportGraphqlContainer = `\nexport default {\n ${exportName}\n};\n`; + + if (fs.existsSync(pathToFileWithExports)) { + const generatedContainerData = fs.readFileSync(pathToFileWithExports); + const generatedContainer = generatedContainerData.toString().trim(); + if (generatedContainer.length > 1) { + const index = generatedContainer.lastIndexOf("';"); + const computedIndex = index >= 0 ? index + 3 : false; + if (computedIndex) { + let computedGeneratedContainer = + generatedContainer.slice(0, computedIndex) + + importString + + generatedContainer.slice(computedIndex, generatedContainer.length); + computedGeneratedContainer = computedGeneratedContainer.replace(/(,|)\s};/g, `,\n ${exportName}\n};`); + return fs.writeFileSync(pathToFileWithExports, computedGeneratedContainer); + } + } + return fs.writeFileSync(pathToFileWithExports, importString + exportGraphqlContainer); + } +} + +/** + * + * @param pathToFileWithExports + * @param exportName + */ +function deleteFromFileWithExports(pathToFileWithExports, exportName) { + if (fs.existsSync(pathToFileWithExports)) { + const generatedElementData = fs.readFileSync(pathToFileWithExports); + const reg = `(\\n\\s\\s${exportName}(.|)|import (${exportName}|{ ${exportName} }).+;\\n+(?!ex))`; + const generatedElement = generatedElementData.toString().replace(new RegExp(reg, 'g'), ''); + fs.writeFileSync(pathToFileWithExports, generatedElement); + + runPrettier(pathToFileWithExports); + } +} + module.exports = { getModulePackageName, getTemplatesPath, @@ -239,11 +302,14 @@ module.exports = { computeRootModulesPath, computePackagePath, computeModulePackageName, + computeGeneratedSchemasPath, addSymlink, removeSymlink, runPrettier, moveToDirectory, deleteDir, getPathsSubdir, - deleteStackDir + deleteStackDir, + updateFileWithExports, + deleteFromFileWithExports }; diff --git a/tools/templates/module/client-react/package.json b/tools/templates/module/client-react/package.json index 0a162b9c24..ce4d62d057 100644 --- a/tools/templates/module/client-react/package.json +++ b/tools/templates/module/client-react/package.json @@ -1,5 +1,8 @@ { "name": "@gqlapp/$-module$-client-react", "version": "1.0.0", - "private": true + "private": true, + "dependencies": { + "@gqlapp/$-module$-common": "1.0.0" + } } diff --git a/tools/templates/module/common/index.ts b/tools/templates/module/common/index.ts new file mode 100644 index 0000000000..e27a6e2f57 --- /dev/null +++ b/tools/templates/module/common/index.ts @@ -0,0 +1 @@ +export * from './schema'; diff --git a/tools/templates/module/common/package.json b/tools/templates/module/common/package.json new file mode 100644 index 0000000000..c6b7326a83 --- /dev/null +++ b/tools/templates/module/common/package.json @@ -0,0 +1,8 @@ +{ + "name": "@gqlapp/$-module$-common", + "version": "1.0.0", + "private": true, + "dependencies": { + "axios": "^0.18.0" + } +} diff --git a/tools/templates/module/common/schema.js b/tools/templates/module/common/schema.js new file mode 100644 index 0000000000..0829fab8d3 --- /dev/null +++ b/tools/templates/module/common/schema.js @@ -0,0 +1,14 @@ +import DomainSchema, { Schema } from '@domain-schema/core'; + +export class $Module$ extends Schema { + constructor() { + super(); + this.__ = { name: '$Module$', tablePrefix: '' }; + this.id = DomainSchema.Int; + this.name = { + type: String + }; + } +} + +export const $Module$Schema = new DomainSchema($Module$); diff --git a/tools/templates/module/server-ts/migrations/T_Module.js b/tools/templates/module/server-ts/migrations/T_Module.js new file mode 100644 index 0000000000..a64be2e85f --- /dev/null +++ b/tools/templates/module/server-ts/migrations/T_Module.js @@ -0,0 +1,6 @@ +import KnexGenerator from '@domain-schema/knex'; +import { $Module$Schema } from '@gqlapp/$-module$-common'; + +exports.up = knex => new KnexGenerator(knex).createTables($Module$Schema); + +exports.down = knex => new KnexGenerator(knex).dropTables($Module$Schema); diff --git a/tools/templates/module/server-ts/package.json b/tools/templates/module/server-ts/package.json index 8dc16993cf..7e583a9274 100644 --- a/tools/templates/module/server-ts/package.json +++ b/tools/templates/module/server-ts/package.json @@ -1,5 +1,8 @@ { "name": "@gqlapp/$-module$-server-ts", "version": "1.0.0", - "private": true + "private": true, + "dependencies": { + "@gqlapp/$-module$-common": "1.0.0" + } } diff --git a/tools/templates/module/server-ts/resolvers.ts b/tools/templates/module/server-ts/resolvers.ts index 3f32ddc897..7565a20eaa 100644 --- a/tools/templates/module/server-ts/resolvers.ts +++ b/tools/templates/module/server-ts/resolvers.ts @@ -1,5 +1,9 @@ export default (pubsub: any) => ({ - Query: {}, + Query: { + $module$s: (parent: any, args: any, ctx: any, info: any) => { + return [{ id: 1, name: 'test' }]; + } + }, Mutation: {}, Subscription: {} }); diff --git a/tools/templates/module/server-ts/schema.graphql b/tools/templates/module/server-ts/schema.graphql index 8dfc4da35d..29102318e5 100644 --- a/tools/templates/module/server-ts/schema.graphql +++ b/tools/templates/module/server-ts/schema.graphql @@ -1,16 +1,11 @@ -# Entity -type TypeName { - typeName: String! +### schema type definitions +type $Module$ { + id: Int! + name: String! } -extend type Query { - queryName: TypeName -} +### end schema type definitions -extend type Mutation { - mutationName(varName: Int!): TypeName -} - -extend type Subscription { - subscriptionName: TypeName +extend type Query { + $module$s: [$Module$] } diff --git a/tools/templates/module/server-ts/seeds/.eslintrc b/tools/templates/module/server-ts/seeds/.eslintrc new file mode 100644 index 0000000000..712a899471 --- /dev/null +++ b/tools/templates/module/server-ts/seeds/.eslintrc @@ -0,0 +1,5 @@ +{ + "rules": { + "import/prefer-default-export": 0 + } +} \ No newline at end of file diff --git a/tools/templates/module/server-ts/seeds/T_Module.js b/tools/templates/module/server-ts/seeds/T_Module.js new file mode 100644 index 0000000000..c30c461693 --- /dev/null +++ b/tools/templates/module/server-ts/seeds/T_Module.js @@ -0,0 +1,7 @@ +import { returnId, truncateTables } from '@gqlapp/database-server-ts'; + +export async function seed(knex, Promise) { + await truncateTables(knex, Promise, ['$_module$']); + + await returnId(knex('$_module$').insert({ name: 'test' })); +} diff --git a/yarn.lock b/yarn.lock index 60794d5337..8e9762f699 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1188,6 +1188,28 @@ exec-sh "^0.3.2" minimist "^1.2.0" +"@domain-schema/core@^0.0.34": + version "0.0.34" + resolved "https://registry.yarnpkg.com/@domain-schema/core/-/core-0.0.34.tgz#5900ae4f0844cb8e7ffa91f5be32bd0417d96429" + integrity sha512-XpYSDStMKxMtRQ81DHwNTjN7psCoPp+1PBMEfG4vsroyUVn7zV8K0wGb9AszJaPvdqhIuVQZiWUS04ukCf1Yxw== + dependencies: + debug "^3.1.0" + +"@domain-schema/graphql@^0.0.33": + version "0.0.33" + resolved "https://registry.yarnpkg.com/@domain-schema/graphql/-/graphql-0.0.33.tgz#201d8cc5d21a9eba69f2eb8cb1fe44f012381cec" + integrity sha512-s81kImrjdwzzXU/nRBdNnw4ujqSAToKApUYUTaDF92vfesWsvmEFKhkYLC9L0tFx2XyAQrMmh+w5zG7lZkBBow== + dependencies: + debug "^3.1.0" + +"@domain-schema/knex@^0.0.35": + version "0.0.35" + resolved "https://registry.yarnpkg.com/@domain-schema/knex/-/knex-0.0.35.tgz#3ea8fb1fdf57e9c9334843ed8bfa3b4e9e1f03c4" + integrity sha512-Qd3Q4MGiSJh7XxodMSTFBSHy0PzoB9DXrvLstsNe0Enn5mi6sYEZ08HYzVgvtdbh6zvpqSARs6aZeWmZgEeydQ== + dependencies: + debug "^3.1.0" + humps "^2.0.1" + "@emotion/cache@^10.0.14": version "10.0.14" resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-10.0.14.tgz#56093cff025c04b0330bdd92afe8335ed326dd18" @@ -5469,6 +5491,14 @@ axios@^0.17.1: follow-redirects "^1.2.5" is-buffer "^1.1.5" +axios@^0.18.0: + version "0.18.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.1.tgz#ff3f0de2e7b5d180e757ad98000f1081b87bcea3" + integrity sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g== + dependencies: + follow-redirects "1.5.10" + is-buffer "^2.0.2" + axios@v0.19.0-beta.1: version "0.19.0-beta.1" resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.0-beta.1.tgz#3d6a9ee75885d1fd39e108df9a4fb2e48e1af1e8" @@ -8516,7 +8546,7 @@ debug@2, debug@2.6.9, debug@2.6.x, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, deb dependencies: ms "2.0.0" -debug@3.1.0: +debug@3.1.0, debug@=3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== @@ -10940,6 +10970,13 @@ follow-redirects@0.0.7: debug "^2.2.0" stream-consume "^0.1.0" +follow-redirects@1.5.10: + version "1.5.10" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" + integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== + dependencies: + debug "=3.1.0" + follow-redirects@^1.0.0, follow-redirects@^1.2.5, follow-redirects@^1.3.0, follow-redirects@^1.4.1: version "1.7.0" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.7.0.tgz#489ebc198dc0e7f64167bd23b03c4c19b5784c76" From 5187f5a66210679df59941f5c6f9c9b1eaf16e52 Mon Sep 17 00:00:00 2001 From: Mitja Debeljak Date: Fri, 19 Jul 2019 14:11:17 +0200 Subject: [PATCH 2/4] correctly add schema to generated schemas --- tools/cli/commands/addModule.js | 64 ++++++++++++++++++++---------- tools/cli/commands/deleteModule.js | 50 ++++++++++++----------- tools/cli/commands/updateSchema.js | 7 ++-- 3 files changed, 72 insertions(+), 49 deletions(-) diff --git a/tools/cli/commands/addModule.js b/tools/cli/commands/addModule.js index 42cc228751..d95bbd6ca0 100644 --- a/tools/cli/commands/addModule.js +++ b/tools/cli/commands/addModule.js @@ -1,6 +1,7 @@ const shell = require('shelljs'); const fs = require('fs'); const chalk = require('chalk'); +const { pascalize, decamelize } = require('humps'); const { getModulePackageName, getTemplatesPath, @@ -10,6 +11,8 @@ const { getModulesEntryPoint, computePackagePath, computeModulePackageName, + computeGeneratedSchemasPath, + updateFileWithExports, addSymlink, runPrettier } = require('../helpers/util'); @@ -29,9 +32,8 @@ function addModule({ logger, packageName, moduleName, old }) { copyTemplates(params); if (packageName !== 'common') { mergeWithModules(params); - if (!old) addDependency(params); } - addSymlink(moduleName, modulePackageName); + if (!old) addDependency(params); logger.info(chalk.green(`✔ New module ${moduleName} for package ${packageName} successfully created!`)); } @@ -93,28 +95,46 @@ function mergeWithModules({ logger, moduleName, modulePackageName, packageName, * Adds new module as a dependency. */ function addDependency({ moduleName, modulePackageName, packageName, old }) { - // Get package content - const packagePath = computePackagePath(packageName); - const packageContent = `${fs.readFileSync(packagePath)}`; - - // Extract dependencies - const dependenciesRegExp = /"dependencies":\s\{([^()]+)\},\n\s+"devDependencies"/g; - const [, dependencies] = dependenciesRegExp.exec(packageContent) || ['', '']; + if (packageName !== 'common') { + // Get package content + const packagePath = computePackagePath(packageName); + const packageContent = `${fs.readFileSync(packagePath)}`; + + // Extract dependencies + const dependenciesRegExp = /"dependencies":\s\{([^()]+)\},\n\s+"devDependencies"/g; + const [, dependencies] = dependenciesRegExp.exec(packageContent) || ['', '']; + + // Insert package and sort + const dependenciesSorted = dependencies.split(','); + dependenciesSorted.push(`\n "${computeModulePackageName(moduleName, modulePackageName, old)}": "^1.0.0"`); + dependenciesSorted.sort(); + + // Add module to package list + shell + .ShellString( + packageContent.replace( + RegExp(dependenciesRegExp, 'g'), + `"dependencies": {${dependenciesSorted}},\n "devDependencies"` + ) + ) + .to(packagePath); + } - // Insert package and sort - const dependenciesSorted = dependencies.split(','); - dependenciesSorted.push(`\n "${computeModulePackageName(moduleName, modulePackageName, old)}": "^1.0.0"`); - dependenciesSorted.sort(); + // Adds new schema to generated schemas list. + const Module = pascalize(moduleName); + const schema = `${Module}Schema`; + const fileName = 'generatedSchemas.js'; + const options = { + pathToFileWithExports: computeGeneratedSchemasPath(packageName, fileName, old), + exportName: schema, + importString: `import { ${schema} } from '@gqlapp/${decamelize(moduleName, { + separator: '-' + })}-${modulePackageName}/schema';\n` + }; + updateFileWithExports(options); + runPrettier(options.pathToFileWithExports); - // Add module to package list - shell - .ShellString( - packageContent.replace( - RegExp(dependenciesRegExp, 'g'), - `"dependencies": {${dependenciesSorted}},\n "devDependencies"` - ) - ) - .to(packagePath); + addSymlink(moduleName, modulePackageName); } module.exports = addModule; diff --git a/tools/cli/commands/deleteModule.js b/tools/cli/commands/deleteModule.js index 9ae31e6ae8..6f9c52c94d 100644 --- a/tools/cli/commands/deleteModule.js +++ b/tools/cli/commands/deleteModule.js @@ -33,9 +33,8 @@ function deleteModule({ logger, packageName, moduleName, old }) { if (packageName !== 'common') { removeFromModules(params); - if (!old) removeDependency(params); } - removeSymlink(moduleName, modulePackageName); + if (!old) removeDependency(params); logger.info(chalk.green(`✔ Module ${moduleName} for package ${packageName} successfully deleted!`)); } else { @@ -102,29 +101,30 @@ function removeFromModules({ logger, moduleName, packageName, modulePackageName, * Removes the module from the dependencies list. */ function removeDependency({ moduleName, packageName, modulePackageName, old }) { - // Get package content - const packagePath = computePackagePath(packageName); - const packageContent = `` + fs.readFileSync(packagePath); - - // Extract dependencies - const dependenciesRegExp = /"dependencies":\s\{([^()]+)\},\n\s+"devDependencies"/g; - const [, dependencies] = dependenciesRegExp.exec(packageContent) || ['', '']; - - // Remove package - const dependenciesWithoutDeleted = dependencies - .split(',') - .filter(pkg => !pkg.includes(computeModulePackageName(moduleName, modulePackageName, old))); - - // Remove module from package list - shell - .ShellString( - packageContent.replace( - RegExp(dependenciesRegExp, 'g'), - `"dependencies": {${dependenciesWithoutDeleted}},\n "devDependencies"` + if (packageName !== 'common') { + // Get package content + const packagePath = computePackagePath(packageName); + const packageContent = `` + fs.readFileSync(packagePath); + + // Extract dependencies + const dependenciesRegExp = /"dependencies":\s\{([^()]+)\},\n\s+"devDependencies"/g; + const [, dependencies] = dependenciesRegExp.exec(packageContent) || ['', '']; + + // Remove package + const dependenciesWithoutDeleted = dependencies + .split(',') + .filter(pkg => !pkg.includes(computeModulePackageName(moduleName, modulePackageName, old))); + + // Remove module from package list + shell + .ShellString( + packageContent.replace( + RegExp(dependenciesRegExp, 'g'), + `"dependencies": {${dependenciesWithoutDeleted}},\n "devDependencies"` + ) ) - ) - .to(packagePath); - + .to(packagePath); + } const Module = pascalize(moduleName); const fileName = 'generatedSchemas.js'; const generatedSchemaPath = computeGeneratedSchemasPath(packageName, fileName, old); @@ -132,6 +132,8 @@ function removeDependency({ moduleName, packageName, modulePackageName, old }) { const schema = `${Module}Schema`; deleteFromFileWithExports(generatedSchemaPath, schema); } + + removeSymlink(moduleName, modulePackageName); } module.exports = deleteModule; diff --git a/tools/cli/commands/updateSchema.js b/tools/cli/commands/updateSchema.js index 9604449d42..27bbbdd734 100644 --- a/tools/cli/commands/updateSchema.js +++ b/tools/cli/commands/updateSchema.js @@ -5,7 +5,7 @@ const chalk = require('chalk'); const { pascalize } = require('humps'); const { getModulePackageName, computeModulePath } = require('../helpers/util'); -// const schemas = require('../../../modules/core/common/generatedSchemas'); +const schemas = require('../../../modules/core/common/generatedSchemas'); /** * Update module schema. @@ -27,8 +27,9 @@ function updateModule({ logger, packageName, moduleName, old }) { if (fs.existsSync(destinationPath)) { console.log('Module:', Module); // get module schema - /*const schema = schemas.default[`${Module}Schema`]; - + const schema = schemas.default[`${Module}Schema`]; + console.log('schema:', schema.keys()); + /* // schema file const file = `schema.graphql`; From 3af55c45450a4a58cc3193c5ca3653a9a2a6ca3d Mon Sep 17 00:00:00 2001 From: Mitja Debeljak Date: Fri, 19 Jul 2019 14:29:40 +0200 Subject: [PATCH 3/4] update graphql schema based on domain-schema --- tools/cli/commands/updateSchema.js | 127 +----------------- .../templates/module/server-ts/schema.graphql | 3 +- 2 files changed, 6 insertions(+), 124 deletions(-) diff --git a/tools/cli/commands/updateSchema.js b/tools/cli/commands/updateSchema.js index 27bbbdd734..77278a27ba 100644 --- a/tools/cli/commands/updateSchema.js +++ b/tools/cli/commands/updateSchema.js @@ -1,7 +1,7 @@ -// const shell = require('shelljs'); +const shell = require('shelljs'); const fs = require('fs'); const chalk = require('chalk'); -// const GraphQLGenerator = require('@domain-schema/graphql').default; +const GraphQLGenerator = require('@domain-schema/graphql').default; const { pascalize } = require('humps'); const { getModulePackageName, computeModulePath } = require('../helpers/util'); @@ -28,66 +28,10 @@ function updateModule({ logger, packageName, moduleName, old }) { console.log('Module:', Module); // get module schema const schema = schemas.default[`${Module}Schema`]; - console.log('schema:', schema.keys()); - /* + // schema file const file = `schema.graphql`; - // regenerate input fields - let inputCreate = ''; - let inputUpdate = ''; - let inputFilter = ` searchText: String\n`; - let manyInput = ''; - for (const key of schema.keys()) { - const value = schema.values[key]; - const hasTypeOf = targetType => value.type === targetType || value.type.prototype instanceof targetType; - if (value.type.isSchema) { - let required = value.optional ? '' : '!'; - const id = value.noIdSuffix ? '' : 'Id'; - inputCreate += ` ${key}${id}: Int${required}\n`; - inputUpdate += ` ${key}${id}: Int\n`; - inputFilter += ` ${key}${id}: Int\n`; - inputFilter += ` ${key}${id}_in: [Int!]\n`; - inputFilter += ` ${key}${id}_contains: Int\n`; - } else if (value.type.constructor !== Array) { - if (key !== 'id') { - inputCreate += ` ${key}: ${generateField(value)}\n`; - inputUpdate += ` ${key}: ${generateField(value, true)}\n`; - } - - if (hasTypeOf(Date)) { - inputFilter += ` ${key}_lte: ${generateField(value, true)}\n`; - inputFilter += ` ${key}_gte: ${generateField(value, true)}\n`; - } else if (key === 'id' || hasTypeOf(String)) { - inputFilter += ` ${key}: ${generateField(value, true)}\n`; - inputFilter += ` ${key}_in: [${generateField(value, true)}!]\n`; - inputFilter += ` ${key}_contains: ${generateField(value, true)}\n`; - } else { - inputFilter += ` ${key}: ${generateField(value, true)}\n`; - } - } else if (value.type.constructor === Array && value.type[0].isSchema) { - inputCreate += ` ${key}: ${pascalize(key)}CreateManyInput\n`; - inputUpdate += ` ${key}: ${pascalize(key)}UpdateManyInput\n`; - inputFilter += ` ${key}: ${pascalize(value.type[0].name)}FilterInput\n`; - manyInput += ` - -input ${pascalize(key)}CreateManyInput { - create: [${pascalize(value.type[0].name)}CreateInput!] -} - -input ${pascalize(key)}UpdateManyInput { - create: [${pascalize(value.type[0].name)}CreateInput!] - delete: [${pascalize(value.type[0].name)}WhereUniqueInput!] - update: [${pascalize(value.type[0].name)}UpdateWhereInput!] -} - -input ${pascalize(value.type[0].name)}UpdateWhereInput { - where: ${pascalize(value.type[0].name)}WhereUniqueInput! - data: ${pascalize(value.type[0].name)}UpdateInput! -}`; - } - } - shell.cd(destinationPath); // override Module type in schema.graphql file const replaceType = `### schema type definitions([^()]+)### end schema type definitions`; @@ -97,75 +41,14 @@ input ${pascalize(value.type[0].name)}UpdateWhereInput { .cat(file) .replace( RegExp(replaceType, 'g'), - `### schema type definitions\n${new GraphQLGenerator().generateTypes( + `### schema type definitions autogenerated\n${new GraphQLGenerator().generateTypes( schema - )}${manyInput}\n\n### end schema type definitions` + )}\n### end schema type definitions` ) ) .to(file); - // override ModuleCreateInput in schema.graphql file - const replaceCreate = `input ${Module}CreateInput {([^}])*\\n}`; - shell - .ShellString( - shell.cat(file).replace(RegExp(replaceCreate, 'g'), `input ${Module}CreateInput {\n${inputCreate}}`) - ) - .to(file); - - // override ModuleUpdateInput in schema.graphql file - const replaceUpdate = `input ${Module}UpdateInput {([^}])*\\n}`; - shell - .ShellString( - shell.cat(file).replace(RegExp(replaceUpdate, 'g'), `input ${Module}UpdateInput {\n${inputUpdate}}`) - ) - .to(file); - - // override ModuleFilterInput in schema.graphql file - const replaceFilter = `input ${Module}FilterInput {([^}])*\\n}`; - shell - .ShellString( - shell.cat(file).replace(RegExp(replaceFilter, 'g'), `input ${Module}FilterInput {\n${inputFilter}}`) - ) - .to(file); - logger.info(chalk.green(`✔ Schema in ${destinationPath}${file} successfully updated!`)); - - const resolverFile = `resolvers.ts`; - let hasBatchResolvers = false; - let replace = ` ${schema.name}: { -`; - for (const key of schema.keys()) { - const value = schema.values[key]; - if (value.type.constructor === Array) { - hasBatchResolvers = true; - const remoteField = value.remoteField ? camelize(value.remoteField) : camelize(schema.name); - replace += ` ${key}: createBatchResolver((sources, args, ctx, info) => { - return ctx.${schema.name}.getByIds(sources.map(({ id }) => id), '${remoteField}', ctx.${value.type[0].name}, info); - }), -`; - } - } - replace += ` }, -`; - replace = hasBatchResolvers ? replace : ''; - - // override batch resolvers in resolvers.ts file - const replaceBatchResolvers = `// schema batch resolvers([^*]+)// end schema batch resolvers`; - shell - .ShellString( - shell - .cat(resolverFile) - .replace( - RegExp(replaceBatchResolvers, 'g'), - `// schema batch resolvers\n${replace} // end schema batch resolvers` - ) - ) - .to(resolverFile); - runPrettier(resolverFile); - - logger.info(chalk.green(`✔ Resolver in ${destinationPath}${resolverFile} successfully updated!`)); - */ - logger.info(chalk.green(`✔ Module ${moduleName} successfully updated!`)); } else { logger.info(chalk.red(`✘ Module ${moduleName} in path ${destinationPath} not found!`)); } diff --git a/tools/templates/module/server-ts/schema.graphql b/tools/templates/module/server-ts/schema.graphql index 29102318e5..0f9a9f7e4c 100644 --- a/tools/templates/module/server-ts/schema.graphql +++ b/tools/templates/module/server-ts/schema.graphql @@ -1,9 +1,8 @@ -### schema type definitions +### schema type definitions autogenerated type $Module$ { id: Int! name: String! } - ### end schema type definitions extend type Query { From 4b728ba1eca74899c0464b96bd96ed574b67d1fa Mon Sep 17 00:00:00 2001 From: Mitja Debeljak Date: Fri, 19 Jul 2019 15:20:01 +0200 Subject: [PATCH 4/4] generate select query based on graphql info --- modules/core/common/log.ts | 2 +- modules/core/common/net.ts | 10 +- modules/database/server-ts/sql/crud.js | 37 ++++- modules/database/server-ts/sql/helpers.js | 130 +++++++++++++++++- modules/database/server-ts/sql/index.ts | 1 + packages/server/package.json | 2 + tools/cli/commands/updateSchema.js | 2 - tools/templates/module/server-ts/resolvers.ts | 2 +- tools/templates/module/server-ts/sql.ts | 11 +- yarn.lock | 62 ++++++--- 10 files changed, 227 insertions(+), 32 deletions(-) diff --git a/modules/core/common/log.ts b/modules/core/common/log.ts index 86776ca598..cb7827594c 100644 --- a/modules/core/common/log.ts +++ b/modules/core/common/log.ts @@ -10,7 +10,7 @@ const log = minilog(loggerName); (log as any).suggest.defaultResult = false; (log as any).suggest.clear().allow(loggerName, settings.app.logging.level); -if (__DEV__ && __SERVER__ && !__TEST__) { +if (typeof __DEV__ !== 'undefined' && typeof __SERVER__ !== 'undefined' && typeof __TEST__ !== 'undefined') { const consoleLog = global.console.log; global.console.log = (...args: any[]) => { if (args.length === 1 && typeof args[0] === 'string' && args[0].match(/^\[(HMR|WDS)\]/)) { diff --git a/modules/core/common/net.ts b/modules/core/common/net.ts index 1c2155202f..755c8e80ba 100644 --- a/modules/core/common/net.ts +++ b/modules/core/common/net.ts @@ -1,17 +1,19 @@ import url from 'url'; import { PLATFORM } from './utils'; +const apiUrlDefine = typeof __API_URL__ !== 'undefined' ? __API_URL__ : '/graphql'; + export const serverPort = PLATFORM === 'server' && (process.env.PORT || (typeof __SERVER_PORT__ !== 'undefined' ? __SERVER_PORT__ : 8080)); -export const isApiExternal = !!url.parse(__API_URL__).protocol; +export const isApiExternal = !!url.parse(apiUrlDefine).protocol; const clientApiUrl = !isApiExternal && PLATFORM === 'web' ? `${window.location.protocol}//${window.location.hostname}${ __DEV__ ? ':8080' : window.location.port ? ':' + window.location.port : '' - }${__API_URL__}` - : __API_URL__; + }${apiUrlDefine}` + : apiUrlDefine; -const serverApiUrl = !isApiExternal ? `http://localhost:${serverPort}${__API_URL__}` : __API_URL__; +const serverApiUrl = !isApiExternal ? `http://localhost:${serverPort}${apiUrlDefine}` : apiUrlDefine; export const apiUrl = PLATFORM === 'server' ? serverApiUrl : clientApiUrl; diff --git a/modules/database/server-ts/sql/crud.js b/modules/database/server-ts/sql/crud.js index ff3f59aa7e..1f003d0413 100644 --- a/modules/database/server-ts/sql/crud.js +++ b/modules/database/server-ts/sql/crud.js @@ -1,12 +1,12 @@ import _ from 'lodash'; import uuidv4 from 'uuid'; -import { camelize, decamelizeKeys, camelizeKeys } from 'humps'; +import { decamelize, decamelizeKeys, camelize, camelizeKeys } from 'humps'; import { log } from '@gqlapp/core-common'; +import knexnest from 'knexnest'; +import parseFields from 'graphql-parse-fields'; import knex from './connector'; - -import { orderedFor } from './helpers'; - +import { selectBy, orderedFor } from './helpers'; import selectAdapter from './select'; export function createWithIdGenAdapter(options) { @@ -402,3 +402,32 @@ export function deleteRelationAdapter(options) { } }; } + +export class Crud { + getTableName() { + return decamelize(this.schema.__.tableName ? this.schema.__.tableName : this.schema.name); + } + + getFullTableName() { + return `${this.schema.__.tablePrefix}${this.getTableName()}`; + } + + getSchema() { + return this.schema; + } + + getBaseQuery() { + return knex(`${this.getFullTableName()} as ${this.getTableName()}`); + } + + _findMany(_, info) { + const select = selectBy(this.schema, info, false); + const queryBuilder = select(this.getBaseQuery()); + + return knexnest(queryBuilder); + } + + findMany(args, info) { + return this._findMany(args, parseFields(info)); + } +} diff --git a/modules/database/server-ts/sql/helpers.js b/modules/database/server-ts/sql/helpers.js index 3bcdca0b9d..cca4231f87 100644 --- a/modules/database/server-ts/sql/helpers.js +++ b/modules/database/server-ts/sql/helpers.js @@ -1,4 +1,5 @@ -import { groupBy } from 'lodash'; +import { groupBy, findIndex } from 'lodash'; +import { decamelize } from 'humps'; import settings from '@gqlapp/config'; @@ -30,3 +31,130 @@ export const orderedFor = (rows, collection, field, singleObject) => { return singleObject ? {} : []; }); }; + +export const orderedForArray = (rows, collection, field, arrayElement) => { + // return the rows ordered for the collection + const inGroupsOfField = groupBy(rows, field); + return collection.map(element => { + const elementArray = inGroupsOfField[element]; + if (elementArray) { + return inGroupsOfField[element].map(elm => { + return elm[arrayElement]; + }); + } + return []; + }); +}; + +/** + * Collecting selects and joins + * @param graphqlFields + * @param domainSchema + * @param selectItems + * @param joinNames + * @param single + * @param parentField + * @private + */ +const _getSelectFields = (graphqlFields, domainSchema, selectItems, joinNames, single, parentKey, parentPath) => { + for (const fieldName of Object.keys(graphqlFields)) { + if (fieldName === '__typename') { + continue; + } + const value = domainSchema.values[fieldName]; + if (graphqlFields[fieldName] === true) { + if (value && value.transient) { + continue; + } + selectItems.push(_getSelectField(fieldName, parentPath, domainSchema, single, parentKey)); + } else { + if (Array.isArray(value.type) || findIndex(joinNames, { fieldName: decamelize(fieldName) }) > -1) { + continue; + } + if (!value.type.__.transient) { + joinNames.push(_getJoinEntity(fieldName, value, domainSchema)); + } + + parentPath.push(fieldName); + + _getSelectFields( + graphqlFields[fieldName], + value.type, + selectItems, + joinNames, + single, + decamelize(fieldName), + parentPath + ); + + parentPath.pop(); + } + } +}; + +/** + * Computing select field + * @param fieldName + * @param parentField + * @param domainSchema + * @param single + * @returns {string} + * @private + */ +const _getSelectField = (fieldName, parentPath, domainSchema, single, parentKey) => { + const alias = parentPath.length > 0 ? `${parentPath.join('_')}_${fieldName}` : fieldName; + const tableName = `${decamelize(domainSchema.__.tableName ? domainSchema.__.tableName : domainSchema.name)}`; + const fullTableName = parentKey !== null && parentKey !== tableName ? `${parentKey}_${tableName}` : tableName; + // returning object would be array or no + const arrayPrefix = single ? '' : '_'; + return `${fullTableName}.${decamelize(fieldName)} as ${arrayPrefix}${alias}`; +}; + +/** + * Computing join entity object + * @param fieldName + * @param value + * @param domainSchema + * @returns {{fieldName: *, prefix: string, suffix: string, baseTableName: *, foreignTableName: *}} + * @private + */ +const _getJoinEntity = (fieldName, value, domainSchema) => { + return { + fieldName: decamelize(fieldName), + prefix: value.type.__.tablePrefix ? value.type.__.tablePrefix : '', + suffix: value.noIdSuffix ? '' : '_id', + baseTableName: decamelize(domainSchema.name), + foreignTableName: decamelize(value.type.__.tableName ? value.type.__.tableName : value.type.name) + }; +}; + +/** + * Computing query with selects and joins + * @param schema + * @param fields + * @param single + * @returns {function(*): *} + */ +export const selectBy = (schema, fields, single = false) => { + // select fields and joins + const parentPath = []; + const selectItems = []; + const joinNames = []; + _getSelectFields(fields, schema, selectItems, joinNames, single, null, parentPath); + + return query => { + // join table names + joinNames.map(({ fieldName, prefix, suffix, baseTableName, foreignTableName }) => { + // if fieldName (schema key) diff with table name than make proper table alias + const tableNameAlias = + fieldName !== null && fieldName !== foreignTableName ? `${fieldName}_${foreignTableName}` : foreignTableName; + query.leftJoin( + `${prefix}${foreignTableName} as ${tableNameAlias}`, + `${tableNameAlias}.id`, + `${baseTableName}.${fieldName}${suffix}` + ); + }); + + return query.select(selectItems); + }; +}; diff --git a/modules/database/server-ts/sql/index.ts b/modules/database/server-ts/sql/index.ts index 797cc2df05..b3ff47c0ad 100644 --- a/modules/database/server-ts/sql/index.ts +++ b/modules/database/server-ts/sql/index.ts @@ -2,3 +2,4 @@ export { default as knex } from './connector'; export { default as populateTestDb } from './populateTestDb'; export { default as createTransaction } from './createTransaction'; export * from './helpers'; +export * from './crud'; diff --git a/packages/server/package.json b/packages/server/package.json index 57d44e4399..54b1bf425b 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -91,6 +91,7 @@ "graphql": "^14.1.1", "graphql-auth": "0.2.6", "graphql-iso-date": "^3.5.0", + "graphql-parse-fields": "^1.2.0", "graphql-resolve-batch": "^1.0.2", "graphql-subscriptions": "^1.1.0", "graphql-tag": "^2.6.0", @@ -101,6 +102,7 @@ "isomorphic-fetch": "^2.2.1", "jsonwebtoken": "^8.1.0", "knex": "^0.14.2", + "knexnest": "^1.0.0", "lerna": "^2.5.1", "lint-staged": "^7.0.4", "lodash": "^4.17.4", diff --git a/tools/cli/commands/updateSchema.js b/tools/cli/commands/updateSchema.js index 77278a27ba..c4a2a39f62 100644 --- a/tools/cli/commands/updateSchema.js +++ b/tools/cli/commands/updateSchema.js @@ -20,12 +20,10 @@ function updateModule({ logger, packageName, moduleName, old }) { // pascalize const Module = pascalize(moduleName); - //const modulePath = `${BASE_PATH}/packages/server/src/modules/${moduleName}`; const modulePackageName = getModulePackageName(packageName, old); const destinationPath = computeModulePath(modulePackageName, old, moduleName); if (fs.existsSync(destinationPath)) { - console.log('Module:', Module); // get module schema const schema = schemas.default[`${Module}Schema`]; diff --git a/tools/templates/module/server-ts/resolvers.ts b/tools/templates/module/server-ts/resolvers.ts index 7565a20eaa..9bf0a6b4b1 100644 --- a/tools/templates/module/server-ts/resolvers.ts +++ b/tools/templates/module/server-ts/resolvers.ts @@ -1,7 +1,7 @@ export default (pubsub: any) => ({ Query: { $module$s: (parent: any, args: any, ctx: any, info: any) => { - return [{ id: 1, name: 'test' }]; + return ctx.$Module$.findMany(args, info); } }, Mutation: {}, diff --git a/tools/templates/module/server-ts/sql.ts b/tools/templates/module/server-ts/sql.ts index 417d2d1f82..ed68c391bd 100644 --- a/tools/templates/module/server-ts/sql.ts +++ b/tools/templates/module/server-ts/sql.ts @@ -1,6 +1,13 @@ -import { knex } from '@gqlapp/database-server-ts'; +import { knex, Crud } from '@gqlapp/database-server-ts'; +import { $Module$Schema } from '@gqlapp/$-module$-common'; + +export default class $Module$ extends Crud { + public schema: any; + constructor() { + super(); + this.schema = $Module$Schema; + } -export default class $Module$ { public $module$s() { return knex.select(); } diff --git a/yarn.lock b/yarn.lock index 8e9762f699..2139a6e03b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5491,14 +5491,6 @@ axios@^0.17.1: follow-redirects "^1.2.5" is-buffer "^1.1.5" -axios@^0.18.0: - version "0.18.1" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.1.tgz#ff3f0de2e7b5d180e757ad98000f1081b87bcea3" - integrity sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g== - dependencies: - follow-redirects "1.5.10" - is-buffer "^2.0.2" - axios@v0.19.0-beta.1: version "0.19.0-beta.1" resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.0-beta.1.tgz#3d6a9ee75885d1fd39e108df9a4fb2e48e1af1e8" @@ -6929,6 +6921,13 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= +cast-array@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cast-array/-/cast-array-1.0.1.tgz#264ef1129e5888bc48cac40fe914e9f69b8d189d" + integrity sha1-Jk7xEp5YiLxIysQP6RTp9puNGJ0= + dependencies: + isarray "0.0.1" + ccount@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.4.tgz#9cf2de494ca84060a2a8d2854edd6dfb0445f386" @@ -8546,7 +8545,7 @@ debug@2, debug@2.6.9, debug@2.6.x, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, deb dependencies: ms "2.0.0" -debug@3.1.0, debug@=3.1.0: +debug@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== @@ -10970,13 +10969,6 @@ follow-redirects@0.0.7: debug "^2.2.0" stream-consume "^0.1.0" -follow-redirects@1.5.10: - version "1.5.10" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" - integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== - dependencies: - debug "=3.1.0" - follow-redirects@^1.0.0, follow-redirects@^1.2.5, follow-redirects@^1.3.0, follow-redirects@^1.4.1: version "1.7.0" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.7.0.tgz#489ebc198dc0e7f64167bd23b03c4c19b5784c76" @@ -11809,6 +11801,13 @@ graphql-iso-date@^3.5.0: resolved "https://registry.yarnpkg.com/graphql-iso-date/-/graphql-iso-date-3.6.1.tgz#bd2d0dc886e0f954cbbbc496bbf1d480b57ffa96" integrity sha512-AwFGIuYMJQXOEAgRlJlFL4H1ncFM8n8XmoVDTNypNOZyQ8LFDG2ppMFlsS862BSTCDcSUfHp8PD3/uJhv7t59Q== +graphql-parse-fields@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/graphql-parse-fields/-/graphql-parse-fields-1.2.0.tgz#c97119a91951b98dcbe656c46a203e966a720f28" + integrity sha1-yXEZqRlRuY3L5lbEaiA+lmpyDyg= + dependencies: + cast-array "^1.0.1" + graphql-resolve-batch@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/graphql-resolve-batch/-/graphql-resolve-batch-1.0.2.tgz#ab069df8ab3410a0188701ed9fa8e382bbb8d6a5" @@ -14437,7 +14436,7 @@ kleur@^3.0.2, kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -knex@^0.14.2: +knex@^0.14.2, knex@^0.14.6: version "0.14.6" resolved "https://registry.yarnpkg.com/knex/-/knex-0.14.6.tgz#ad57c4ef8fa1b51ebc8c37c2c9b483f6fb34e41e" integrity sha512-A+iP8oSSmEF3JbSMfUGuJveqduDMEgyS5E/dO0ycVzAT4EE5askfunk7+37+hPqC951vnbFK/fIiNDaJIjVW0w== @@ -14461,6 +14460,14 @@ knex@^0.14.2: uuid "^3.2.1" v8flags "^3.0.2" +knexnest@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/knexnest/-/knexnest-1.0.0.tgz#b8840d13c2692bdd5fa3d5de67a456283d538fce" + integrity sha512-pClYHQu04G7yFBCRdwWxG/OPUAw/GR/A1Soqqb/JtcM2TADunt490HFA9+5nx1nC+MnnAIaCVgeAt0yiqBt0Kg== + dependencies: + knex "^0.14.6" + nesthydrationjs "^1.0.5" + last-call-webpack-plugin@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz#9742df0e10e3cf46e5c0381c2de90d3a7a2d7555" @@ -14943,6 +14950,11 @@ lodash.isarray@^3.0.0: resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" integrity sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U= +lodash.isarray@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-4.0.0.tgz#2aca496b28c4ca6d726715313590c02e6ea34403" + integrity sha1-KspJayjEym1yZxUxNZDALm6jRAM= + lodash.isboolean@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" @@ -15012,6 +15024,11 @@ lodash.keys@^3.0.0, lodash.keys@^3.1.2: lodash.isarguments "^3.0.0" lodash.isarray "^3.0.0" +lodash.keys@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-4.2.0.tgz#a08602ac12e4fb83f91fc1fb7a360a4d9ba35205" + integrity sha1-oIYCrBLk+4P5H8H7ejYKTZujUgU= + lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -16378,6 +16395,17 @@ neo-async@^2.5.0, neo-async@^2.6.0: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== +nesthydrationjs@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nesthydrationjs/-/nesthydrationjs-1.0.5.tgz#89fe5be87055184461de5a550475ac515491f921" + integrity sha512-LnqhV0LfczSX5LTp3UP1QIIyP8iEd0tn5ckslsPsnnbFxHWU25VXmR4cj/Ast/4MuEEYC8hxjrTQSHaZYwmBDQ== + dependencies: + lodash.isarray "^4.0.0" + lodash.isfunction "^3.0.9" + lodash.isplainobject "^4.0.6" + lodash.keys "^4.2.0" + lodash.values "^4.3.0" + next-tick@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"