microfleet/core

View on GitHub
packages/plugin-couchdb/src/couchdb.ts

Summary

Maintainability
A
2 hrs
Test Coverage
import { strict as assert } from 'node:assert'
import { resolve } from 'path'
import { NotFoundError } from 'common-errors'
import type { Microfleet } from '@microfleet/core'
import type { PluginInterface } from '@microfleet/core-types'
import { PluginTypes } from '@microfleet/utils'
import retry from 'bluebird-retry'
import CouchDB from 'nano'
import type * as _ from '@microfleet/plugin-validator'
import type * as __ from '@microfleet/plugin-logger'

/**
 * Relative priority inside the same plugin group type
 */
export const priority = 0
export const name = 'couchdb'
export const type = PluginTypes.database

export interface Config {
  connection: CouchDB.Configuration;
  database: string;
  indexDefinitions?: CouchDB.CreateIndexRequest[];
}

declare module '@microfleet/core-types' {
  interface Microfleet {
    couchdb: CouchDB.DocumentScope<any> | null;
  }

  interface ConfigurationOptional {
    couchbd: Config
  }
}

/**
 * Defines closure
 */
const startupHandlers = (
  service: Microfleet,
  nano: CouchDB.ServerScope,
  database: string,
  indices: Config['indexDefinitions'] = []
): PluginInterface => ({
  async connect() {
    assert(!service.couchdb, 'couchdb has already been initialized')

    const db = nano.use(database)
    const establishConnection = async () => {
      try {
        await nano.db.create(database)
      } catch (e: any) {
        if (e.statusCode !== 412 || e.error !== 'file_exists') {
          throw e
        }
      }

      for (const indexReq of indices) {
        const resp = await db.createIndex(indexReq)
        service.log.info({ index: resp }, 'created index')
      }

      service.couchdb = db
    }

    await retry(establishConnection, {
      interval: 500,
      backoff: 2,
      max_interval: 5000,
      timeout: 60000,
      max_tries: 100,
      predicate(err: Error) {
        service.log.warn({ err }, 'failing to connect to couchdb, scheduling reconnect')
        return true
      },
    })

    service.emit(`plugin:connect:${name}`, service.couchdb)
    return service.couchdb
  },

  async close() {
    const { couchdb } = service
    service.emit(`plugin:close:${name}`, couchdb)
    service.couchdb = null
  },
})

export async function attach(
  this: Microfleet,
  params: Config
): Promise<PluginInterface> {
  assert(this.hasPlugin('logger'), new NotFoundError('log module must be included'))
  assert(this.hasPlugin('validator'), new NotFoundError('validator module must be included'))

  // load local schemas
  await this.validator.addLocation(resolve(__dirname, '../schemas'))

  const opts = this.validator.ifError<Config>(name, params)
  const nano = CouchDB(opts.connection)

  return startupHandlers(this, nano, opts.database, opts.indexDefinitions)
}