UNPKG

dmn-js

Version:

A dmn toolkit and web modeler

1,932 lines (1,805 loc) 2.61 MB
/*! * dmn-js - dmn-modeler v17.2.1 * * Copyright (c) 2014-present, camunda Services GmbH * * Released under the bpmn.io license * http://bpmn.io/license * * Source Code: https://github.com/bpmn-io/dmn-js * * Date: 2025-05-23 */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.DmnJS = factory()); })(this, (function () { 'use strict'; /** * Flatten array, one level deep. * * @template T * * @param {T[][] | T[] | null} [arr] * * @return {T[]} */ function flatten$1(arr) { return Array.prototype.concat.apply([], arr); } const nativeToString$4 = Object.prototype.toString; const nativeHasOwnProperty$3 = Object.prototype.hasOwnProperty; function isUndefined$5(obj) { return obj === undefined; } function isDefined(obj) { return obj !== undefined; } function isNil(obj) { return obj == null; } function isArray$6(obj) { return nativeToString$4.call(obj) === '[object Array]'; } function isObject$2(obj) { return nativeToString$4.call(obj) === '[object Object]'; } function isNumber$3(obj) { return nativeToString$4.call(obj) === '[object Number]'; } /** * @param {any} obj * * @return {boolean} */ function isFunction$2(obj) { const tag = nativeToString$4.call(obj); return tag === '[object Function]' || tag === '[object AsyncFunction]' || tag === '[object GeneratorFunction]' || tag === '[object AsyncGeneratorFunction]' || tag === '[object Proxy]'; } function isString$4(obj) { return nativeToString$4.call(obj) === '[object String]'; } /** * Return true, if target owns a property with the given key. * * @param {Object} target * @param {String} key * * @return {Boolean} */ function has$3(target, key) { return !isNil(target) && nativeHasOwnProperty$3.call(target, key); } /** * @template T * @typedef { ( * ((e: T) => boolean) | * ((e: T, idx: number) => boolean) | * ((e: T, key: string) => boolean) | * string | * number * ) } Matcher */ /** * @template T * @template U * * @typedef { ( * ((e: T) => U) | string | number * ) } Extractor */ /** * @template T * @typedef { (val: T, key: any) => boolean } MatchFn */ /** * @template T * @typedef { T[] } ArrayCollection */ /** * @template T * @typedef { { [key: string]: T } } StringKeyValueCollection */ /** * @template T * @typedef { { [key: number]: T } } NumberKeyValueCollection */ /** * @template T * @typedef { StringKeyValueCollection<T> | NumberKeyValueCollection<T> } KeyValueCollection */ /** * @template T * @typedef { KeyValueCollection<T> | ArrayCollection<T> } Collection */ /** * Find element in collection. * * @template T * @param {Collection<T>} collection * @param {Matcher<T>} matcher * * @return {Object} */ function find$2(collection, matcher) { const matchFn = toMatcher$1(matcher); let match; forEach$3(collection, function (val, key) { if (matchFn(val, key)) { match = val; return false; } }); return match; } /** * Filter elements in collection. * * @template T * @param {Collection<T>} collection * @param {Matcher<T>} matcher * * @return {T[]} result */ function filter$2(collection, matcher) { const matchFn = toMatcher$1(matcher); let result = []; forEach$3(collection, function (val, key) { if (matchFn(val, key)) { result.push(val); } }); return result; } /** * Iterate over collection; returning something * (non-undefined) will stop iteration. * * @template T * @param {Collection<T>} collection * @param { ((item: T, idx: number) => (boolean|void)) | ((item: T, key: string) => (boolean|void)) } iterator * * @return {T} return result that stopped the iteration */ function forEach$3(collection, iterator) { let val, result; if (isUndefined$5(collection)) { return; } const convertKey = isArray$6(collection) ? toNum$3 : identity$3; for (let key in collection) { if (has$3(collection, key)) { val = collection[key]; result = iterator(val, convertKey(key)); if (result === false) { return val; } } } } /** * Reduce collection, returning a single result. * * @template T * @template V * * @param {Collection<T>} collection * @param {(result: V, entry: T, index: any) => V} iterator * @param {V} result * * @return {V} result returned from last iterator */ function reduce(collection, iterator, result) { forEach$3(collection, function (value, idx) { result = iterator(result, value, idx); }); return result; } /** * Return true if every element in the collection * matches the criteria. * * @param {Object|Array} collection * @param {Function} matcher * * @return {Boolean} */ function every(collection, matcher) { return !!reduce(collection, function (matches, val, key) { return matches && matcher(val, key); }, true); } /** * Return true if some elements in the collection * match the criteria. * * @param {Object|Array} collection * @param {Function} matcher * * @return {Boolean} */ function some(collection, matcher) { return !!find$2(collection, matcher); } /** * Transform a collection into another collection * by piping each member through the given fn. * * @param {Object|Array} collection * @param {Function} fn * * @return {Array} transformed collection */ function map$3(collection, fn) { let result = []; forEach$3(collection, function (val, key) { result.push(fn(val, key)); }); return result; } /** * Get the collections keys. * * @param {Object|Array} collection * * @return {Array} */ function keys(collection) { return collection && Object.keys(collection) || []; } /** * Shorthand for `keys(o).length`. * * @param {Object|Array} collection * * @return {Number} */ function size(collection) { return keys(collection).length; } /** * Get the values in the collection. * * @param {Object|Array} collection * * @return {Array} */ function values$1(collection) { return map$3(collection, val => val); } /** * Group collection members by attribute. * * @param {Object|Array} collection * @param {Extractor} extractor * * @return {Object} map with { attrValue => [ a, b, c ] } */ function groupBy(collection, extractor, grouped = {}) { extractor = toExtractor(extractor); forEach$3(collection, function (val) { let discriminator = extractor(val) || '_'; let group = grouped[discriminator]; if (!group) { group = grouped[discriminator] = []; } group.push(val); }); return grouped; } function uniqueBy(extractor, ...collections) { extractor = toExtractor(extractor); let grouped = {}; forEach$3(collections, c => groupBy(c, extractor, grouped)); let result = map$3(grouped, function (val, key) { return val[0]; }); return result; } /** * Sort collection by criteria. * * @template T * * @param {Collection<T>} collection * @param {Extractor<T, number | string>} extractor * * @return {Array} */ function sortBy(collection, extractor) { extractor = toExtractor(extractor); let sorted = []; forEach$3(collection, function (value, key) { let disc = extractor(value, key); let entry = { d: disc, v: value }; for (var idx = 0; idx < sorted.length; idx++) { let { d } = sorted[idx]; if (disc < d) { sorted.splice(idx, 0, entry); return; } } // not inserted, append (!) sorted.push(entry); }); return map$3(sorted, e => e.v); } /** * Create an object pattern matcher. * * @example * * ```javascript * const matcher = matchPattern({ id: 1 }); * * let element = find(elements, matcher); * ``` * * @template T * * @param {T} pattern * * @return { (el: any) => boolean } matcherFn */ function matchPattern(pattern) { return function (el) { return every(pattern, function (val, key) { return el[key] === val; }); }; } /** * @param {string | ((e: any) => any) } extractor * * @return { (e: any) => any } */ function toExtractor(extractor) { /** * @satisfies { (e: any) => any } */ return isFunction$2(extractor) ? extractor : e => { // @ts-ignore: just works return e[extractor]; }; } /** * @template T * @param {Matcher<T>} matcher * * @return {MatchFn<T>} */ function toMatcher$1(matcher) { return isFunction$2(matcher) ? matcher : e => { return e === matcher; }; } function identity$3(arg) { return arg; } function toNum$3(arg) { return Number(arg); } /* global setTimeout clearTimeout */ /** * @typedef { { * (...args: any[]): any; * flush: () => void; * cancel: () => void; * } } DebouncedFunction */ /** * Debounce fn, calling it only once if the given time * elapsed between calls. * * Lodash-style the function exposes methods to `#clear` * and `#flush` to control internal behavior. * * @param {Function} fn * @param {Number} timeout * * @return {DebouncedFunction} debounced function */ function debounce(fn, timeout) { let timer; let lastArgs; let lastThis; let lastNow; function fire(force) { let now = Date.now(); let scheduledDiff = force ? 0 : lastNow + timeout - now; if (scheduledDiff > 0) { return schedule(scheduledDiff); } fn.apply(lastThis, lastArgs); clear(); } function schedule(timeout) { timer = setTimeout(fire, timeout); } function clear() { if (timer) { clearTimeout(timer); } timer = lastNow = lastArgs = lastThis = undefined; } function flush() { if (timer) { fire(true); } clear(); } /** * @type { DebouncedFunction } */ function callback(...args) { lastNow = Date.now(); lastArgs = args; lastThis = this; // ensure an execution is scheduled if (!timer) { schedule(timeout); } } callback.flush = flush; callback.cancel = clear; return callback; } /** * Throttle fn, calling at most once * in the given interval. * * @param {Function} fn * @param {Number} interval * * @return {Function} throttled function */ function throttle(fn, interval) { let throttling = false; return function (...args) { if (throttling) { return; } fn(...args); throttling = true; setTimeout(() => { throttling = false; }, interval); }; } /** * Bind function against target <this>. * * @param {Function} fn * @param {Object} target * * @return {Function} bound function */ function bind$3(fn, target) { return fn.bind(target); } /** * Convenience wrapper for `Object.assign`. * * @param {Object} target * @param {...Object} others * * @return {Object} the target */ function assign$4(target, ...others) { return Object.assign(target, ...others); } /** * Pick properties from the given target. * * @template T * @template {any[]} V * * @param {T} target * @param {V} properties * * @return Pick<T, V> */ function pick$1(target, properties) { let result = {}; let obj = Object(target); forEach$3(properties, function (prop) { if (prop in obj) { result[prop] = target[prop]; } }); return result; } /** * Pick all target properties, excluding the given ones. * * @template T * @template {any[]} V * * @param {T} target * @param {V} properties * * @return {Omit<T, V>} target */ function omit(target, properties) { let result = {}; let obj = Object(target); forEach$3(obj, function (prop, key) { if (properties.indexOf(key) === -1) { result[key] = prop; } }); return result; } var FN_REF = '__fn'; var DEFAULT_PRIORITY$8 = 1000; var slice = Array.prototype.slice; /** * @typedef { { * stopPropagation(): void; * preventDefault(): void; * cancelBubble: boolean; * defaultPrevented: boolean; * returnValue: any; * } } Event */ /** * @template E * * @typedef { (event: E & Event, ...any) => any } EventBusEventCallback */ /** * @typedef { { * priority: number; * next: EventBusListener | null; * callback: EventBusEventCallback<any>; * } } EventBusListener */ /** * A general purpose event bus. * * This component is used to communicate across a diagram instance. * Other parts of a diagram can use it to listen to and broadcast events. * * * ## Registering for Events * * The event bus provides the {@link EventBus#on} and {@link EventBus#once} * methods to register for events. {@link EventBus#off} can be used to * remove event registrations. Listeners receive an instance of {@link Event} * as the first argument. It allows them to hook into the event execution. * * ```javascript * * // listen for event * eventBus.on('foo', function(event) { * * // access event type * event.type; // 'foo' * * // stop propagation to other listeners * event.stopPropagation(); * * // prevent event default * event.preventDefault(); * }); * * // listen for event with custom payload * eventBus.on('bar', function(event, payload) { * console.log(payload); * }); * * // listen for event returning value * eventBus.on('foobar', function(event) { * * // stop event propagation + prevent default * return false; * * // stop event propagation + return custom result * return { * complex: 'listening result' * }; * }); * * * // listen with custom priority (default=1000, higher is better) * eventBus.on('priorityfoo', 1500, function(event) { * console.log('invoked first!'); * }); * * * // listen for event and pass the context (`this`) * eventBus.on('foobar', function(event) { * this.foo(); * }, this); * ``` * * * ## Emitting Events * * Events can be emitted via the event bus using {@link EventBus#fire}. * * ```javascript * * // false indicates that the default action * // was prevented by listeners * if (eventBus.fire('foo') === false) { * console.log('default has been prevented!'); * }; * * * // custom args + return value listener * eventBus.on('sum', function(event, a, b) { * return a + b; * }); * * // you can pass custom arguments + retrieve result values. * var sum = eventBus.fire('sum', 1, 2); * console.log(sum); // 3 * ``` * * @template [EventMap=null] */ function EventBus() { /** * @type { Record<string, EventBusListener> } */ this._listeners = {}; // cleanup on destroy on lowest priority to allow // message passing until the bitter end this.on('diagram.destroy', 1, this._destroy, this); } /** * @overlord * * Register an event listener for events with the given name. * * The callback will be invoked with `event, ...additionalArguments` * that have been passed to {@link EventBus#fire}. * * Returning false from a listener will prevent the events default action * (if any is specified). To stop an event from being processed further in * other listeners execute {@link Event#stopPropagation}. * * Returning anything but `undefined` from a listener will stop the listener propagation. * * @template T * * @param {string|string[]} events to subscribe to * @param {number} [priority=1000] listen priority * @param {EventBusEventCallback<T>} callback * @param {any} [that] callback context */ /** * Register an event listener for events with the given name. * * The callback will be invoked with `event, ...additionalArguments` * that have been passed to {@link EventBus#fire}. * * Returning false from a listener will prevent the events default action * (if any is specified). To stop an event from being processed further in * other listeners execute {@link Event#stopPropagation}. * * Returning anything but `undefined` from a listener will stop the listener propagation. * * @template {keyof EventMap} EventName * * @param {EventName} events to subscribe to * @param {number} [priority=1000] listen priority * @param {EventBusEventCallback<EventMap[EventName]>} callback * @param {any} [that] callback context */ EventBus.prototype.on = function (events, priority, callback, that) { events = isArray$6(events) ? events : [events]; if (isFunction$2(priority)) { that = callback; callback = priority; priority = DEFAULT_PRIORITY$8; } if (!isNumber$3(priority)) { throw new Error('priority must be a number'); } var actualCallback = callback; if (that) { actualCallback = bind$3(callback, that); // make sure we remember and are able to remove // bound callbacks via {@link #off} using the original // callback actualCallback[FN_REF] = callback[FN_REF] || callback; } var self = this; events.forEach(function (e) { self._addListener(e, { priority: priority, callback: actualCallback, next: null }); }); }; /** * @overlord * * Register an event listener that is called only once. * * @template T * * @param {string|string[]} events to subscribe to * @param {number} [priority=1000] the listen priority * @param {EventBusEventCallback<T>} callback * @param {any} [that] callback context */ /** * Register an event listener that is called only once. * * @template {keyof EventMap} EventName * * @param {EventName} events to subscribe to * @param {number} [priority=1000] listen priority * @param {EventBusEventCallback<EventMap[EventName]>} callback * @param {any} [that] callback context */ EventBus.prototype.once = function (events, priority, callback, that) { var self = this; if (isFunction$2(priority)) { that = callback; callback = priority; priority = DEFAULT_PRIORITY$8; } if (!isNumber$3(priority)) { throw new Error('priority must be a number'); } function wrappedCallback() { wrappedCallback.__isTomb = true; var result = callback.apply(that, arguments); self.off(events, wrappedCallback); return result; } // make sure we remember and are able to remove // bound callbacks via {@link #off} using the original // callback wrappedCallback[FN_REF] = callback; this.on(events, priority, wrappedCallback); }; /** * Removes event listeners by event and callback. * * If no callback is given, all listeners for a given event name are being removed. * * @param {string|string[]} events * @param {EventBusEventCallback} [callback] */ EventBus.prototype.off = function (events, callback) { events = isArray$6(events) ? events : [events]; var self = this; events.forEach(function (event) { self._removeListener(event, callback); }); }; /** * Create an event recognized be the event bus. * * @param {Object} data Event data. * * @return {Event} An event that will be recognized by the event bus. */ EventBus.prototype.createEvent = function (data) { var event = new InternalEvent(); event.init(data); return event; }; /** * Fires an event. * * @example * * ```javascript * // fire event by name * events.fire('foo'); * * // fire event object with nested type * var event = { type: 'foo' }; * events.fire(event); * * // fire event with explicit type * var event = { x: 10, y: 20 }; * events.fire('element.moved', event); * * // pass additional arguments to the event * events.on('foo', function(event, bar) { * alert(bar); * }); * * events.fire({ type: 'foo' }, 'I am bar!'); * ``` * * @param {string} [type] event type * @param {Object} [data] event or event data * @param {...any} [args] additional arguments the callback will be called with. * * @return {any} The return value. Will be set to `false` if the default was prevented. */ EventBus.prototype.fire = function (type, data) { var event, firstListener, returnValue, args; args = slice.call(arguments); if (typeof type === 'object') { data = type; type = data.type; } if (!type) { throw new Error('no event type specified'); } firstListener = this._listeners[type]; if (!firstListener) { return; } // we make sure we fire instances of our home made // events here. We wrap them only once, though if (data instanceof InternalEvent) { // we are fine, we alread have an event event = data; } else { event = this.createEvent(data); } // ensure we pass the event as the first parameter args[0] = event; // original event type (in case we delegate) var originalType = event.type; // update event type before delegation if (type !== originalType) { event.type = type; } try { returnValue = this._invokeListeners(event, args, firstListener); } finally { // reset event type after delegation if (type !== originalType) { event.type = originalType; } } // set the return value to false if the event default // got prevented and no other return value exists if (returnValue === undefined && event.defaultPrevented) { returnValue = false; } return returnValue; }; /** * Handle an error by firing an event. * * @param {Error} error The error to be handled. * * @return {boolean} Whether the error was handled. */ EventBus.prototype.handleError = function (error) { return this.fire('error', { error: error }) === false; }; EventBus.prototype._destroy = function () { this._listeners = {}; }; /** * @param {Event} event * @param {any[]} args * @param {EventBusListener} listener * * @return {any} */ EventBus.prototype._invokeListeners = function (event, args, listener) { var returnValue; while (listener) { // handle stopped propagation if (event.cancelBubble) { break; } returnValue = this._invokeListener(event, args, listener); listener = listener.next; } return returnValue; }; /** * @param {Event} event * @param {any[]} args * @param {EventBusListener} listener * * @return {any} */ EventBus.prototype._invokeListener = function (event, args, listener) { var returnValue; if (listener.callback.__isTomb) { return returnValue; } try { // returning false prevents the default action returnValue = invokeFunction(listener.callback, args); // stop propagation on return value if (returnValue !== undefined) { event.returnValue = returnValue; event.stopPropagation(); } // prevent default on return false if (returnValue === false) { event.preventDefault(); } } catch (error) { if (!this.handleError(error)) { console.error('unhandled error in event listener', error); throw error; } } return returnValue; }; /** * Add new listener with a certain priority to the list * of listeners (for the given event). * * The semantics of listener registration / listener execution are * first register, first serve: New listeners will always be inserted * after existing listeners with the same priority. * * Example: Inserting two listeners with priority 1000 and 1300 * * * before: [ 1500, 1500, 1000, 1000 ] * * after: [ 1500, 1500, (new=1300), 1000, 1000, (new=1000) ] * * @param {string} event * @param {EventBusListener} newListener */ EventBus.prototype._addListener = function (event, newListener) { var listener = this._getListeners(event), previousListener; // no prior listeners if (!listener) { this._setListeners(event, newListener); return; } // ensure we order listeners by priority from // 0 (high) to n > 0 (low) while (listener) { if (listener.priority < newListener.priority) { newListener.next = listener; if (previousListener) { previousListener.next = newListener; } else { this._setListeners(event, newListener); } return; } previousListener = listener; listener = listener.next; } // add new listener to back previousListener.next = newListener; }; /** * @param {string} name * * @return {EventBusListener} */ EventBus.prototype._getListeners = function (name) { return this._listeners[name]; }; /** * @param {string} name * @param {EventBusListener} listener */ EventBus.prototype._setListeners = function (name, listener) { this._listeners[name] = listener; }; EventBus.prototype._removeListener = function (event, callback) { var listener = this._getListeners(event), nextListener, previousListener, listenerCallback; if (!callback) { // clear listeners this._setListeners(event, null); return; } while (listener) { nextListener = listener.next; listenerCallback = listener.callback; if (listenerCallback === callback || listenerCallback[FN_REF] === callback) { if (previousListener) { previousListener.next = nextListener; } else { // new first listener this._setListeners(event, nextListener); } } previousListener = listener; listener = nextListener; } }; /** * A event that is emitted via the event bus. */ function InternalEvent() {} InternalEvent.prototype.stopPropagation = function () { this.cancelBubble = true; }; InternalEvent.prototype.preventDefault = function () { this.defaultPrevented = true; }; InternalEvent.prototype.init = function (data) { assign$4(this, data || {}); }; /** * Invoke function. Be fast... * * @param {Function} fn * @param {any[]} args * * @return {any} */ function invokeFunction(fn, args) { return fn.apply(null, args); } /** * Flatten array, one level deep. * * @param {Array<?>} arr * * @return {Array<?>} */ var nativeToString$3 = Object.prototype.toString; function isString$3(obj) { return nativeToString$3.call(obj) === '[object String]'; } function _extends$3() { _extends$3 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends$3.apply(this, arguments); } /** * Convenience wrapper for `Object.assign`. * * @param {Object} target * @param {...Object} others * * @return {Object} the target */ function assign$3(target) { for (var _len = arguments.length, others = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { others[_key - 1] = arguments[_key]; } return _extends$3.apply(void 0, [target].concat(others)); } /** * Flatten array, one level deep. * * @param {Array<?>} arr * * @return {Array<?>} */ var nativeToString$2 = Object.prototype.toString; var nativeHasOwnProperty$2 = Object.prototype.hasOwnProperty; function isUndefined$4(obj) { return obj === undefined; } function isArray$5(obj) { return nativeToString$2.call(obj) === '[object Array]'; } function isObject$1(obj) { return nativeToString$2.call(obj) === '[object Object]'; } function isString$2(obj) { return nativeToString$2.call(obj) === '[object String]'; } /** * Return true, if target owns a property with the given key. * * @param {Object} target * @param {String} key * * @return {Boolean} */ function has$2(target, key) { return nativeHasOwnProperty$2.call(target, key); } /** * Iterate over collection; returning something * (non-undefined) will stop iteration. * * @param {Array|Object} collection * @param {Function} iterator * * @return {Object} return result that stopped the iteration */ function forEach$2(collection, iterator) { var val, result; if (isUndefined$4(collection)) { return; } var convertKey = isArray$5(collection) ? toNum$2 : identity$2; for (var key in collection) { if (has$2(collection, key)) { val = collection[key]; result = iterator(val, convertKey(key)); if (result === false) { return val; } } } } function identity$2(arg) { return arg; } function toNum$2(arg) { return Number(arg); } /** * Bind function against target <this>. * * @param {Function} fn * @param {Object} target * * @return {Function} bound function */ function bind$2(fn, target) { return fn.bind(target); } function _extends$2() { _extends$2 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends$2.apply(this, arguments); } /** * Convenience wrapper for `Object.assign`. * * @param {Object} target * @param {...Object} others * * @return {Object} the target */ function assign$2(target) { for (var _len = arguments.length, others = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { others[_key - 1] = arguments[_key]; } return _extends$2.apply(void 0, [target].concat(others)); } /** * Pick given properties from the target object. * * @param {Object} target * @param {Array} properties * * @return {Object} target */ function pick(target, properties) { var result = {}; var obj = Object(target); forEach$2(properties, function (prop) { if (prop in obj) { result[prop] = target[prop]; } }); return result; } /** * Moddle base element. */ function Base$1() {} Base$1.prototype.get = function (name) { return this.$model.properties.get(this, name); }; Base$1.prototype.set = function (name, value) { this.$model.properties.set(this, name, value); }; /** * A model element factory. * * @param {Moddle} model * @param {Properties} properties */ function Factory(model, properties) { this.model = model; this.properties = properties; } Factory.prototype.createType = function (descriptor) { var model = this.model; var props = this.properties, prototype = Object.create(Base$1.prototype); // initialize default values forEach$2(descriptor.properties, function (p) { if (!p.isMany && p.default !== undefined) { prototype[p.name] = p.default; } }); props.defineModel(prototype, model); props.defineDescriptor(prototype, descriptor); var name = descriptor.ns.name; /** * The new type constructor */ function ModdleElement(attrs) { props.define(this, '$type', { value: name, enumerable: true }); props.define(this, '$attrs', { value: {} }); props.define(this, '$parent', { writable: true }); forEach$2(attrs, bind$2(function (val, key) { this.set(key, val); }, this)); } ModdleElement.prototype = prototype; ModdleElement.hasType = prototype.$instanceOf = this.model.hasType; // static links props.defineModel(ModdleElement, model); props.defineDescriptor(ModdleElement, descriptor); return ModdleElement; }; /** * Built-in moddle types */ var BUILTINS = { String: true, Boolean: true, Integer: true, Real: true, Element: true }; /** * Converters for built in types from string representations */ var TYPE_CONVERTERS = { String: function (s) { return s; }, Boolean: function (s) { return s === 'true'; }, Integer: function (s) { return parseInt(s, 10); }, Real: function (s) { return parseFloat(s); } }; /** * Convert a type to its real representation */ function coerceType(type, value) { var converter = TYPE_CONVERTERS[type]; if (converter) { return converter(value); } else { return value; } } /** * Return whether the given type is built-in */ function isBuiltIn(type) { return !!BUILTINS[type]; } /** * Return whether the given type is simple */ function isSimple(type) { return !!TYPE_CONVERTERS[type]; } /** * Parses a namespaced attribute name of the form (ns:)localName to an object, * given a default prefix to assume in case no explicit namespace is given. * * @param {String} name * @param {String} [defaultPrefix] the default prefix to take, if none is present. * * @return {Object} the parsed name */ function parseName$1(name, defaultPrefix) { var parts = name.split(/:/), localName, prefix; // no prefix (i.e. only local name) if (parts.length === 1) { localName = name; prefix = defaultPrefix; } else // prefix + local name if (parts.length === 2) { localName = parts[1]; prefix = parts[0]; } else { throw new Error('expected <prefix:localName> or <localName>, got ' + name); } name = (prefix ? prefix + ':' : '') + localName; return { name: name, prefix: prefix, localName: localName }; } /** * A utility to build element descriptors. */ function DescriptorBuilder(nameNs) { this.ns = nameNs; this.name = nameNs.name; this.allTypes = []; this.allTypesByName = {}; this.properties = []; this.propertiesByName = {}; } DescriptorBuilder.prototype.build = function () { return pick(this, ['ns', 'name', 'allTypes', 'allTypesByName', 'properties', 'propertiesByName', 'bodyProperty', 'idProperty']); }; /** * Add property at given index. * * @param {Object} p * @param {Number} [idx] * @param {Boolean} [validate=true] */ DescriptorBuilder.prototype.addProperty = function (p, idx, validate) { if (typeof idx === 'boolean') { validate = idx; idx = undefined; } this.addNamedProperty(p, validate !== false); var properties = this.properties; if (idx !== undefined) { properties.splice(idx, 0, p); } else { properties.push(p); } }; DescriptorBuilder.prototype.replaceProperty = function (oldProperty, newProperty, replace) { var oldNameNs = oldProperty.ns; var props = this.properties, propertiesByName = this.propertiesByName, rename = oldProperty.name !== newProperty.name; if (oldProperty.isId) { if (!newProperty.isId) { throw new Error('property <' + newProperty.ns.name + '> must be id property ' + 'to refine <' + oldProperty.ns.name + '>'); } this.setIdProperty(newProperty, false); } if (oldProperty.isBody) { if (!newProperty.isBody) { throw new Error('property <' + newProperty.ns.name + '> must be body property ' + 'to refine <' + oldProperty.ns.name + '>'); } // TODO: Check compatibility this.setBodyProperty(newProperty, false); } // validate existence and get location of old property var idx = props.indexOf(oldProperty); if (idx === -1) { throw new Error('property <' + oldNameNs.name + '> not found in property list'); } // remove old property props.splice(idx, 1); // replacing the named property is intentional // // * validate only if this is a "rename" operation // * add at specific index unless we "replace" // this.addProperty(newProperty, replace ? undefined : idx, rename); // make new property available under old name propertiesByName[oldNameNs.name] = propertiesByName[oldNameNs.localName] = newProperty; }; DescriptorBuilder.prototype.redefineProperty = function (p, targetPropertyName, replace) { var nsPrefix = p.ns.prefix; var parts = targetPropertyName.split('#'); var name = parseName$1(parts[0], nsPrefix); var attrName = parseName$1(parts[1], name.prefix).name; var redefinedProperty = this.propertiesByName[attrName]; if (!redefinedProperty) { throw new Error('refined property <' + attrName + '> not found'); } else { this.replaceProperty(redefinedProperty, p, replace); } delete p.redefines; }; DescriptorBuilder.prototype.addNamedProperty = function (p, validate) { var ns = p.ns, propsByName = this.propertiesByName; if (validate) { this.assertNotDefined(p, ns.name); this.assertNotDefined(p, ns.localName); } propsByName[ns.name] = propsByName[ns.localName] = p; }; DescriptorBuilder.prototype.removeNamedProperty = function (p) { var ns = p.ns, propsByName = this.propertiesByName; delete propsByName[ns.name]; delete propsByName[ns.localName]; }; DescriptorBuilder.prototype.setBodyProperty = function (p, validate) { if (validate && this.bodyProperty) { throw new Error('body property defined multiple times ' + '(<' + this.bodyProperty.ns.name + '>, <' + p.ns.name + '>)'); } this.bodyProperty = p; }; DescriptorBuilder.prototype.setIdProperty = function (p, validate) { if (validate && this.idProperty) { throw new Error('id property defined multiple times ' + '(<' + this.idProperty.ns.name + '>, <' + p.ns.name + '>)'); } this.idProperty = p; }; DescriptorBuilder.prototype.assertNotDefined = function (p, name) { var propertyName = p.name, definedProperty = this.propertiesByName[propertyName]; if (definedProperty) { throw new Error('property <' + propertyName + '> already defined; ' + 'override of <' + definedProperty.definedBy.ns.name + '#' + definedProperty.ns.name + '> by ' + '<' + p.definedBy.ns.name + '#' + p.ns.name + '> not allowed without redefines'); } }; DescriptorBuilder.prototype.hasProperty = function (name) { return this.propertiesByName[name]; }; DescriptorBuilder.prototype.addTrait = function (t, inherited) { var typesByName = this.allTypesByName, types = this.allTypes; var typeName = t.name; if (typeName in typesByName) { return; } forEach$2(t.properties, bind$2(function (p) { // clone property to allow extensions p = assign$2({}, p, { name: p.ns.localName, inherited: inherited }); Object.defineProperty(p, 'definedBy', { value: t }); var replaces = p.replaces, redefines = p.redefines; // add replace/redefine support if (replaces || redefines) { this.redefineProperty(p, replaces || redefines, replaces); } else { if (p.isBody) { this.setBodyProperty(p); } if (p.isId) { this.setIdProperty(p); } this.addProperty(p); } }, this)); types.push(t); typesByName[typeName] = t; }; /** * A registry of Moddle packages. * * @param {Array<Package>} packages * @param {Properties} properties */ function Registry(packages, properties) { this.packageMap = {}; this.typeMap = {}; this.packages = []; this.properties = properties; forEach$2(packages, bind$2(this.registerPackage, this)); } Registry.prototype.getPackage = function (uriOrPrefix) { return this.packageMap[uriOrPrefix]; }; Registry.prototype.getPackages = function () { return this.packages; }; Registry.prototype.registerPackage = function (pkg) { // copy package pkg = assign$2({}, pkg); var pkgMap = this.packageMap; ensureAvailable(pkgMap, pkg, 'prefix'); ensureAvailable(pkgMap, pkg, 'uri'); // register types forEach$2(pkg.types, bind$2(function (descriptor) { this.registerType(descriptor, pkg); }, this)); pkgMap[pkg.uri] = pkgMap[pkg.prefix] = pkg; this.packages.push(pkg); }; /** * Register a type from a specific package with us */ Registry.prototype.registerType = function (type, pkg) { type = assign$2({}, type, { superClass: (type.superClass || []).slice(), extends: (type.extends || []).slice(), properties: (type.properties || []).slice(), meta: assign$2(type.meta || {}) }); var ns = parseName$1(type.name, pkg.prefix), name = ns.name, propertiesByName = {}; // parse properties forEach$2(type.properties, bind$2(function (p) { // namespace property names var propertyNs = parseName$1(p.name, ns.prefix), propertyName = propertyNs.name; // namespace property types if (!isBuiltIn(p.type)) { p.type = parseName$1(p.type, propertyNs.prefix).name; } assign$2(p, { ns: propertyNs, name: propertyName }); propertiesByName[propertyName] = p; }, this)); // update ns + name assign$2(type, { ns: ns, name: name, propertiesByName: propertiesByName }); forEach$2(type.extends, bind$2(function (extendsName) { var extended = this.typeMap[extendsName]; extended.traits = extended.traits || []; extended.traits.push(name); }, this)); // link to package this.definePackage(type, pkg); // register this.typeMap[name] = type; }; /** * Traverse the type hierarchy from bottom to top, * calling iterator with (type, inherited) for all elements in * the inheritance chain. * * @param {Object} nsName * @param {Function} iterator * @param {Boolean} [trait=false] */ Registry.prototype.mapTypes = function (nsName, iterator, trait) { var type = isBuiltIn(nsName.name) ? { name: nsName.name } : this.typeMap[nsName.name]; var self = this; /** * Traverse the selected trait. * * @param {String} cls */ function traverseTrait(cls) { return traverseSuper(cls, true); } /** * Traverse the selected super type or trait * * @param {String} cls * @param {Boolean} [trait=false] */ function traverseSuper(cls, trait) { var parentNs = parseName$1(cls, isBuiltIn(cls) ? '' : nsName.prefix); self.mapTypes(parentNs, iterator, trait); } if (!type) { throw new Error('unknown type <' + nsName.name + '>'); } forEach$2(type.superClass, trait ? traverseTrait : traverseSuper); // call iterator with (type, inherited=!trait) iterator(type, !trait); forEach$2(type.traits, traverseTrait); }; /** * Returns the effective descriptor for a type. * * @param {String} type the namespaced name (ns:localName) of the type * * @return {Descriptor} the resulting effective descriptor */ Registry.prototype.getEffectiveDescriptor = function (name) { var nsName = parseName$1(name); var builder = new DescriptorBuilder(nsName); this.mapTypes(nsName, function (type, inherited) { builder.addTrait(type, inherited); }); var descriptor = builder.build(); // define package link this.definePackage(descriptor, descriptor.allTypes[descriptor.allTypes.length - 1].$pkg); return descriptor; }; Registry.prototype.definePackage = function (target, pkg) { this.properties.define(target, '$pkg', { value: pkg }); }; ///////// helpers //////////////////////////// function ensureAvailable(packageMap, pkg, identifierKey) { var value = pkg[identifierKey]; if (value in packageMap) { throw new Error('package with ' + identifierKey + ' <' + value + '> already defined'); } } /** * A utility that gets and sets properties of model elements. * * @param {Model} model */ function Properties(model) { this.model = model; } /** * Sets a named property on the target element. * If the value is undefined, the property gets deleted. * * @param {Object} target * @param {String} name * @param {Object} value */ Properties.prototype.set = function (target, name, value) { if (!isString$2(name) || !name.length) { throw new TypeError('property name must be a non-empty string'); } var property = this.model.getPropertyDescriptor(target, name); var propertyName = property && property.name; if (isUndefined$3(value)) { // unset the property, if the specified value is undefined; // delete from $attrs (for extensions) or the target itself if (property) { delete target[propertyName]; } else { delete target.$attrs[name]; } } else { // set the property, defining well defined properties on the fly // or simply updating them in target.$attrs (for extensions) if (property) { if (propertyName in target) { target[propertyName] = value; } else { defineProperty$2(target, property, value); } } else { target.$attrs[name] = value; } } }; /** * Returns the named property of the given element * * @param {Object} target * @param {String} name * * @return {Object} */ Properties.prototype.get = function (target, name) { var property = this.model.getPropertyDescriptor(target, name); if (!property) { return target.$attrs[name]; } var propertyName = property.name; // check if access to collection property and lazily initialize it if (!target[propertyName] && property.isMany) { defineProperty$2(target, property, []); } return target[propertyName]; }; /** * Define a property on the target element * * @param {Object} target * @param {String} name * @param {Object} options */ Properties.prototype.define = function (target, name, options) { if (!options.writable) { var value = options.value; // use getters for read-only variables to support ES6 proxies // cf. https://github.com/bpmn-io/internal-docs/issues/386 options = assign$2({}, options, { get: function () { return value; } }); delete options.value; } Object.defineProperty(target, name, options); }; /** * Define the descriptor for an element */ Properties.prototype.defineDescriptor = function (target, descriptor) { this.define(target, '$descriptor', { value: descriptor }); }; /** * Define the model for an element */ Properties.prototype.defineModel = function (target, model) { this.define(target, '$model', { value: model }); }; function isUndefined$3(val) { return typeof val === 'undefined'; } function defineProperty$2(target, property, value) { Object.defineProperty(target, property.name, { enumerable: !property.isReference, writable: true, value: value, configurable: true }); } //// Moddle implementation ///////////////////////////////////////////////// /** * @class Moddle * * A model that can be used to create elements of a specific type. * * @example * * var Moddle = require('moddle'); * * var pkg = { * name: 'mypackage', * prefix: 'my', * types: [ * { name: 'Root' } * ] * }; * * var moddle = new Moddle([pkg]); * * @param {Array<Package>} packages the packages to contain */ function Moddle(packages) { this.properties = new Properties(this); this.factory = new Factory(this, this.properties); this.registry = new Registry(packages, this.properties); this.typeCache = {}; } /** * Create an instance of the specified type. * * @method Moddle#create * * @example * * var foo = moddle.create('my:Foo'); * var bar = moddle.create('my:Bar', { id: 'BAR_1' }); * * @param {String|Object} descriptor the type descriptor or name know to the model * @param {Object} attrs a number of attributes to initialize the model instance with * @return {Object} model instance */ Moddle.prototype.create = function (descriptor, attrs) { var Type = this.getType(descriptor); if (!Type) { throw new Error('unknown type <' + descriptor + '>'); } return new Type(attrs); }; /** * Returns the type representing a given descriptor * * @method Moddle#getType * * @example