makeomatic/mservice

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

Summary

Maintainability
A
1 hr
Test Coverage
import assert from 'node:assert/strict'
import { resolve } from 'path'
import rfdc from 'rfdc'
import { isObject } from 'lodash'
import { Microfleet, PluginTypes } from '@microfleet/core'
import { defaultsDeep } from '@microfleet/utils'

import { Router, ActionTransport } from './router'
import Routes from './routes'
import Tracker from './tracker'
import { auditLog } from './extensions/index'
import { Lifecycle } from './lifecycle/index'

import type { RouterPluginConfig } from './types/plugin'
import type { ServiceRequest } from './types/router'
import type { PluginInterface } from '@microfleet/core-types'

export const name = 'router'
export const type = PluginTypes.transport
export const priority = 10

/**
 * Shallow copies object, pass-through everything else
 */
const shallowObjectClone = (prop: any) => isObject(prop)
  ? { ...prop }
  : prop

/**
 * Allows to deep clone object
 */
const deepClone = rfdc()

/**
 * Fills gaps in default service request.
 * @param request - service request.
 * @returns Prepared service request.
 */
const prepareInternalRequest = (request: Partial<ServiceRequest>): ServiceRequest => ({
  // initiate action to ensure that we have prepared proto fo the object
  // input params
  // make sure we standardize the request
  // to provide similar interfaces
  action: null as any,
  headers: shallowObjectClone(request.headers),
  locals: shallowObjectClone(request.locals),
  auth: shallowObjectClone(request.auth),
  log: console as any,
  method: ActionTransport.internal,
  params: request.params != null
    ? deepClone(request.params)
    : Object.create(null),
  parentSpan: null,
  span: null,
  query: Object.create(null),
  route: '',
  transport: ActionTransport.internal,
  transportRequest: Object.create(null),
  reformatError: false,
})

const defaultConfig: Partial<RouterPluginConfig> = {
 /* Routes configuration */
 routes: {
   /* Directory to scan for actions. */
   directory: resolve(process.cwd(), 'src/actions'),
   /* Enables health action by default */
   enabledGenericActions: [
     'health',
   ],
   /* Enables response validation. */
   responseValidation: {
     enabled: false,
     maxSample: 7,
     panic: false,
   }
 },
 /* Extensions configuration */
 extensions: {
   register: [auditLog()],
 },
}

export async function attach(
  this: Microfleet,
  options: Partial<RouterPluginConfig>
): Promise<PluginInterface> {
  assert(this.hasPlugin('logger'), 'log module must be included')
  assert(this.hasPlugin('validator'), 'validator module must be included')

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

  const {
    auth,
    extensions: { register: extensions },
    routes: {
      prefix,
      directory,
      enabled,
      allRoutes,
      enabledGenericActions,
      responseValidation: validateResponse
    }
  } = this.validator.ifError<RouterPluginConfig>('router', defaultsDeep(options, defaultConfig))

  const routes = new Routes()
  const lifecycle = new Lifecycle({
    extensions,
    config: { auth, validateResponse },
    context: this,
  })
  const router = this.router = new Router({
    routes,
    lifecycle,
    config: {
      prefix,
      directory,
      enabled,
      enabledGenericActions,
      allRoutes,
    },
    log: this.log,
    requestCountTracker: new Tracker(this)
  })

  // dispatcher
  this.dispatch = (route: string, request: Partial<ServiceRequest>) =>
    router.prefixAndDispatch(route, prepareInternalRequest(request))

  return {
    async connect() {
      await router.ready()
    }
  }
}