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/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 b94a80787b..54b1bf425b 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", @@ -88,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", @@ -98,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.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..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'); @@ -27,7 +30,9 @@ function addModule({ logger, packageName, moduleName, old }) { const params = { logger, packageName, moduleName, modulePackageName, templatesPath, old }; copyTemplates(params); - mergeWithModules(params); + if (packageName !== 'common') { + mergeWithModules(params); + } if (!old) addDependency(params); logger.info(chalk.green(`✔ New module ${moduleName} for package ${packageName} successfully created!`)); @@ -90,28 +95,44 @@ 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) || ['', '']; - - // 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"` + 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); + .to(packagePath); + } + + // 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); addSymlink(moduleName, modulePackageName); } diff --git a/tools/cli/commands/deleteModule.js b/tools/cli/commands/deleteModule.js index bc46882758..6f9c52c94d 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,7 +30,10 @@ function deleteModule({ logger, packageName, moduleName, old }) { if (fs.existsSync(modulePath)) { deleteTemplates(params); - removeFromModules(params); + + if (packageName !== 'common') { + removeFromModules(params); + } if (!old) removeDependency(params); logger.info(chalk.green(`✔ Module ${moduleName} for package ${packageName} successfully deleted!`)); @@ -95,28 +101,37 @@ 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); + if (fs.existsSync(generatedSchemaPath)) { + const schema = `${Module}Schema`; + deleteFromFileWithExports(generatedSchemaPath, schema); + } removeSymlink(moduleName, modulePackageName); } diff --git a/tools/cli/commands/updateSchema.js b/tools/cli/commands/updateSchema.js new file mode 100644 index 0000000000..c4a2a39f62 --- /dev/null +++ b/tools/cli/commands/updateSchema.js @@ -0,0 +1,56 @@ +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 modulePackageName = getModulePackageName(packageName, old); + const destinationPath = computeModulePath(modulePackageName, old, moduleName); + + if (fs.existsSync(destinationPath)) { + // get module schema + const schema = schemas.default[`${Module}Schema`]; + + // schema file + const file = `schema.graphql`; + + 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 autogenerated\n${new GraphQLGenerator().generateTypes( + schema + )}\n### end schema type definitions` + ) + ) + .to(file); + + logger.info(chalk.green(`✔ Schema in ${destinationPath}${file} 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..9bf0a6b4b1 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 ctx.$Module$.findMany(args, info); + } + }, Mutation: {}, Subscription: {} }); diff --git a/tools/templates/module/server-ts/schema.graphql b/tools/templates/module/server-ts/schema.graphql index 8dfc4da35d..0f9a9f7e4c 100644 --- a/tools/templates/module/server-ts/schema.graphql +++ b/tools/templates/module/server-ts/schema.graphql @@ -1,16 +1,10 @@ -# Entity -type TypeName { - typeName: String! +### schema type definitions autogenerated +type $Module$ { + id: Int! + name: String! } +### end schema type definitions extend type Query { - queryName: TypeName -} - -extend type Mutation { - mutationName(varName: Int!): TypeName -} - -extend type Subscription { - subscriptionName: TypeName + $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/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 60794d5337..2139a6e03b 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" @@ -6899,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" @@ -11772,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" @@ -14400,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== @@ -14424,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" @@ -14906,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" @@ -14975,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" @@ -16341,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"