feathersjs/feathers

View on GitHub
packages/generators/src/connection/index.ts

Summary

Maintainability
A
1 hr
Test Coverage
import { dirname } from 'path'
import { runGenerator, prompt, mergeJSON, toFile, when } from '@featherscloud/pinion'
import { fileURLToPath } from 'url'
import chalk from 'chalk'
import {
  install,
  FeathersBaseContext,
  DatabaseType,
  getDatabaseAdapter,
  addVersions,
  checkPreconditions,
  initializeBaseContext
} from '../commons.js'

// Set __dirname in es module
const __dirname = dirname(fileURLToPath(import.meta.url))

export interface ConnectionGeneratorContext extends FeathersBaseContext {
  name?: string
  database: DatabaseType
  connectionString: string
  dependencies: string[]
}

export type ConnectionGeneratorArguments = FeathersBaseContext &
  Partial<Pick<ConnectionGeneratorContext, 'database' | 'connectionString' | 'name'>>

export const defaultConnectionString = (type: DatabaseType, name: string) => {
  const connectionStrings = {
    mongodb: `mongodb://127.0.0.1:27017/${name}`,
    mysql: `mysql://root:@localhost:3306/${name}`,
    postgresql: `postgres://postgres:@localhost:5432/${name}`,
    sqlite: `${name}.sqlite`,
    mssql: `mssql://root:password@localhost:1433/${name}`,
    other: ''
  }

  return connectionStrings[type]
}

export const prompts = ({ database, connectionString, pkg, name }: ConnectionGeneratorArguments) => [
  {
    name: 'database',
    type: 'list',
    when: !database,
    message: 'Which database are you connecting to?',
    suffix: chalk.grey(' Databases can be added at any time'),
    choices: [
      { value: 'sqlite', name: 'SQLite' },
      { value: 'mongodb', name: 'MongoDB' },
      { value: 'postgresql', name: 'PostgreSQL' },
      { value: 'mysql', name: 'MySQL/MariaDB' },
      { value: 'mssql', name: 'Microsoft SQL' },
      {
        value: 'other',
        name: `Another database ${chalk.grey('(not configured automatically, use with custom services)')}`
      }
    ]
  },
  {
    name: 'connectionString',
    type: 'input',
    when: (answers: ConnectionGeneratorContext) =>
      !connectionString && database !== 'other' && answers.database !== 'other',
    message: 'Enter your database connection string',
    default: (answers: ConnectionGeneratorContext) =>
      defaultConnectionString(answers.database, answers.name || name || pkg.name)
  }
]

export const DATABASE_CLIENTS = {
  mongodb: 'mongodb',
  sqlite: 'sqlite3',
  postgresql: 'pg',
  mysql: 'mysql',
  mssql: 'mssql'
}

export const getDatabaseClient = (database: DatabaseType) =>
  database === 'other' ? null : DATABASE_CLIENTS[database]

export const generate = (ctx: ConnectionGeneratorArguments) =>
  Promise.resolve(ctx)
    .then(initializeBaseContext())
    .then(checkPreconditions())
    .then(prompt(prompts))
    .then((ctx) => ctx as ConnectionGeneratorContext)
    .then(
      when<ConnectionGeneratorContext>(
        (ctx) => ctx.database !== 'other',
        runGenerator<ConnectionGeneratorContext>(
          __dirname,
          'templates',
          ({ database }) => `${getDatabaseAdapter(database)}.tpl.js`
        ),
        mergeJSON<ConnectionGeneratorContext>(
          ({ connectionString, database }) =>
            getDatabaseAdapter(database) === 'knex'
              ? {
                  [database]: {
                    client: getDatabaseClient(database),
                    connection: connectionString,
                    ...(database === 'sqlite' ? { useNullAsDefault: true } : {})
                  }
                }
              : {
                  [database]: connectionString
                },
          toFile('config', 'default.json')
        ),
        async (ctx: ConnectionGeneratorContext) => {
          const dependencies: string[] = []
          const adapter = getDatabaseAdapter(ctx.database)
          const dbClient = getDatabaseClient(ctx.database)

          dependencies.push(`@feathersjs/${adapter}`)

          if (adapter === 'knex') {
            dependencies.push('knex')
          }

          dependencies.push(dbClient)

          if (ctx.dependencies) {
            return {
              ...ctx,
              dependencies: [...ctx.dependencies, ...dependencies]
            }
          }

          return install<ConnectionGeneratorContext>(
            addVersions(dependencies, ctx.dependencyVersions),
            false,
            ctx.feathers.packager
          )(ctx)
        }
      )
    )