Skip to content

onify/flow-extensions

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Jan 13, 2025
aaee78d · Jan 13, 2025

History

56 Commits
Jan 13, 2025
May 5, 2023
Nov 26, 2024
Nov 26, 2024
Jul 14, 2024
May 5, 2023
Dec 22, 2023
May 4, 2024
Jan 25, 2024
Nov 26, 2024
May 4, 2024
Feb 27, 2022
May 4, 2024
Apr 11, 2024
Oct 27, 2024
Jan 13, 2025

Repository files navigation

Onify Flow Extensions

Built latestCoverage Status

Api

  • extensions: Flow extensions
  • extendFn: extend function to pass to serializer

Examples

Bpmn engine example

import { createRequire } from 'node:module';
import { fileURLToPath } from 'node:url';
import { Engine } from 'bpmn-engine';
import { extensions } from '@onify/flow-extensions';
import { FlowScripts } from '@onify/flow-extensions/FlowScripts';

const nodeRequire = createRequire(fileURLToPath(import.meta.url));

const source = `
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:camunda="http://camunda.org/schema/1.0/bpmn">
  <process id="theProcess" isExecutable="true">
    <serviceTask id="task1" camunda:expression="\${environment.services.serviceFn}" camunda:resultVariable="result" />
    <sequenceFlow id="to-task2" sourceRef="task1" targetRef="task2" />
    <scriptTask id="task2" camunda:resultVariable="out" scriptFormat="js">
      <script>
        next(null, myContextFn());
      </script>
    </scriptTask>
  </process>
</definitions>`;

const name = 'onify flow';
const engine = new Engine({
  name,
  source,
  moddleOptions: {
    camunda: nodeRequire('camunda-bpmn-moddle/resources/camunda.json'),
  },
  services: {
    serviceFn(scope, callback) {
      callback(null, { data: 1 });
    },
  },
  extensions: {
    onify: extensions,
  },
  scripts: new FlowScripts(name, './script-resources', {
    myContextFn() {
      return 2;
    },
  }),
});

engine.execute((err, instance) => {
  if (err) throw err;
  console.log(instance.name, instance.environment.output);
});

Extract scripts with extend function

import { createRequire } from 'node:module';
import { fileURLToPath } from 'node:url';
import BpmnModdle from 'bpmn-moddle';
import * as Elements from 'bpmn-elements';
import { Serializer, TypeResolver } from 'moddle-context-serializer';
import { extendFn } from '@onify/flow-extensions';

const nodeRequire = createRequire(fileURLToPath(import.meta.url));

const source = `
<definitions id="Def_0" xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
  xmlns:camunda="http://camunda.org/schema/1.0/bpmn"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  targetNamespace="http://bpmn.io/schema/bpmn">
  <process id="process-1" name="Onify Flow" isExecutable="true">
    <serviceTask id="task1">
      <extensionElements>
        <camunda:connector>
          <camunda:connectorId>onifyApiRequest</camunda:connectorId>
          <camunda:inputOutput>
            <camunda:inputParameter name="method">GET</camunda:inputParameter>
            <camunda:inputParameter name="url">/my/items/workspace-1</camunda:inputParameter>
            <camunda:outputParameter name="result">
              <camunda:script scriptFormat="js">
                next(null, {
                  id: content.id,
                  statuscode,
                });
              </camunda:script>
            </camunda:outputParameter>
          </camunda:inputOutput>
        </camunda:connector>
        <camunda:inputOutput>
          <camunda:outputParameter name="result">\${content.output.result.statuscode}</camunda:outputParameter>
        </camunda:inputOutput>
      </extensionElements>
    </serviceTask>
    <sequenceFlow id="to-task2" sourceRef="task1" targetRef="task2" />
    <scriptTask id="task2" camunda:resultVariable="out" scriptFormat="js">
      <script>
        next(null, 2);
      </script>
    </scriptTask>
  </process>
</definitions>`;

getScripts(source).then(console.log).catch(console.error);

async function getScripts(bpmnSource) {
  const moddle = await getModdleContext(bpmnSource, {
    camunda: nodeRequire('camunda-bpmn-moddle/resources/camunda.json'),
  });

  const serialized = Serializer(moddle, TypeResolver(Elements), extendFn);
  return serialized.elements.scripts;
}

function getModdleContext(source, options) {
  const bpmnModdle = new BpmnModdle(options);
  return bpmnModdle.fromXML(Buffer.isBuffer(source) ? source.toString() : source.trim());
}

Extract timers

import { createRequire } from 'node:module';
import { fileURLToPath } from 'node:url';
import BpmnModdle from 'bpmn-moddle';
import * as Elements from 'bpmn-elements';
import { Serializer, TypeResolver } from 'moddle-context-serializer';
import { extendFn, OnifyTimerEventDefinition } from '@onify/flow-extensions';

const nodeRequire = createRequire(fileURLToPath(import.meta.url));

