UNPKG

flairjs

Version:

True Object Oriented JavaScript

1,147 lines (1,052 loc) 369 kB
/** * @preserve * Flair.js * True Object Oriented JavaScript * * Assembly: flair * File: ./flair.js * Version: 0.59.75 * Wed, 25 Sep 2019 04:56:26 GMT * * (c) 2017-2019 Vikas Burman * MIT */ (function(root, factory) { 'use strict'; if (typeof define === 'function' && define.amd) { // AMD support define(() => { return factory(); }); } else if (typeof exports === 'object') { // CommonJS and Node.js module support let fo = factory(); if (typeof module !== 'undefined' && module.exports) { exports = module.exports = fo; // Node.js specific `module.exports` } module.exports = exports = fo; // CommonJS } else { // expose as global on window root.flair = factory(); } })((this || globalThis), function() { 'use strict'; /* eslint-disable no-unused-vars */ // locals let isServer = new Function("try {return this===global;}catch(e){return false;}")(), isWorker = false, sym = [], symKey = 'FLAIR_SYMBOLS', symString = '', meta = Symbol('[meta]'), modulesRootFolder = 'modules', disposers = [], options = {}, flairTypes = ['class', 'enum', 'interface', 'mixin', 'struct'], flairInstances = ['instance', 'sinstance'], settings = {}, config = {}, envX = null, envProps = {}, isAppStarted = false; /* eslint-enable no-unused-vars */ // worker setting if (isServer) { try { let worker_threads = require('worker_threads'); isWorker = worker_threads.isMainThread; } catch (err) { // eslint-disable-line no-unused-vars isWorker = false; } } else { // client isWorker = (typeof WorkerGlobalScope !== 'undefined' ? true : false); } // flairapp bootstrapper let flair = async (arg1, arg2) => { let ADO = null, options = null; if (typeof arg1 === 'string') { // just the entry point is specified options = { main: arg1 }; } else if (arg1.main && arg1.module && arg1.engine) { // this is start options object options = arg1; } else { ADO = arg1; } if (options) { if (typeof arg2 === 'string') { options.config = arg2; } // config is also given if (!isAppStarted) { // boot isAppStarted = await flair.AppDomain.boot(options); } // return return flair.AppDomain.app(); } else if (ADO) { flair.AppDomain.registerAdo(ADO); } }; // read symbols from environment // symbols can be pass in variety of formats: // server: command line args (process.argv), environment variables (process.env.FLAIR_SYMBOLS) // worker-server: get whatever symbols collection server main thread had - passed as workerData.symbols // client: global variable (window.FLAIR_SYMBOLS) // worker-client: get whatever symbols collection client main thread had - set in WorkerGlobalScope if (isServer) { if (isWorker) { // from workerData.symbols let workerData = require('worker_threads').workerData; symString = workerData.symbols || ''; } else { // from process.argv let idx = process.argv.findIndex((item) => { return (item.startsWith(`--${symKey}`) ? true : false); }); if (idx !== -1) { symString = process.argv[idx].substr(2).split('=')[1]; } // from process.env if (process.env[symKey]) { // add to list if (symString) { symString += ','; } symString += process.env[symKey]; } } } else { // client if (isWorker) { symString = WorkerGlobalScope[symKey] || ''; } else { // from window symString += window[symKey] || ''; } } if (symString) { sym = symString.split(',').map(item => item.trim()); } // options.symbols = Object.freeze(sym); options.env = Object.freeze({ type: (isServer ? 'server' : 'client'), isServer: isServer, isClient: !isServer, isWorker : isWorker, isMain: !isWorker, cores: ((isServer ? (require('os').cpus().length) : window.navigator.hardwareConcurrency) || 4), isCordova: (!isServer && !!window.cordova), isNodeWebkit: (isServer && process.versions['node-webkit']), isProd: ((sym.indexOf('PROD') !== -1 || sym.indexOf('STAGE') !== -1) && sym.indexOf('DEV') === -1), isStage: (sym.indexOf('STAGE') !== -1 && sym.indexOf('DEV') === -1), isDev: (sym.indexOf('DEV') !== -1), isLocal: ((isServer ? require('os').hostname() : self.location.host).indexOf('local') !== -1), isDebug: (sym.indexOf('DEBUG') !== -1), isTest: (sym.indexOf('TEST') !== -1), isAppMode: () => { return isAppStarted; }, x: (once) => { if (!envX && once) { envX = Object.freeze(once); } // set once - extra env properties are added here during runtime, generally via reading from a config file - once return envX || {}; }, props: (ns, key, value) => { if (typeof value === 'undefined') { if (typeof key === 'undefined') { return envProps[ns] || {}; } else { return (envProps[ns] ? envProps[ns][key] : null); } } else { envProps[ns] = envProps[ns] || {}; if (value === null) { delete envProps[ns][key]; } else { envProps[ns][key] = value; } } } }); // Prod / Stage vs Dev are mutually exclusive environments // Prod is set to true when either PROD or STAGE or both are present and DEV is not present // Stage is true only when STAGE is present and DEV is not present // Dev is true only when DEV is present even if PROD / STAGE is also present // Local, Debug and Test can be true in any of these environments // flair flair.members = []; flair.options = Object.freeze(options); flair.env = flair.options.env; // direct env access as well const a2f = (name, obj, disposer) => { flair[name] = Object.freeze(obj); flair.members.push(name); if (typeof disposer === 'function') { disposers.push(disposer); } }; // members /** * @name noop * @description No Operation function * @example * noop() * @params * @returns */ const _noop = () => {}; // attach to flair a2f('noop', _noop); /** * @name nip * @description Not Implemented Property * @example * nip() * @params * @returns */ const _nip = { get: () => { throw _Exception.NotImplemented('prop', _nip.get); }, set: () => { throw _Exception.NotImplemented('prop', _nip.set); } }; _nip.ni = true; // a special flag to quick check that this is a not-implemented object // attach to flair a2f('nip', _nip); /** * @name nim * @description Not Implemented Method * @example * nim() * @params * @returns */ const _nim = () => { throw _Exception.NotImplemented('func', _nim); }; _nim.ni = true; // a special flag to quick check that this is a not-implemented object // attach to flair a2f('nim', _nim); /** * @name Exception * @description Lightweight Exception class that extends Error object and serves as base of all exceptions * @example * Exception() * Exception(type) * Exception(type, stStart) * Exception(error) * Exception(error, stStart) * Exception(type, message) * Exception(type, message, stStart) * Exception(type, error) * Exception(type, error, stStart) * Exception(type, message, error) * Exception(type, message, error, stStart) * @params * type: string - error name or type * message: string - error message * error: object - inner error or exception object * stStart: function - hide stack trace before this function * @constructs Exception object */ const _Exception = function(arg1, arg2, arg3, arg4) { let _this = new Error(), stStart = _Exception; switch(typeof arg1) { case 'string': _this.name = arg1; switch(typeof arg2) { case 'string': _this.message = arg2; switch(typeof arg3) { case 'object': _this.error = arg3; if (typeof arg4 === 'function') { stStart = arg4; } break; case 'function': stStart = arg3; break; } break; case 'object': _this.message = arg2.message || ''; _this.error = arg2; if (typeof arg3 === 'function') { stStart = arg3; } break; case 'function': stStart = arg2; break; } break; case 'object': _this.name = arg1.name || 'Unknown'; _this.message = arg1.message || ''; _this.error = arg1; if (typeof arg2 === 'function') { stStart = arg2; } break; } _this.name = _this.name || 'Undefined'; if (!_this.name.endsWith('Exception')) { _this.name += 'Exception'; } // limit stacktrace if (typeof Error.captureStackTrace === 'function') { Error.captureStackTrace(_this, stStart); } // add hint of error if (_this.error) { _this.message += '[' + _this.error + ']'; } // return return Object.freeze(_this); }; // all inbuilt exceptions _Exception.OperationFailed = (name, error, stStart = _Exception.OperationFailed) => { return new _Exception('OperationFailed', `Operation failed with error. (${name})`, error, stStart); } _Exception.OperationConflict = (name, stStart = _Exception.OperationConflict) => { return new _Exception('OperationConflict', `Operation failed with conflict. (${name})`, stStart); } _Exception.Unauthorized = (name, stStart = _Exception.Unauthorized) => { return new _Exception('Unauthorized', `Access is not authorized. (${name})`, stStart); } _Exception.Circular = (name, stStart = _Exception.Circular) => { return new _Exception('Circular', `Circular calls found. (${name})`, stStart); } _Exception.Continue = (name, stStart = _Exception.Continue) => { return new _Exception('Continue', `Continue requested. (${name})`, stStart); } _Exception.Redirect = (name, stStart = _Exception.Continue) => { return new _Exception('Redirect', `Redirect requested. (${name})`, stStart); } _Exception.Duplicate = (name, stStart = _Exception.Duplicate) => { return new _Exception('Duplicate', `Item already exists. (${name})`, stStart); } _Exception.NotFound = (name, stStart = _Exception.NotFound) => { return new _Exception('NotFound', `Item not found. (${name})`, stStart); } _Exception.InvalidArgument = (name, stStart = _Exception.InvalidArgument) => { return new _Exception('InvalidArgument', `Argument type is invalid. (${name})`, stStart); } _Exception.InvalidDefinition = (name, stStart = _Exception.InvalidDefinition) => { return new _Exception('InvalidDefinition', `Item definition is invalid. (${name})`, stStart); } _Exception.InvalidOperation = (name, stStart = _Exception.InvalidOperation) => { return new _Exception('InvalidOperation', `Operation is invalid in current context. (${name})`, stStart); } _Exception.NotImplemented = (name, stStart = _Exception.NotImplemented) => { return new _Exception('NotImplemented', `Member is not implemented. (${name})`, stStart); } _Exception.NotDefined = (name, stStart = _Exception.NotDefined) => { return new _Exception('NotDefined', `Member is not defined or is not accessible. (${name})`, stStart); } _Exception.NotAvailable = (name, stStart = _Exception.NotAvailable) => { return new _Exception('NotAvailable', `Feature is not available. (${name})`, stStart); } _Exception.NotSupported = (name, stStart = _Exception.NotSupported) => { return new _Exception('NotSupported', `Operation is not supported. (${name})`, stStart); } _Exception.NotAllowed = (name, stStart = _Exception.NotAllowed) => { return new _Exception('NotAllowed', `Operation is not allowed. (${name})`, stStart); } _Exception.NoContent = (name, stStart = _Exception.NoContent) => { return new _Exception('NoContent', `No content to return. (${name})`, stStart); } _Exception.NotModified = (name, stStart = _Exception.NotModified) => { return new _Exception('NotModified', `Content is not changed. (${name})`, stStart); } // attach to flair a2f('Exception', _Exception); const guid = () => { return '_xxxxxxxx_xxxx_4xxx_yxxx_xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); }; const which = (def) => { // full blown def can be: // envProp::mainThreadOnServer{.min}.xyz ~ envProp::workerThreadOnServer{.min}.xyz | envProp::mainThreadOnClient{.min}.xyz ~ envProp::workerThreadOnClient{.min}.xyz let item = def, items = null, envProp = null; if (item.indexOf('|') !== -1) { // server | client items = item.split('|'); if (options.env.isServer) { // left is server item = items[0].trim(); } else { // right is client item = items[1].trim(); } if (item === 'x') { item = ''; } // special case to explicitly mark absence of a type } // worker environment specific pick if (item.indexOf('~') !== -1) { // main thread ~ worker thread items = item.split('~'); if (!options.env.isWorker) { // left is main thread item = items[0].trim(); } else { // right is worker thread item = items[1].trim(); } if (item === 'x') { item = ''; } // special case to explicitly mark absence of a type } // environment specific condition if (item.indexOf('::') !== -1) { // isVue::./flair.ui.vue{.min}.js items = item.split('::'), envProp = items[0].trim(); item = items[1].trim(); if (!(options.env[envProp] || options.env.x()[envProp])) { // if envProp is NOT defined neither at root env nor at extended env, OR defined but is false / falsy item = ''; // special case to dynamically mark absence of a type } } // debug/prod specific pick if (item.indexOf('{.min}') !== -1) { if (options.env.isDebug) { item = item.replace('{.min}', ''); // a{.min}.js => a.js } else { item = item.replace('{.min}', '.min'); // a{.min}.js => a.min.js } } return item; // modified or as is or empty }; const isArrow = (fn) => { return (!(fn).hasOwnProperty('prototype') && fn.constructor.name === 'Function'); }; const isASync = (fn) => { return (fn.constructor.name === 'AsyncFunction'); }; const findIndexByProp = (arr, propName, propValue) => { return arr.findIndex((item) => { return (item[propName] === propValue ? true : false); }); }; const findItemByProp = (arr, propName, propValue) => { let idx = arr.findIndex((item) => { return (item[propName] === propValue ? true : false); }); if (idx !== -1) { return arr[idx]; } return null; }; const splitAndTrim = (str, splitChar) => { if (!splitChar) { splitChar = ','; } return str.split(splitChar).map((item) => { return item.trim(); }); }; const escapeRegExp = (string) => { return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // eslint-disable-line no-useless-escape }; const replaceAll = (string, find, replace) => { return string.replace(new RegExp(escapeRegExp(find), 'g'), replace); }; const stuff = (str, args) => { if (typeof str === 'string' && Array.isArray(args) && args.length > 0) { let idx = 0; for(let arg of args) { str = replaceAll(str, `%${++idx}`, arg); } } return str; }; const shallowCopy = (target, source, overwrite, except) => { if (!except) { except = []; } for(let item in source) { if (source.hasOwnProperty(item) && except.indexOf(item) === -1) { if (!overwrite) { if (item in target) { continue; }} target[item] = source[item]; } } return target; }; const loadFile = async (file) => { // text based file loading operation - not a general purpose fetch of any url (it assumes it is a phycical file) let loader = null; if (isServer) { loader = _Port('serverFile'); } else { // client loader = _Port('clientFile'); } return await loader(file); }; const loadModule = async (module, globalObjName, isDelete) => { if (isServer) { return await _Port('serverModule').require(module); } else { // client let obj = await _Port('clientModule').require(module); if (!obj && typeof globalObjName === 'string') { if (isWorker) { obj = WorkerGlobalScope[globalObjName] || null; if (isDelete) { delete WorkerGlobalScope[globalObjName]; } } else { obj = window[globalObjName] || null; if (isDelete) { delete window[globalObjName]; } } } if (obj) { return obj; } } }; const lens = (obj, path) => path.split(".").reduce((o, key) => o && o[key] ? o[key] : null, obj); const globalSetting = (path, defaultValue, globalRoot = 'global') => { // any global setting (i.e., outside of a specific assembly setting) can be defined at: // "global" root node (or anything else that is given) in appConfig/webConfig file // Each setting can be at any depth inside "global" (or anything else given) and its generally a good idea to namespace intelligently to // avoid picking someone else' setting let _globalSettings = _AppDomain.config() ? (_AppDomain.config()[globalRoot] || {}) : {}; return lens(_globalSettings, path) || defaultValue; }; const sieve = (obj, props, isFreeze, add) => { let _props = props ? splitAndTrim(props) : Object.keys(obj); // if props are not give, pick all const extract = (_obj) => { let result = {}; if (_props.length > 0) { // copy defined for(let prop of _props) { result[prop] = _obj[prop]; } } else { // copy all for(let prop in obj) { if (obj.hasOwnProperty(prop)) { result[prop] = obj[prop]; } } } if (add) { for(let prop in add) { result[prop] = add[prop]; } } if (isFreeze) { result = Object.freeze(result); } return result; }; if (Array.isArray(obj)) { let result = []; for(let item of obj) { result.push(extract(item)); } return result; } else { return extract(obj); } }; const b64EncodeUnicode = (str) => { // eslint-disable-line no-unused-vars // first we use encodeURIComponent to get percent-encoded UTF-8, // then we convert the percent encodings into raw bytes which // can be fed into btoa. return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function toSolidBytes(match, p1) { return String.fromCharCode('0x' + p1); })); }; const b64DecodeUnicode = (str) => { // Going backwards: from bytestream, to percent-encoding, to original string. return decodeURIComponent(atob(str).split('').map(function(c) { return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); }).join('')); }; const uncacheModule = (module) => { if (isServer) { _Port('serverModule').undef(module); } else { _Port('clientModule').undef(module); } }; const deepMerge = (objects, isMergeArray = true) => { // credit: https://stackoverflow.com/a/48218209 const isObject = obj => obj && typeof obj === 'object'; return objects.reduce((prev, obj) => { Object.keys(obj).forEach(key => { const pVal = prev[key]; const oVal = obj[key]; if (Array.isArray(pVal) && Array.isArray(oVal)) { if (isMergeArray) { prev[key] = pVal.concat(...oVal); // merge array } else { prev[key] = [].concat(...oVal); // overwrite as new array } } else if (isObject(pVal) && isObject(oVal)) { prev[key] = deepMerge([pVal, oVal], isMergeArray); } else { prev[key] = oVal; } }); return prev; }, {}); }; const getLoadedScript = (...scriptNames) => { if (isServer || isWorker) { return ''; } let scriptFile = '', baseUri = '', el = null; for(let scriptName of scriptNames) { for(let script of window.document.scripts) { if (script.src.endsWith(scriptName)) { el = window.document.createElement('a'); el.href = script.src; baseUri = el.protocol + '//' + el.host + '/'; el = null; scriptFile = './' + script.src.replace(baseUri, ''); break; } } if (scriptFile) { break; } } return scriptFile; }; /** * @name typeOf * @description Finds the type of given object in flair type system * @example * typeOf(obj) * @params * obj: object - object that needs to be checked * @returns string - type of the given object * it can be following: * > special ones like 'undefined', 'null', 'NaN', infinity * > special javascript data types like 'array', 'date', etc. * > inbuilt flair object types like 'class', 'struct', 'enum', etc. * > native regular javascript data types like 'string', 'number', 'function', 'symbol', etc. */ const _typeOf = (obj) => { let _type = ''; // undefined if (typeof obj === 'undefined') { _type = 'undefined'; } // null if (!_type && obj === null) { _type = 'null'; } // infinity if (!_type && typeof obj === 'number' && isFinite(obj) === false) { _type = 'infinity'; } // array if (!_type && Array.isArray(obj)) { _type = 'array'; } // date if (!_type && (obj instanceof Date)) { _type = 'date'; } // flair types if (!_type && obj[meta]) { _type = obj[meta].type; } // native javascript types if (!_type) { _type = typeof obj; } // return return _type; }; // attach to flair a2f('typeOf', _typeOf); /** * @name is * @description Checks if given object is of a given type * @example * is(obj, type) * @params * obj: object - object that needs to be checked * type: string OR type - type to be checked for, it can be following: * > expected native javascript data types like 'string', 'number', 'function', 'array', 'date', etc. * > 'function' - any function, cfunction' - constructor function and 'afunction - arrow function * > any 'flair' object or type, 'flairtype' - only flair types and 'flairinstance' - only flair instances * > inbuilt flair object types like 'class', 'struct', 'enum', etc. * > custom flair object instance types which are checked in following order: * >> for class instances: * isInstanceOf given as type * isImplements given as interface * isMixed given as mixin * >> for struct instances: * isInstance of given as struct type * @returns boolean - true/false */ const _is = (obj, type) => { // NOTE: in all 'check' type functions, Args() is not to be used, as Args use them itself // obj may be undefined or null or false, so don't check for validation of that here if (type[meta]) { type = type[meta].name || type[meta].Type.getName(); } // since it can be a type as well if (_typeOf(type) !== 'string') { throw _Exception.InvalidArgument('type', _is); } let isMatched = false; if (obj) { switch(type) { case 'NaN': isMatched = isNaN(obj); break; case 'infinity': isMatched = (typeof obj === 'number' && isFinite(obj) === false); break; case 'array': case 'Array': isMatched = Array.isArray(obj); break; case 'date': case 'Date': isMatched = (obj instanceof Date); break; case 'flairtype': isMatched = (obj[meta] && flairTypes.indexOf(obj[meta].type) !== -1); break; case 'flairinstance': isMatched = (obj[meta] && flairInstances.indexOf(obj[meta].type) !== -1); break; case 'flair': // presence ot meta symbol means it is flair type/instance isMatched = typeof obj[meta] !== 'undefined'; break; case 'cfunction': isMatched = (typeof obj === 'function' && !isArrow(obj)); break; case 'afunction': isMatched = (typeof obj === 'function' && isArrow(obj)); break; default: // native javascript types (including simple 'function') if (!isMatched) { isMatched = (typeof obj === type); } if (!isMatched && obj[meta]) { // flair types if (!isMatched) { isMatched = (type === obj[meta].type); } // flair instance check (instance) if (!isMatched && flairInstances.indexOf(obj[meta].type) !== -1) { isMatched = _isInstanceOf(obj, type); } // flair type check (derived from) if (!isMatched && obj[meta].type === 'class') { isMatched = _isDerivedFrom(obj, type); } // flair type check (direct name) if (!isMatched && flairTypes.indexOf(obj[meta].type) !== -1) { isMatched = (obj[meta].name === type); } } } } else { switch(type) { case 'undefined': isMatched = (typeof obj === 'undefined'); break; case 'null': isMatched = (obj === null); break; case 'NaN': isMatched = isNaN(obj); break; } } // return return isMatched; }; // attach to flair a2f('is', _is); /** * @name is * @description Checks if given object has specified member defined * @example * isDefined(obj, memberName) * @params * obj: object - object that needs to be checked * memberName: string - name of the member to check * @returns boolean - true/false */ const _isDefined = (obj, memberName) => { // NOTE: in all 'check' type functions, Args() is not to be used, as Args use them itself let isErrorOccured = false; try { obj[memberName]; // try to access it, it will throw error if not defined on an object which is a flair-object // if error does not occur above, means either member is defined or it was not a flairjs object, in that case check for 'undefined' isErrorOccured = (typeof obj[memberName] === 'undefined'); } catch (err) { isErrorOccured = true; } // return return !isErrorOccured; }; // attach to flair a2f('isDefined', _isDefined); /** * @name Args * @description Lightweight args pattern processing that returns a validator function to validate arguments against given arg patterns * @example * Args(...patterns) * @params * patterns: string(s) - multiple pattern strings, each representing one pattern set * each pattern set can take following forms: * 'type, type, type, ...' OR 'name: type, name: type, name: type, ...' * type: can be following: * > special types: 'undefined' - for absence of a passed value * > expected native javascript data types like 'string', 'number', 'function', 'array', etc. * > 'function' - any function, cfunction' - constructor function and 'afunction - arrow function * > inbuilt flair object types like 'class', 'struct', 'enum', etc. * > custom flair object instance types which are checked in following order: * >> for class instances: * isInstanceOf given as type * isImplements given as interface * isMixed given as mixin * >> for struct instances: * isInstance of given as struct type * name: argument name which will be used to store extracted value by parser * @returns function - validator function that is configured for specified patterns */ const _Args = (...patterns) => { if (patterns.length === 0) { throw _Exception.InvalidArgument('patterns', _Args); } /** * @description Args validator function that validates against given patterns * @example * (...args) * @params * args: any - multiple arguments to match against given pattern sets * @returns object - result object, having: * raw: (array) - original arguments as passed * index: (number) - index of pattern-set that matches for given arguments, -1 if no match found * if more than one patterns may match, it will stop at first match * types: (string) - types which matched - e.g., string_string * this is the '_' joint string of all type definition part from the matching pattern-set * isInvalid: (boolean) - to check if any match could not be achieved * <name(s)>: <value(s)> - argument name as given in pattern having corresponding argument value * if a name was not given in pattern, a default unique name will be created * special names like 'raw', 'index' and 'isInvalid' cannot be used. */ let _args = (...args) => { // process each pattern - exit with first matching pattern let types = null, items = null, name = '', type = '', pIndex = -1, aIndex = -1, // pattern index, argument index matched = false, mCount = 0, // matched arguments count of pattern faliedMatch = '', result = { raw: args || [], index: -1, isInvalid: false, error: null, values: {} }; if (patterns) { for(let pattern of patterns) { // pattern pIndex++; aIndex=-1; matched = false; mCount = 0; types = pattern.split(','); for(let item of types) { aIndex++; items = item.split(':'); if (items.length !== 2) { name = `_${pIndex}_${aIndex}`; // e.g., _0_0 or _1_2, etc. type = item.trim() || ''; } else { name = items[0].trim() || '', type = items[1].trim() || ''; } if (aIndex > result.raw.length) { matched = false; break; } if (!_is(result.raw[aIndex], type)) { matched = false; faliedMatch = name; break; } result.values[name] = result.raw[aIndex]; matched = true; mCount++; } if (matched && mCount === types.length) {result.index = pIndex; break; } } } // set state result.isInvalid = (result.index === -1 ? true : false); result.error = (result.isInvalid ? _Exception.InvalidArgument(faliedMatch) : null); // throw helper result.throwOnError = (stStart) => { if (result.error) { throw new _Exception(result.error, stStart || _args); } }; // return return Object.freeze(result); }; // return freezed return Object.freeze(_args); }; // attach to flair a2f('Args', _Args); /** * @name event * @description Event marker * @example * event() * @params * argsProcessor - args processor function, if args to be processed before event is raised * @returns * function - returns given function or a noop function as is with an event marked tag */ const _event = (argsProcessor) => { let args = _Args('argsProcessor: undefined', 'argsProcessor: afunction')(argsProcessor); args.throwOnError(_event); argsProcessor = argsProcessor || ((...eventArgs) => { return eventArgs; }); argsProcessor.event = true; // attach tag return argsProcessor; } // attach to flair a2f('event', _event); /** * @name nie * @description Not Implemented Event * @example * nie() * @params * @returns */ const _nie = _event(() => { throw _Exception.NotImplemented('event', _nie); }); _nie.ni = true; // a special flag to quick check that this is a not-implemented object // attach to flair a2f('nie', _nie); /** * @name Dispatcher * @description Event dispatching. */ const Dispatcher = function(eventHost) { let events = {}; eventHost = eventHost || ''; // add event listener this.add = (event, handler) => { let args = _Args('event: string, handler: afunction')(event, handler); args.throwOnError(this.add); if (!events[event]) { events[event] = []; } events[event].push(handler); }; // remove event listener this.remove = (event, handler) => { let args = _Args('event: string, handler: afunction')(event, handler); args.throwOnError(this.remove); if (events[event]) { let idx = events[event].indexOf(handler); if (idx !== -1) { events[event].splice(idx, 1); } } }; // dispatch event this.dispatch = (event, eventArgs) => { let args = _Args('event: string')(event); args.throwOnError(this.dispatch); // note: no check for eventArgs, as it can be anything if (events[event]) { events[event].forEach(handler => { // NOTE: any change here should also be done in SharedChannel where progress event is being routed across threads setTimeout(() => { handler(Object.freeze({ host: eventHost, name: event, args: eventArgs || [] })); }, 0); // <-- event handler will receive this }); } }; // get number of attached listeners this.count = (event) => { let args = _Args('event: string')(event); args.throwOnError(this.count); return (events[event] ? events[event].length : 0); }; // clear all handlers for all events associated with this dispatcher this.clear = () => { events = {}; }; }; /** * @name InjectedArg * @description An argument that is injected by a custom attribute OR an advise * @example * InjectedArg(value); * @params * @returns */ const InjectedArg = function(value) { this.value = value; }; InjectedArg.filter = (args) => { // return all plain args, leaving all injected args let filteredArgs = []; if (args) { for(let a of args) { if (!(a instanceof InjectedArg)) { filteredArgs.push(a); } } } return filteredArgs; }; InjectedArg.extract = (args) => { // return all raw injected args, in reverse order let injectedArgs = []; if (args) { for(let a of args) { if (a instanceof InjectedArg) { injectedArgs.push(a.value); } } } return injectedArgs.reverse(); }; // attach to flair a2f('InjectedArg', Object.freeze(InjectedArg)); /** * @name Port * @description Customize configurable functionality of the core. This gives a way to configure a different component to * handle some specific functionalities of the core, e.g., fetching a file on server, or loading a module on * client, or handling sessionStorage, to name a few. * Ports are defined by a component and handlers of required interface types can be supplied from outside * as per usage requirements * @example * Port(name) // @returns handler/null - if connected returns handler else null * Port.define(name, members, intf) // @returns void * Port.connect(name, handler) // @returns void * Port.disconnect(name) // @returns void * Port.disconnect.all() // @returns void * Port.isDefined(name) // @returns boolean - true/false * Port.isConnected(name) // @returns boolean - true/false * @params * name: string - name of the port * members: array of strings - having member names that are checked for their presence * handler: function - a factory that return the actual handler to provide named functionality for current environment * inbuilt: function - an inbuilt factory implementation of the port functionality, if nothing is configured, this implementation will be returned * NOTE: Both handler and inbuilt are passed flair.options.env object to return most suited implementation of the port * @returns handler/boolean/void - as specified above */ let ports_registry = {}; const _Port = (name) => { if (typeof name !== 'string') { throw _Exception.InvalidArgument('name', _Port); } if (ports_registry[name]) { return (ports_registry[name].handler ? ports_registry[name].handler : ports_registry[name].inbuilt); // inbuilt could also be null if not inbuilt implementation is given } return null; }; _Port.define = (name, members, inbuilt) => { let args = _Args('name: string, members: array, inbuilt: afunction', 'name: string, inbuilt: afunction', 'name: string, members: array', 'name: string')(name, members, inbuilt); args.throwOnError(_Port.define); if (ports_registry[name]) { throw _Exception.Duplicate(name, _Port.define); } ports_registry[name] = { type: (args.values.members ? 'object' : 'function'), // a port handler can be members: args.values.members || null, handler: null, inbuilt: (args.values.inbuilt ? args.values.inbuilt(options.env) : null) }; }; _Port.connect = (name, handler) => { let args = _Args('name: string, handler: afunction')(name, handler); args.throwOnError(_Port.connect); if (!ports_registry[name]) { throw _Exception.NotFound(name, _Port.connect); } let actualHandler = handler(options.env); // let it return handler as per context if (typeof actualHandler !== ports_registry[name].type) { throw _Exception.InvalidArgument('handler', _Port.connect); } let members = ports_registry[name].members; if (members) { for(let member of members) { if (typeof actualHandler[member] === 'undefined') { throw _Exception.NotImplemented(member, _Port.connect); } } } ports_registry[name].handler = actualHandler; }; _Port.disconnect = (name) => { if (typeof name !== 'string') { throw _Exception.InvalidArgument('name', _Port.disconnect); } if (ports_registry[name]) { ports_registry[name].handler = null; } }; _Port.isDefined = (name) => { if (typeof name !== 'string') { throw _Exception.InvalidArgument('name', _Port.isDefined); } return (ports_registry[name] ? true : false); }; _Port.isConnected = (name) => { if (typeof name !== 'string') { throw _Exception.InvalidArgument('name', _Port.isConnected); } return (ports_registry[name] && ports_registry[name].handler ? false : true); }; // attach to flair a2f('Port', _Port, () => { // disconnect all ports for(let port in ports_registry) { if (ports_registry.hasOwnProperty(port)) { ports_registry[port].handler = null; } } // clear registry ports_registry = {}; }); /** * @name AssemblyLoadContext * @description The isolation boundary of type loading across assemblies. */ const AssemblyLoadContext = function(name, domain, defaultLoadContext, currentContexts, contexts) { let alcTypes = {}, alcResources = {}, alcRoutes = {}, instances = {}, asmFiles = {}, asmNames = {}, namespaces = {}, isUnloaded = false, currentAssembliesBeingLoaded = [], onLoadCompleteFunctions = {}; // context this.name = name; this.domain = domain; this.isUnloaded = () => { return isUnloaded || domain.isUnloaded(); }; this.unload = () => { if (!isUnloaded) { // mark unloaded isUnloaded = true; // delete from domain registry delete contexts[name]; // dispose all active instances for(let instance in instances) { if (instance.hasOwnProperty(instance)) { _dispose(instances[instance]); } } // clear registries alcTypes = {}; asmFiles = {}; asmNames = {}; alcResources = {}; alcRoutes = {}; instances = {}; namespaces = {}; } }; this.current = () => { if (this.isUnloaded()) { throw _Exception.InvalidOperation(`Context is already unloaded. (${this.name})`, this.current); } if (currentContexts.length === 0) { return defaultLoadContext || this; // the first content created is the default context, so in first case, it will come as null, hence return this } else { // return last added context // when a context load any assembly, it pushes itself to this list, so // that context become current context and all loading types will attach itself to this // new context, and as soon as load is completed, it removes itself. // Now, if for some reason, an assembly load operation itself (via some code in index.js) // initiate another context load operation, that will push itself on top of this context and // it will trickle back to this context when that secondary load is done // so always return from here the last added context in list return currentContexts[currentContexts.length - 1]; } }; // types this.registerType = (Type) => { if (this.isUnloaded()) { throw _Exception.InvalidOperation(`Context is already unloaded. (${this.name})`, this.registerType); } // certain types are built as instances, like interface and enum let name = '', type = '', typeMeta = Type[meta]; if (typeMeta.Type) { name = typeMeta.Type[meta].name; type = typeMeta.Type[meta].type; } else { name = typeMeta.name; type = typeMeta.type; } // only valid types are allowed if (flairTypes.indexOf(type) === -1) { throw _Exception.InvalidArgument('Type', this.registerType); } // namespace name is already attached to it, and for all '(root)' // marked types' no namespace is added, so it will automatically go to root let ns = name.substr(0, name.lastIndexOf('.')), onlyName = name.replace(ns + '.', ''); // check if already registered if (alcTypes[name]) { throw _Exception.Duplicate(name, this.registerType); } if (alcResources[name]) { throw _Exception.Duplicate(`Already registered as Resource. (${name})`, this.registerType); } if (alcRoutes[name]) { throw _Exception.Duplicate(`Already registered as Route. (${name})`, this.registerType); } // register alcTypes[name] = Type; // register to namespace as well if (ns) { if (!namespaces[ns]) { namespaces[ns] = {}; } namespaces[ns][onlyName] = Type; } else { // root namespaces[onlyName] = Type; } // return namespace where it gets registered return ns; }; this.getType = (qualifiedName) => { if (this.isUnloaded()) { throw _Exception.InvalidOperation(`Context is already unloaded. (${this.name})`, this.getType); } if (typeof qualifiedName !== 'string') { throw _Exception.InvalidArgument('qualifiedName', this.getType); } return alcTypes[qualifiedName] || null; }; this.ensureType = async (qualifiedName) => { if (this.isUnloaded()) { throw _Exception.InvalidOperation(`Context is already unloaded. (${this.name})`); } if (typeof qualifiedName !== 'string') { throw _Exception.InvalidArgument('qualifiedName'); } let Type = this.getType(qualifiedName); if (!Type) { let asmFile = domain.resolve(qualifiedName); if (asmFile) { await this.loadAssembly(asmFile); Type = this.getType(qualifiedName); if (!Type) { throw _Exception.OperationFailed(`Assembly could not be loaded. (${asmFile})`); } } else { throw _Exception.NotFound(qualifiedName); } } return Type; }; this.allTypes = () => { if (this.isUnloaded()) { throw _Exception.InvalidOperation(`Context is already unloaded. (${this.name})`, this.allTypes); } return Object.keys(alcTypes); }; this.execute = async (info, progressListener) => { // NOTE: The logi