sql-to-graphql
Version:
Generate a GraphQL API based on your SQL database structure
180 lines (144 loc) • 4.89 kB
JavaScript
;
var path = require('path');
var async = require('async');
var opts = require('./cli/args');
var prompts = require('./cli/prompts');
var merge = require('lodash/object/merge');
var partial = require('lodash/function/partial');
var backends = require('./backends');
var mapValues = require('lodash/object/mapValues');
var steps = {
getTables: require('./steps/table-list'),
tableToObject: require('./steps/table-to-object'),
findReferences: require('./steps/find-references'),
findOneToManyReferences: require('./steps/find-one-to-many-rels'),
generateTypes: require('./steps/generate-types'),
outputData: require('./steps/output-data'),
collect: {
tableStructure: require('./steps/table-structure'),
tableComments: require('./steps/table-comment')
}
};
// Force recast to throw away whitespace information
opts.reuseWhitespace = false;
if (opts.backend === 'sqlite' && !opts.database) {
opts.database = 'main';
}
if (opts.backend === 'sqlite' && !opts.dbFilename) {
return bail(new Error('You need to specify a database filename (--db-filename) when using the \'sqlite\' backend'));
}
if (!opts.interactive && !opts.database) {
return bail(new Error('You need to specify a database (--database)'));
}
if (!opts.interactive && !opts.outputDir) {
return bail(new Error('You need to provide an output directory (--output-dir=<path>) to generate an application'));
}
if (opts.interactive) {
prompts.dbCredentials(opts, function(options) {
opts = merge({}, opts, options);
initOutputPath();
});
} else {
initOutputPath();
}
function initOutputPath() {
if (opts.outputDir) {
return initStyleOpts();
}
prompts.outputPath(function(outPath) {
opts.outputDir = outPath;
initStyleOpts();
});
}
function initStyleOpts() {
if (!opts.interactive) {
return instantiate();
}
prompts.styleOptions(opts, function(options) {
opts = options;
instantiate();
});
}
var adapter;
function instantiate() {
// Do we support the given backend?
var backend = backends(opts.backend);
if (!backend) {
return bail(new Error('Database backend "' + opts.backend + '" not supported'));
}
// Instantiate the adapter for the given backend
adapter = backend(opts, function(err) {
bailOnError(err);
setTimeout(getTables, 1000);
});
}
function getTables() {
// Collect a list of available tables
steps.getTables(adapter, opts, function(err, tableNames) {
bailOnError(err);
// If we're in interactive mode, prompt the user to select from the list of available tables
if (opts.interactive) {
return prompts.tableSelection(tableNames, onTablesSelected);
}
// Use the found tables (or a filtered set if --table is used)
return onTablesSelected(tableNames);
});
}
// When tables have been selected, fetch data for those tables
function onTablesSelected(tables) {
// Assign partialed functions to make the code slightly more readable
steps.collect = mapValues(steps.collect, function(method) {
return partial(method, adapter, { tables: tables });
});
// Collect the data in parallel
async.parallel(steps.collect, onTableDataCollected);
}
// When table data has been collected, build an object representation of them
function onTableDataCollected(err, data) {
bailOnError(err);
var tableName, models = {}, model;
for (tableName in data.tableStructure) {
model = steps.tableToObject({
name: tableName,
columns: data.tableStructure[tableName],
comment: data.tableComments[tableName]
}, opts);
models[model.name] = model;
}
data.models = steps.findReferences(models);
// Note: This mutates the models - sorry. PRs are welcome.
steps.findOneToManyReferences(adapter, data.models, function(refErr) {
if (refErr) {
throw refErr;
}
data.types = steps.generateTypes(data, opts);
adapter.close();
steps.outputData(data, opts, onDataOutput);
});
}
// When the data has been written to stdout/files
function onDataOutput() {
if (!opts.outputDir) {
return;
}
if (opts.interactive) {
console.log('\n\n\n');
}
var dir = path.resolve(opts.outputDir);
console.log('Demo app generated in ' + dir + '. To run:');
console.log('cd ' + dir);
console.log('npm install');
console.log('npm start');
console.log();
console.log('Then point your browser at http://localhost:3000');
}
function bail(err) {
console.error(err.message ? err.message : err.toString());
process.exit(1);
}
function bailOnError(err) {
if (err) {
return bail(err);
}
}