const source = `
<definitions id="Def_0" xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
  xmlns:camunda="http://camunda.org/schema/1.0/bpmn"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  targetNamespace="http://bpmn.io/schema/bpmn">
  <process id="cycle-1" name="Onify start at time cycle" isExecutable="true" camunda:historyTimeToLive="PT180M">
    <startEvent id="start">
      <timerEventDefinition>
        <timeCycle xsi:type="tFormalExpression">0 1 * * *</timeCycle>
      </timerEventDefinition>
    </startEvent>
    <sequenceFlow id="to-task" sourceRef="start" targetRef="task" />
    <userTask id="task" />
    <boundaryEvent id="bound-timer" cancelActivity="false" attachedToRef="task">
      <timerEventDefinition>
        <timeDuration xsi:type="tFormalExpression">R3/PT1M</timeDuration>
      </timerEventDefinition>
    </boundaryEvent>
    <sequenceFlow id="to-wait" sourceRef="task" targetRef="wait" />
    <intermediateThrowEvent id="timer">
      <timerEventDefinition>
        <timeCycle xsi:type="tFormalExpression">\${environment.settings.postpone}</timeCycle>
      </timerEventDefinition>
    </intermediateThrowEvent>
    <sequenceFlow id="to-end" sourceRef="wait" targetRef="end" />
    <endEvent id="end" />
  </process>
</definitions>`;

getTimers(source).then(console.log).catch(console.error);

const dummyEventActivity = { broker: {}, environment: { Logger() {} } };

async function getTimers(bpmnSource) {
  const moddle = await getModdleContext(bpmnSource, {
    camunda: nodeRequire('camunda-bpmn-moddle/resources/camunda.json'),
  });

  const serialized = Serializer(moddle, TypeResolver(Elements), extendFn);

  for (const t of serialized.elements.timers) {
    const ed = new OnifyTimerEventDefinition(dummyEventActivity, t.timer);

    try {
      t.parsed = ed.parse(t.timer.timerType, t.timer.value);
    } catch {}
  }

  return serialized.elements.timers;
}

function getModdleContext(source, options) {
  const bpmnModdle = new BpmnModdle(options);
  return bpmnModdle.fromXML(Buffer.isBuffer(source) ? source.toString() : source.trim());
}

Extend sequence flow with properties and take listeners

import { createRequire } from 'node:module';
import { fileURLToPath } from 'node:url';
import { Engine } from 'bpmn-engine';
import * as Elements from 'bpmn-elements';
import { OnifySequenceFlow, extensions } from '@onify/flow-extensions';
import { FlowScripts } from '@onify/flow-extensions/FlowScripts';

const nodeRequire = createRequire(fileURLToPath(import.meta.url));

const source = `
<?xml version="1.0" encoding="UTF-8"?>
<definitions
  id="Def_0"
  xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
  xmlns:camunda="http://camunda.org/schema/1.0/bpmn"
  targetNamespace="http://bpmn.io/schema/bpmn">
  <process id="Process_1kk79yr" isExecutable="true">
    <startEvent id="start" />
    <sequenceFlow id="to-script" sourceRef="start" targetRef="script">
      <extensionElements>
        <camunda:properties>
          <camunda:property name="source" value="\${content.id}" />
        </camunda:properties>
        <camunda:executionListener event="take">
          <camunda:script scriptFormat="js">environment.output.fields = listener.fields; next();</camunda:script>
          <camunda:field name="taken">
            <camunda:expression>\${true}</camunda:expression>
          </camunda:field>
          <camunda:field name="bar">
            <camunda:string>baz</camunda:string>
          </camunda:field>
        </camunda:executionListener>
      </extensionElements>
    </sequenceFlow>
    <sequenceFlow id="to-end" sourceRef="script" targetRef="end">
      <extensionElements>
        <camunda:properties>
          <camunda:property name="foo" value="bar" />
        </camunda:properties>
      </extensionElements>
    </sequenceFlow>
    <scriptTask id="script" name="script" scriptFormat="js">
      <script>next(null, { foo: environment.variables.required.input });</script>
    </scriptTask>
    <boundaryEvent id="catch-err" attachedToRef="script">
      <errorEventDefinition />
    </boundaryEvent>
    <endEvent id="end-err">
      <extensionElements>
        <camunda:executionListener event="start">
          <camunda:script scriptFormat="js">
            environment.output.failedBy = content.inbound[0].properties.error;
            if (next) next();
          </camunda:script>
        </camunda:executionListener>
      </extensionElements>
    </endEvent>
    <sequenceFlow id="to-end-err" sourceRef="catch-err" targetRef="end-err">
      <extensionElements>
        <camunda:properties>
          <camunda:property name="error" value="\${content.output}" />
        </camunda:properties>
      </extensionElements>
    </sequenceFlow>
    <endEvent id="end" />
  </process>
</definitions>`;

const engine = new Engine({
  name: 'sequence flow extension',
  source,
  moddleOptions: {
    camunda: nodeRequire('camunda-bpmn-moddle/resources/camunda.json'),
  },
  extensions: {
    onify: extensions,
  },
  scripts: new FlowScripts('sequence flow extension'),
  elements: {
    ...Elements,
    SequenceFlow: OnifySequenceFlow,
  },
  variables: {
    required: {
      input: true,
    },
  },
});

engine.execute((err, instance) => {
  if (err) throw err;
  console.log(instance.name, instance.environment.output);
});