mod_workspace_mergeTemplates.js

/**
## /workspace/mergeTemplates

The workspace is cached in the module scope to allow for the mergeObjectTemplates(layer) method to assign template objects defined in a JSON layer to the workspace.templates{}.

@requires /utils/merge
@requires /utils/envReplace
@requires /workspace/getTemplate
@requires /workspace/cache

@module /workspace/mergeTemplates
*/

const merge = require('../utils/merge')

const envReplace = require('../utils/envReplace')

const getTemplate = require('./getTemplate')

const workspaceCache = require('./cache')

let workspace

/**
@function mergeTemplates
@async

@description
The mergeTemplates method will be called for a layer or locale obj.

The locale or layer object will be merged with a template defined as obj.template string property.

The method will check for a template matching the obj.key string property if obj.template is undefined.

An array of templates can be defined as obj.templates[]. The templates will be merged into the obj in the order the template keys are in the templates[] array.

@param {Object} obj 

@property {string} [obj.template] Key of template for the object.
@property {string} obj.key Fallback for lookup of template if not an implicit property.
@property {array} [obj.templates] An array of template keys to be merged into the object.

@returns {Promise} The layer or locale provided as obj param.
*/
module.exports = async function mergeTemplates(obj) {

  // Cache workspace in module scope for template assignment.
  workspace = await workspaceCache()

  // The object has an implicit template to merge into.
  if (obj.template) {

    const template = await getTemplate(obj.template)

    // Failed to get template matching obj.template from template.src!
    if (template.err instanceof Error) {

      obj.err ??= []
      obj.err.push(template.err.message)

      // The template is not in the workspace.templates{}
    } else if (template instanceof Error) {

      obj.err ??= []
      obj.err.push(template.message)

    } else {

      // Merge obj --> template
      // Template must be cloned to prevent cross polination and array aggregation.
      obj = merge(structuredClone(template), obj)
    }

    // Check whether the object key exist as template if no implicit template has been defined.
  } else if (Object.hasOwn(workspace.templates, obj.key)) {

    obj.err ??= []
    obj.err.push(`Template matching ${obj.key} exists in workspace.`)
  }

  for (const template_key of obj.templates || []) {

    const template = await getTemplate(template_key)

    // Failed to retrieve template matching template_key
    if (template.err instanceof Error) {

      obj.err ??= []
      obj.err.push(template.err.message)

      // A template matching the template_key does not exist.
    } else if (template instanceof Error) {

      obj.err ??= []
      obj.err.push(`${template_key}: ${template.message}`)
    } else {

      //The object key must not be overwritten by a template key.
      delete template.key;

      //The object template must not be overwritten by a templates template.
      delete template.template;

      // Merge template --> obj
      obj = merge(obj, template)
    }
  }

  // Substitute ${SRC_*} in object string.
  obj = envReplace(obj)

  // Assign templates to workspace.
  assignWorkspaceTemplates(obj)

  // Assign default workspace dbs if not defined in template.
  obj.dbs ??= workspace.dbs

  return obj
}

/**
@function assignWorkspaceTemplates

@description
The method parses an object for a template object property. The template property value will be assigned to the workspace.templates{} object matching the template key value.

The template._type property will be set to 'template' indicating that the templates origin is in the workspace. It is possible to overassign _type:'core' templates which are loaded from the /mod/workspace/templates directory.

The method will call itself for nested objects.

@param {Object} obj 
*/
function assignWorkspaceTemplates(obj) {
  // Return early if object is null or empty
  if (obj === null) return;

  if (obj instanceof Object && !Object.keys(obj)) return;

  Object.entries(obj).forEach(entry => {
    // Process template objects - if found, add type and merge into workspace templates
    if (entry[0] === 'template' && entry[1].key) {

      entry[1]._type = 'template';
      workspace.templates[entry[1].key] = Object.assign(workspace.templates[entry[1].key] || {}, entry[1])

      return;
    }

    // Recursively process each item if we find an array
    if (Array.isArray(entry[1])) {

      entry[1].forEach(assignWorkspaceTemplates)
      return;
    }

    // Recursively process nested objects
    if (entry[1] instanceof Object) {

      assignWorkspaceTemplates(entry[1]);
    }

  })
}