Source: engine/universal/core/src/separator.webpack.js

// Do esm import on prod build -> webpack
// This is for easy development / debugging so we don't need webpack
// eslint-disable-next-line import/no-extraneous-dependencies
//const BPMNModdle = require('esm')(module)('bpmn-moddle').default;
import BPMNModdle from 'bpmn-moddle';

const moddle = new BPMNModdle();

/**
 * Promisizes the conversion from XML to bpmn-moddle
 * @param {string} xml
 * @returns {object} The bpmn-moddle instance
 * @private
 */
function fromXML(xml) {
  return new Promise((resolve, reject) => {
    moddle.fromXML(xml, (err, bpmnModel) => {
      if (err) {
        reject(err);
      } else {
        resolve(bpmnModel);
      }
    });
  });
}

/**
 * Promisizes the conversion from bpmn-moddle to XML
 * @param {object} bpmn
 * @returns {string} The generated XML
 * @private
 */
function toXML(bpmn) {
  return new Promise((resolve, reject) => {
    moddle.toXML(bpmn, (err, xml) => {
      if (err) {
        reject(err);
      } else {
        resolve(xml);
      }
    });
  });
}

/**
 * Return the executable process in the given definition.
 * @param {object} definition The definition with the executable process(es)
 * @returns {object} The executable process
 * @private
 */
function getExecutableProcess(definition) {
  return definition
    .get('rootElements')
    .find((el) => el.$type === 'bpmn:Process' && el.isExecutable === true);
}

/**
 * Create a sequence flow from the `sourceRef` to the `targetRef`.
 * This functions adds the newly created sequnce flow
 * to the respective references as `outgoing` or `incoming` properties.
 * @param {object} sourceRef The source reference for the new sequence flow
 * @param {object} targetRef The target reference for the new sequence flow
 * @returns {object} The newly created sequence flow
 * @private
 */
function createSequenceFlow(sourceRef, targetRef) {
  const seq = moddle.create('bpmn:SequenceFlow', {
    id: String(Math.random()),
    sourceRef,
    targetRef,
  });
  sourceRef.get('outgoing').push(seq);
  targetRef.get('incoming').push(seq);
  return seq;
}

/**
 * Create a subprocess for execution of a single activity
 * wrapped with a start and end event.
 * This method creates all necessary sequence flows between the
 * respective elements.
 * @param {object} bpmnModel The BPMN definition
 * @param {object} startEvent The BPMN start event
 * @param {object} activity The BPMN activity
 * @param {Array<object>} endEvents The BPMN end events
 * @returns {object} The modified BPMN definition
 * @private
 */
function createSubProcess(bpmnModel, startEvent, activity, endEvents) {
  const newProcess = getExecutableProcess(bpmnModel);
  const flowElements = [startEvent, activity, ...endEvents];

  // Add sequence flow from start event to activity
  // Reset, since the original elements refer to other sequence flows
  startEvent.set('outgoing', []);
  activity.set('incoming', []);
  let seq = createSequenceFlow(startEvent, activity);
  flowElements.push(seq);

  // Add sequence flows from activity to end events
  activity.set('outgoing', []);
  endEvents.forEach((endEvent) => {
    endEvent.set('incoming', []);
    seq = createSequenceFlow(activity, endEvent);
    flowElements.push(seq);
  });

  newProcess.set('flowElements', flowElements);
  return bpmnModel;
}

function copyElement(element) {
  const options = Object.assign({}, element);
  delete options.$type;
  return moddle.create(element.$type, options);
}

/**
 * Return the activity and wrapper elements (start and end events) for
 * the given activity id.
 * @param {Array<object>} flowElements The flow elements of the parent process
 * @param {string} activityID The id of the activity we want to extract
 * @returns {Array<object>} The startEvent, activity and end events
 * @private
 */
function getActivityAndWrapperElements(flowElements, activityID) {
  let startEvent;
  let activity;
  if (!activityID) {
    // No ID, fetch the start event and first activity of the process
    startEvent = flowElements.find((el) => el.$type === 'bpmn:StartEvent');
    activity = startEvent.get('outgoing')[0].get('targetRef');
  } else {
    startEvent = moddle.create('bpmn:StartEvent', { id: String(Math.random()) });
    activity = flowElements.find((el) => el.id === activityID);
  }

  // Create an end event for each outgoing
  // sequence flow of the activity.
  const endEvents = [];
  activity.get('outgoing').forEach((seqFlow) => {
    endEvents.push(moddle.create('bpmn:EndEvent', { id: seqFlow.targetRef.id }));
  });

  // Make copies of the elements so we don't alter the original BPMN diagram
  startEvent = copyElement(startEvent);
  activity = copyElement(activity);

  return [startEvent, activity, endEvents];
}

/**
 * @memberof module:@proceed/core
 * @class
 * Separator class that offers functionality to extract subprocesses
 * for each activity in a BPMN diagram.
 */
class BPMNSeparator {
  constructor() {
    this.definition = null;
    this.scaffold = '';
  }

  /**
   * Set the BPMN diagram for the separator to use.
   * @param {string} bpmnXML The BPMN XML
   * @returns {Promise} Resolves when the init was finished
   */
  async setBPMN(bpmnXML) {
    // Parse the BPMN XML
    return fromXML(bpmnXML).then((bpmnModel) => {
      // Store the definition
      this.definition = bpmnModel;
      return this._setScaffold(bpmnXML);
    });
  }

  async getBPMN() {
    return toXML(this.definition);
  }

  /**
   * Extract and store the scaffold to use for every
   * subprocess of this BPMN diagram.
   * @param {string} bpmnXML The original BPMN XML
   * @returns {Promise} Resolves when the scaffold was set
   * @private
   */
  async _setScaffold(bpmnXML) {
    return fromXML(bpmnXML)
      .then((bpmnModel) => {
        bpmnModel.set('diagrams', undefined);
        const process = getExecutableProcess(bpmnModel);
        process.set('flowElements', []);
        return bpmnModel;
      })
      .then((bpmnModel) => toXML(bpmnModel))
      .then((xml) => {
        this.scaffold = xml;
      });
  }

  /**
   * Creates and returns the next subprocess of the original BPMN
   * diagram corresponding to the activity with the given id, or
   * the first activity if none was given.
   * @param {string} endEventID The id of the end event (equal to the id of the next activity)
   * @returns {Promise} Resolves when the next activity subprocess was created
   */
  async next(endEventID) {
    return fromXML(this.scaffold).then(async (bpmnModel) => {
      const flowElements = getExecutableProcess(this.definition).get('flowElements');
      const [startEvent, activity, endEvents] = getActivityAndWrapperElements(
        flowElements,
        endEventID
      );

      if (activity.$type === 'bpmn:EndEvent') {
        // Return just the end event id (for static deployment)
        // if we reached the end of the BPMN diagram.
        return { bpmn: { id: activity.id } };
      }

      // Create the artificial subprocess
      createSubProcess(bpmnModel, startEvent, activity, endEvents);

      const xml = await toXML(bpmnModel);
      return { bpmn: xml, activity };
    });
  }
}

//exports.default = BPMNSeparator;
export default BPMNSeparator;