UNPKG

y-protocols

Version:
1,966 lines (1,776 loc) 293 kB
(function () { 'use strict'; /** * Utility module to work with key-value stores. * * @module map */ /** * Creates a new Map instance. * * @function * @return {Map<any, any>} * * @function */ const create$6 = () => new Map(); /** * Copy a Map object into a fresh Map object. * * @function * @template X,Y * @param {Map<X,Y>} m * @return {Map<X,Y>} */ const copy = m => { const r = create$6(); m.forEach((v, k) => { r.set(k, v); }); return r }; /** * Get map property. Create T if property is undefined and set T on map. * * ```js * const listeners = map.setIfUndefined(events, 'eventName', set.create) * listeners.add(listener) * ``` * * @function * @template V,K * @template {Map<K,V>} MAP * @param {MAP} map * @param {K} key * @param {function():V} createT * @return {V} */ const setIfUndefined = (map, key, createT) => { let set = map.get(key); if (set === undefined) { map.set(key, set = createT()); } return set }; /** * Creates an Array and populates it with the content of all key-value pairs using the `f(value, key)` function. * * @function * @template K * @template V * @template R * @param {Map<K,V>} m * @param {function(V,K):R} f * @return {Array<R>} */ const map$1 = (m, f) => { const res = []; for (const [key, value] of m) { res.push(f(value, key)); } return res }; /** * Tests whether any key-value pairs pass the test implemented by `f(value, key)`. * * @todo should rename to some - similarly to Array.some * * @function * @template K * @template V * @param {Map<K,V>} m * @param {function(V,K):boolean} f * @return {boolean} */ const any = (m, f) => { for (const [key, value] of m) { if (f(value, key)) { return true } } return false }; /** * Utility module to work with sets. * * @module set */ const create$5 = () => new Set(); /** * Utility module to work with Arrays. * * @module array */ /** * Return the last element of an array. The element must exist * * @template L * @param {ArrayLike<L>} arr * @return {L} */ const last = arr => arr[arr.length - 1]; /** * Transforms something array-like to an actual Array. * * @function * @template T * @param {ArrayLike<T>|Iterable<T>} arraylike * @return {T} */ const from = Array.from; const isArray = Array.isArray; /** * @param {string} s * @return {string} */ const toLowerCase = s => s.toLowerCase(); const trimLeftRegex = /^\s*/g; /** * @param {string} s * @return {string} */ const trimLeft = s => s.replace(trimLeftRegex, ''); const fromCamelCaseRegex = /([A-Z])/g; /** * @param {string} s * @param {string} separator * @return {string} */ const fromCamelCase = (s, separator) => trimLeft(s.replace(fromCamelCaseRegex, match => `${separator}${toLowerCase(match)}`)); /** * @param {string} str * @return {Uint8Array} */ const _encodeUtf8Polyfill = str => { const encodedString = unescape(encodeURIComponent(str)); const len = encodedString.length; const buf = new Uint8Array(len); for (let i = 0; i < len; i++) { buf[i] = /** @type {number} */ (encodedString.codePointAt(i)); } return buf }; /* c8 ignore next */ const utf8TextEncoder = /** @type {TextEncoder} */ (typeof TextEncoder !== 'undefined' ? new TextEncoder() : null); /** * @param {string} str * @return {Uint8Array} */ const _encodeUtf8Native = str => utf8TextEncoder.encode(str); /** * @param {string} str * @return {Uint8Array} */ /* c8 ignore next */ const encodeUtf8 = utf8TextEncoder ? _encodeUtf8Native : _encodeUtf8Polyfill; /* c8 ignore next */ let utf8TextDecoder = typeof TextDecoder === 'undefined' ? null : new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }); /* c8 ignore start */ if (utf8TextDecoder && utf8TextDecoder.decode(new Uint8Array()).length === 1) { // Safari doesn't handle BOM correctly. // This fixes a bug in Safari 13.0.5 where it produces a BOM the first time it is called. // utf8TextDecoder.decode(new Uint8Array()).length === 1 on the first call and // utf8TextDecoder.decode(new Uint8Array()).length === 1 on the second call // Another issue is that from then on no BOM chars are recognized anymore /* c8 ignore next */ utf8TextDecoder = null; } /** * Often used conditions. * * @module conditions */ /** * @template T * @param {T|null|undefined} v * @return {T|null} */ /* c8 ignore next */ const undefinedToNull = v => v === undefined ? null : v; /* eslint-env browser */ /** * Isomorphic variable storage. * * Uses LocalStorage in the browser and falls back to in-memory storage. * * @module storage */ /* c8 ignore start */ class VarStoragePolyfill { constructor () { this.map = new Map(); } /** * @param {string} key * @param {any} newValue */ setItem (key, newValue) { this.map.set(key, newValue); } /** * @param {string} key */ getItem (key) { return this.map.get(key) } } /* c8 ignore stop */ /** * @type {any} */ let _localStorage = new VarStoragePolyfill(); let usePolyfill = true; /* c8 ignore start */ try { // if the same-origin rule is violated, accessing localStorage might thrown an error if (typeof localStorage !== 'undefined') { _localStorage = localStorage; usePolyfill = false; } } catch (e) { } /* c8 ignore stop */ /** * This is basically localStorage in browser, or a polyfill in nodejs */ /* c8 ignore next */ const varStorage = _localStorage; /** * Utility functions for working with EcmaScript objects. * * @module object */ /** * Object.assign */ const assign = Object.assign; /** * @param {Object<string,any>} obj */ const keys = Object.keys; /** * @template V * @param {{[k:string]:V}} obj * @param {function(V,string):any} f */ const forEach$1 = (obj, f) => { for (const key in obj) { f(obj[key], key); } }; /** * @todo implement mapToArray & map * * @template R * @param {Object<string,any>} obj * @param {function(any,string):R} f * @return {Array<R>} */ const map = (obj, f) => { const results = []; for (const key in obj) { results.push(f(obj[key], key)); } return results }; /** * @param {Object<string,any>} obj * @return {number} */ const length$1 = obj => keys(obj).length; /** * @param {Object|undefined} obj */ const isEmpty = obj => { // eslint-disable-next-line for (const _k in obj) { return false } return true }; /** * @param {Object<string,any>} obj * @param {function(any,string):boolean} f * @return {boolean} */ const every = (obj, f) => { for (const key in obj) { if (!f(obj[key], key)) { return false } } return true }; /** * Calls `Object.prototype.hasOwnProperty`. * * @param {any} obj * @param {string|symbol} key * @return {boolean} */ const hasProperty = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key); /** * @param {Object<string,any>} a * @param {Object<string,any>} b * @return {boolean} */ const equalFlat = (a, b) => a === b || (length$1(a) === length$1(b) && every(a, (val, key) => (val !== undefined || hasProperty(b, key)) && b[key] === val)); /** * Common functions and function call helpers. * * @module function */ /** * Calls all functions in `fs` with args. Only throws after all functions were called. * * @param {Array<function>} fs * @param {Array<any>} args */ const callAll = (fs, args, i = 0) => { try { for (; i < fs.length; i++) { fs[i](...args); } } finally { if (i < fs.length) { callAll(fs, args, i + 1); } } }; /** * @template T * * @param {T} a * @param {T} b * @return {boolean} */ const equalityStrict = (a, b) => a === b; /* c8 ignore start */ /** * @param {any} a * @param {any} b * @return {boolean} */ const equalityDeep = (a, b) => { if (a == null || b == null) { return equalityStrict(a, b) } if (a.constructor !== b.constructor) { return false } if (a === b) { return true } switch (a.constructor) { case ArrayBuffer: a = new Uint8Array(a); b = new Uint8Array(b); // eslint-disable-next-line no-fallthrough case Uint8Array: { if (a.byteLength !== b.byteLength) { return false } for (let i = 0; i < a.length; i++) { if (a[i] !== b[i]) { return false } } break } case Set: { if (a.size !== b.size) { return false } for (const value of a) { if (!b.has(value)) { return false } } break } case Map: { if (a.size !== b.size) { return false } for (const key of a.keys()) { if (!b.has(key) || !equalityDeep(a.get(key), b.get(key))) { return false } } break } case Object: if (length$1(a) !== length$1(b)) { return false } for (const key in a) { if (!hasProperty(a, key) || !equalityDeep(a[key], b[key])) { return false } } break case Array: if (a.length !== b.length) { return false } for (let i = 0; i < a.length; i++) { if (!equalityDeep(a[i], b[i])) { return false } } break default: return false } return true }; /** * @template V * @template {V} OPTS * * @param {V} value * @param {Array<OPTS>} options */ // @ts-ignore const isOneOf = (value, options) => options.includes(value); /** * Isomorphic module to work access the environment (query params, env variables). * * @module map */ /* c8 ignore next */ // @ts-ignore const isNode = typeof process !== 'undefined' && process.release && /node|io\.js/.test(process.release.name); /* c8 ignore next */ const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined' && !isNode; /** * @type {Map<string,string>} */ let params; /* c8 ignore start */ const computeParams = () => { if (params === undefined) { if (isNode) { params = create$6(); const pargs = process.argv; let currParamName = null; for (let i = 0; i < pargs.length; i++) { const parg = pargs[i]; if (parg[0] === '-') { if (currParamName !== null) { params.set(currParamName, ''); } currParamName = parg; } else { if (currParamName !== null) { params.set(currParamName, parg); currParamName = null; } } } if (currParamName !== null) { params.set(currParamName, ''); } // in ReactNative for example this would not be true (unless connected to the Remote Debugger) } else if (typeof location === 'object') { params = create$6(); // eslint-disable-next-line no-undef (location.search || '?').slice(1).split('&').forEach((kv) => { if (kv.length !== 0) { const [key, value] = kv.split('='); params.set(`--${fromCamelCase(key, '-')}`, value); params.set(`-${fromCamelCase(key, '-')}`, value); } }); } else { params = create$6(); } } return params }; /* c8 ignore stop */ /** * @param {string} name * @return {boolean} */ /* c8 ignore next */ const hasParam = (name) => computeParams().has(name); /** * @param {string} name * @param {string} defaultVal * @return {string} */ /* c8 ignore next 2 */ const getParam = (name, defaultVal) => computeParams().get(name) || defaultVal; /** * @param {string} name * @return {string|null} */ /* c8 ignore next 4 */ const getVariable = (name) => isNode ? undefinedToNull(process.env[name.toUpperCase()]) : undefinedToNull(varStorage.getItem(name)); /** * @param {string} name * @return {boolean} */ /* c8 ignore next 2 */ const hasConf = (name) => hasParam('--' + name) || getVariable(name) !== null; /* c8 ignore next */ hasConf('production'); /* c8 ignore next 2 */ const forceColor = isNode && isOneOf(process.env.FORCE_COLOR, ['true', '1', '2']); /* c8 ignore start */ const supportsColor = !hasParam('no-colors') && (!isNode || process.stdout.isTTY || forceColor) && ( !isNode || hasParam('color') || forceColor || getVariable('COLORTERM') !== null || (getVariable('TERM') || '').includes('color') ); /* c8 ignore stop */ /** * Working with value pairs. * * @module pair */ /** * @template L,R */ class Pair { /** * @param {L} left * @param {R} right */ constructor (left, right) { this.left = left; this.right = right; } } /** * @template L,R * @param {L} left * @param {R} right * @return {Pair<L,R>} */ const create$4 = (left, right) => new Pair(left, right); /** * @template L,R * @param {Array<Pair<L,R>>} arr * @param {function(L, R):any} f */ const forEach = (arr, f) => arr.forEach(p => f(p.left, p.right)); /* eslint-env browser */ /* c8 ignore start */ /** * @type {Document} */ const doc = /** @type {Document} */ (typeof document !== 'undefined' ? document : {}); /** * @param {string} name * @return {HTMLElement} */ const createElement = name => doc.createElement(name); /** * @return {DocumentFragment} */ const createDocumentFragment = () => doc.createDocumentFragment(); /** * @param {string} text * @return {Text} */ const createTextNode = text => doc.createTextNode(text); /** @type {DOMParser} */ (typeof DOMParser !== 'undefined' ? new DOMParser() : null); /** * @param {Element} el * @param {Array<pair.Pair<string,string|boolean>>} attrs Array of key-value pairs * @return {Element} */ const setAttributes = (el, attrs) => { forEach(attrs, (key, value) => { if (value === false) { el.removeAttribute(key); } else if (value === true) { el.setAttribute(key, ''); } else { // @ts-ignore el.setAttribute(key, value); } }); return el }; /** * @param {Array<Node>|HTMLCollection} children * @return {DocumentFragment} */ const fragment = children => { const fragment = createDocumentFragment(); for (let i = 0; i < children.length; i++) { appendChild(fragment, children[i]); } return fragment }; /** * @param {Element} parent * @param {Array<Node>} nodes * @return {Element} */ const append = (parent, nodes) => { appendChild(parent, fragment(nodes)); return parent }; /** * @param {EventTarget} el * @param {string} name * @param {EventListener} f */ const addEventListener = (el, name, f) => el.addEventListener(name, f); /** * @param {string} name * @param {Array<pair.Pair<string,string>|pair.Pair<string,boolean>>} attrs Array of key-value pairs * @param {Array<Node>} children * @return {Element} */ const element = (name, attrs = [], children = []) => append(setAttributes(createElement(name), attrs), children); /** * @param {string} t * @return {Text} */ const text = createTextNode; /** * @param {Map<string,string>} m * @return {string} */ const mapToStyleString = m => map$1(m, (value, key) => `${key}:${value};`).join(''); /** * @param {Node} parent * @param {Node} child * @return {Node} */ const appendChild = (parent, child) => parent.appendChild(child); doc.ELEMENT_NODE; doc.TEXT_NODE; doc.CDATA_SECTION_NODE; doc.COMMENT_NODE; doc.DOCUMENT_NODE; doc.DOCUMENT_TYPE_NODE; doc.DOCUMENT_FRAGMENT_NODE; /* c8 ignore stop */ /** * JSON utility functions. * * @module json */ /** * Transform JavaScript object to JSON. * * @param {any} object * @return {string} */ const stringify = JSON.stringify; /* global requestIdleCallback, requestAnimationFrame, cancelIdleCallback, cancelAnimationFrame */ /** * Utility module to work with EcmaScript's event loop. * * @module eventloop */ /** * @type {Array<function>} */ let queue = []; const _runQueue = () => { for (let i = 0; i < queue.length; i++) { queue[i](); } queue = []; }; /** * @param {function():void} f */ const enqueue = f => { queue.push(f); if (queue.length === 1) { setTimeout(_runQueue, 0); } }; /** * Common Math expressions. * * @module math */ const floor = Math.floor; const ceil = Math.ceil; const abs = Math.abs; const round = Math.round; const log10 = Math.log10; /** * @function * @param {number} a * @param {number} b * @return {number} The sum of a and b */ const add = (a, b) => a + b; /** * @function * @param {number} a * @param {number} b * @return {number} The smaller element of a and b */ const min = (a, b) => a < b ? a : b; /** * @function * @param {number} a * @param {number} b * @return {number} The bigger element of a and b */ const max = (a, b) => a > b ? a : b; /** * Base 10 exponential function. Returns the value of 10 raised to the power of pow. * * @param {number} exp * @return {number} */ const exp10 = exp => Math.pow(10, exp); /** * @param {number} n * @return {boolean} Wether n is negative. This function also differentiates between -0 and +0 */ const isNegativeZero = n => n !== 0 ? n < 0 : 1 / n < 0; /** * Utility module to work with EcmaScript Symbols. * * @module symbol */ /** * Return fresh symbol. * * @return {Symbol} */ const create$3 = Symbol; /** * Utility module to convert metric values. * * @module metric */ const prefixUp = ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']; const prefixDown = ['', 'm', 'μ', 'n', 'p', 'f', 'a', 'z', 'y']; /** * Calculate the metric prefix for a number. Assumes E.g. `prefix(1000) = { n: 1, prefix: 'k' }` * * @param {number} n * @param {number} [baseMultiplier] Multiplier of the base (10^(3*baseMultiplier)). E.g. `convert(time, -3)` if time is already in milli seconds * @return {{n:number,prefix:string}} */ const prefix = (n, baseMultiplier = 0) => { const nPow = n === 0 ? 0 : log10(n); let mult = 0; while (nPow < mult * 3 && baseMultiplier > -8) { baseMultiplier--; mult--; } while (nPow >= 3 + mult * 3 && baseMultiplier < 8) { baseMultiplier++; mult++; } const prefix = baseMultiplier < 0 ? prefixDown[-baseMultiplier] : prefixUp[baseMultiplier]; return { n: round((mult > 0 ? n / exp10(mult * 3) : n * exp10(mult * -3)) * 1e12) / 1e12, prefix } }; /** * Utility module to work with time. * * @module time */ /** * Return current unix time. * * @return {number} */ const getUnixTime = Date.now; /** * Transform time (in ms) to a human readable format. E.g. 1100 => 1.1s. 60s => 1min. .001 => 10μs. * * @param {number} d duration in milliseconds * @return {string} humanized approximation of time */ const humanizeDuration = d => { if (d < 60000) { const p = prefix(d, -1); return round(p.n * 100) / 100 + p.prefix + 's' } d = floor(d / 1000); const seconds = d % 60; const minutes = floor(d / 60) % 60; const hours = floor(d / 3600) % 24; const days = floor(d / 86400); if (days > 0) { return days + 'd' + ((hours > 0 || minutes > 30) ? ' ' + (minutes > 30 ? hours + 1 : hours) + 'h' : '') } if (hours > 0) { /* c8 ignore next */ return hours + 'h' + ((minutes > 0 || seconds > 30) ? ' ' + (seconds > 30 ? minutes + 1 : minutes) + 'min' : '') } return minutes + 'min' + (seconds > 0 ? ' ' + seconds + 's' : '') }; const BOLD = create$3(); const UNBOLD = create$3(); const BLUE = create$3(); const GREY = create$3(); const GREEN = create$3(); const RED = create$3(); const PURPLE = create$3(); const ORANGE = create$3(); const UNCOLOR = create$3(); /* c8 ignore start */ /** * @param {Array<string|Symbol|Object|number>} args * @return {Array<string|object|number>} */ const computeNoColorLoggingArgs = args => { const logArgs = []; // try with formatting until we find something unsupported let i = 0; for (; i < args.length; i++) { const arg = args[i]; if (arg.constructor === String || arg.constructor === Number) ; else if (arg.constructor === Object) { logArgs.push(JSON.stringify(arg)); } } return logArgs }; /* c8 ignore stop */ /** * Isomorphic logging module with support for colors! * * @module logging */ /** * @type {Object<Symbol,pair.Pair<string,string>>} */ const _browserStyleMap = { [BOLD]: create$4('font-weight', 'bold'), [UNBOLD]: create$4('font-weight', 'normal'), [BLUE]: create$4('color', 'blue'), [GREEN]: create$4('color', 'green'), [GREY]: create$4('color', 'grey'), [RED]: create$4('color', 'red'), [PURPLE]: create$4('color', 'purple'), [ORANGE]: create$4('color', 'orange'), // not well supported in chrome when debugging node with inspector - TODO: deprecate [UNCOLOR]: create$4('color', 'black') }; /** * @param {Array<string|Symbol|Object|number>} args * @return {Array<string|object|number>} */ /* c8 ignore start */ const computeBrowserLoggingArgs = (args) => { const strBuilder = []; const styles = []; const currentStyle = create$6(); /** * @type {Array<string|Object|number>} */ let logArgs = []; // try with formatting until we find something unsupported let i = 0; for (; i < args.length; i++) { const arg = args[i]; // @ts-ignore const style = _browserStyleMap[arg]; if (style !== undefined) { currentStyle.set(style.left, style.right); } else { if (arg.constructor === String || arg.constructor === Number) { const style = mapToStyleString(currentStyle); if (i > 0 || style.length > 0) { strBuilder.push('%c' + arg); styles.push(style); } else { strBuilder.push(arg); } } else { break } } } if (i > 0) { // create logArgs with what we have so far logArgs = styles; logArgs.unshift(strBuilder.join('')); } // append the rest for (; i < args.length; i++) { const arg = args[i]; if (!(arg instanceof Symbol)) { logArgs.push(arg); } } return logArgs }; /* c8 ignore stop */ /* c8 ignore start */ const computeLoggingArgs = supportsColor ? computeBrowserLoggingArgs : computeNoColorLoggingArgs; /* c8 ignore stop */ /** * @param {Array<string|Symbol|Object|number>} args */ const print = (...args) => { console.log(...computeLoggingArgs(args)); /* c8 ignore next */ vconsoles.forEach((vc) => vc.print(args)); }; /* c8 ignore stop */ /** * @param {Error} err */ /* c8 ignore start */ const printError = (err) => { console.error(err); vconsoles.forEach((vc) => vc.printError(err)); }; /* c8 ignore stop */ /** * @param {string} url image location * @param {number} height height of the image in pixel */ /* c8 ignore start */ const printImg = (url, height) => { if (isBrowser) { console.log( '%c ', `font-size: ${height}px; background-size: contain; background-repeat: no-repeat; background-image: url(${url})` ); // console.log('%c ', `font-size: ${height}x; background: url(${url}) no-repeat;`) } vconsoles.forEach((vc) => vc.printImg(url, height)); }; /* c8 ignore stop */ /** * @param {string} base64 * @param {number} height */ /* c8 ignore next 2 */ const printImgBase64 = (base64, height) => printImg(`data:image/gif;base64,${base64}`, height); /** * @param {Array<string|Symbol|Object|number>} args */ const group = (...args) => { console.group(...computeLoggingArgs(args)); /* c8 ignore next */ vconsoles.forEach((vc) => vc.group(args)); }; /** * @param {Array<string|Symbol|Object|number>} args */ const groupCollapsed = (...args) => { console.groupCollapsed(...computeLoggingArgs(args)); /* c8 ignore next */ vconsoles.forEach((vc) => vc.groupCollapsed(args)); }; const groupEnd = () => { console.groupEnd(); /* c8 ignore next */ vconsoles.forEach((vc) => vc.groupEnd()); }; const vconsoles = create$5(); /** * @param {Array<string|Symbol|Object|number>} args * @return {Array<Element>} */ /* c8 ignore start */ const _computeLineSpans = (args) => { const spans = []; const currentStyle = new Map(); // try with formatting until we find something unsupported let i = 0; for (; i < args.length; i++) { const arg = args[i]; // @ts-ignore const style = _browserStyleMap[arg]; if (style !== undefined) { currentStyle.set(style.left, style.right); } else { if (arg.constructor === String || arg.constructor === Number) { // @ts-ignore const span = element('span', [ create$4('style', mapToStyleString(currentStyle)) ], [text(arg.toString())]); if (span.innerHTML === '') { span.innerHTML = '&nbsp;'; } spans.push(span); } else { break } } } // append the rest for (; i < args.length; i++) { let content = args[i]; if (!(content instanceof Symbol)) { if (content.constructor !== String && content.constructor !== Number) { content = ' ' + stringify(content) + ' '; } spans.push( element('span', [], [text(/** @type {string} */ (content))]) ); } } return spans }; /* c8 ignore stop */ const lineStyle = 'font-family:monospace;border-bottom:1px solid #e2e2e2;padding:2px;'; /* c8 ignore start */ class VConsole { /** * @param {Element} dom */ constructor (dom) { this.dom = dom; /** * @type {Element} */ this.ccontainer = this.dom; this.depth = 0; vconsoles.add(this); } /** * @param {Array<string|Symbol|Object|number>} args * @param {boolean} collapsed */ group (args, collapsed = false) { enqueue(() => { const triangleDown = element('span', [ create$4('hidden', collapsed), create$4('style', 'color:grey;font-size:120%;') ], [text('▼')]); const triangleRight = element('span', [ create$4('hidden', !collapsed), create$4('style', 'color:grey;font-size:125%;') ], [text('▶')]); const content = element( 'div', [create$4( 'style', `${lineStyle};padding-left:${this.depth * 10}px` )], [triangleDown, triangleRight, text(' ')].concat( _computeLineSpans(args) ) ); const nextContainer = element('div', [ create$4('hidden', collapsed) ]); const nextLine = element('div', [], [content, nextContainer]); append(this.ccontainer, [nextLine]); this.ccontainer = nextContainer; this.depth++; // when header is clicked, collapse/uncollapse container addEventListener(content, 'click', (_event) => { nextContainer.toggleAttribute('hidden'); triangleDown.toggleAttribute('hidden'); triangleRight.toggleAttribute('hidden'); }); }); } /** * @param {Array<string|Symbol|Object|number>} args */ groupCollapsed (args) { this.group(args, true); } groupEnd () { enqueue(() => { if (this.depth > 0) { this.depth--; // @ts-ignore this.ccontainer = this.ccontainer.parentElement.parentElement; } }); } /** * @param {Array<string|Symbol|Object|number>} args */ print (args) { enqueue(() => { append(this.ccontainer, [ element('div', [ create$4( 'style', `${lineStyle};padding-left:${this.depth * 10}px` ) ], _computeLineSpans(args)) ]); }); } /** * @param {Error} err */ printError (err) { this.print([RED, BOLD, err.toString()]); } /** * @param {string} url * @param {number} height */ printImg (url, height) { enqueue(() => { append(this.ccontainer, [ element('img', [ create$4('src', url), create$4('height', `${round(height * 1.5)}px`) ]) ]); }); } /** * @param {Node} node */ printDom (node) { enqueue(() => { append(this.ccontainer, [node]); }); } destroy () { enqueue(() => { vconsoles.delete(this); }); } } /* c8 ignore stop */ /** * @param {Element} dom */ /* c8 ignore next */ const createVConsole = (dom) => new VConsole(dom); /* eslint-env browser */ /** * Binary data constants. * * @module binary */ /** * n-th bit activated. * * @type {number} */ const BIT1 = 1; const BIT2 = 2; const BIT3 = 4; const BIT4 = 8; const BIT6 = 32; const BIT7 = 64; const BIT8 = 128; const BITS5 = 31; const BITS6 = 63; const BITS7 = 127; /** * @type {number} */ const BITS31 = 0x7FFFFFFF; /** * @type {number} */ const BITS32 = 0xFFFFFFFF; /* eslint-env browser */ const getRandomValues = crypto.getRandomValues.bind(crypto); /** * Isomorphic module for true random numbers / buffers / uuids. * * Attention: falls back to Math.random if the browser does not support crypto. * * @module random */ const uint32 = () => getRandomValues(new Uint32Array(1))[0]; // @ts-ignore const uuidv4Template = [1e7] + -1e3 + -4e3 + -8e3 + -1e11; /** * @return {string} */ const uuidv4 = () => uuidv4Template.replace(/[018]/g, /** @param {number} c */ c => (c ^ uint32() & 15 >> c / 4).toString(16) ); /** * @module prng */ /** * Xorshift32 is a very simple but elegang PRNG with a period of `2^32-1`. */ class Xorshift32 { /** * @param {number} seed Unsigned 32 bit number */ constructor (seed) { this.seed = seed; /** * @type {number} */ this._state = seed; } /** * Generate a random signed integer. * * @return {Number} A 32 bit signed integer. */ next () { let x = this._state; x ^= x << 13; x ^= x >> 17; x ^= x << 5; this._state = x; return (x >>> 0) / (BITS32 + 1) } } /** * @module prng */ /** * This is a variant of xoroshiro128plus - the fastest full-period generator passing BigCrush without systematic failures. * * This implementation follows the idea of the original xoroshiro128plus implementation, * but is optimized for the JavaScript runtime. I.e. * * The operations are performed on 32bit integers (the original implementation works with 64bit values). * * The initial 128bit state is computed based on a 32bit seed and Xorshift32. * * This implementation returns two 32bit values based on the 64bit value that is computed by xoroshiro128plus. * Caution: The last addition step works slightly different than in the original implementation - the add carry of the * first 32bit addition is not carried over to the last 32bit. * * [Reference implementation](http://vigna.di.unimi.it/xorshift/xoroshiro128plus.c) */ class Xoroshiro128plus { /** * @param {number} seed Unsigned 32 bit number */ constructor (seed) { this.seed = seed; // This is a variant of Xoroshiro128plus to fill the initial state const xorshift32 = new Xorshift32(seed); this.state = new Uint32Array(4); for (let i = 0; i < 4; i++) { this.state[i] = xorshift32.next() * BITS32; } this._fresh = true; } /** * @return {number} Float/Double in [0,1) */ next () { const state = this.state; if (this._fresh) { this._fresh = false; return ((state[0] + state[2]) >>> 0) / (BITS32 + 1) } else { this._fresh = true; const s0 = state[0]; const s1 = state[1]; const s2 = state[2] ^ s0; const s3 = state[3] ^ s1; // function js_rotl (x, k) { // k = k - 32 // const x1 = x[0] // const x2 = x[1] // x[0] = x2 << k | x1 >>> (32 - k) // x[1] = x1 << k | x2 >>> (32 - k) // } // rotl(s0, 55) // k = 23 = 55 - 32; j = 9 = 32 - 23 state[0] = (s1 << 23 | s0 >>> 9) ^ s2 ^ (s2 << 14 | s3 >>> 18); state[1] = (s0 << 23 | s1 >>> 9) ^ s3 ^ (s3 << 14); // rol(s1, 36) // k = 4 = 36 - 32; j = 23 = 32 - 9 state[2] = s3 << 4 | s2 >>> 28; state[3] = s2 << 4 | s3 >>> 28; return (((state[1] + state[3]) >>> 0) / (BITS32 + 1)) } } } /* // Reference implementation // Source: http://vigna.di.unimi.it/xorshift/xoroshiro128plus.c // By David Blackman and Sebastiano Vigna // Who published the reference implementation under Public Domain (CC0) #include <stdint.h> #include <stdio.h> uint64_t s[2]; static inline uint64_t rotl(const uint64_t x, int k) { return (x << k) | (x >> (64 - k)); } uint64_t next(void) { const uint64_t s0 = s[0]; uint64_t s1 = s[1]; s1 ^= s0; s[0] = rotl(s0, 55) ^ s1 ^ (s1 << 14); // a, b s[1] = rotl(s1, 36); // c return (s[0] + s[1]) & 0xFFFFFFFF; } int main(void) { int i; s[0] = 1111 | (1337ul << 32); s[1] = 1234 | (9999ul << 32); printf("1000 outputs of genrand_int31()\n"); for (i=0; i<100; i++) { printf("%10lu ", i); printf("%10lu ", next()); printf("- %10lu ", s[0] >> 32); printf("%10lu ", (s[0] << 32) >> 32); printf("%10lu ", s[1] >> 32); printf("%10lu ", (s[1] << 32) >> 32); printf("\n"); // if (i%5==4) printf("\n"); } return 0; } */ /** * Utility helpers for working with numbers. * * @module number */ const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER; /* c8 ignore next */ const isInteger = Number.isInteger || (num => typeof num === 'number' && isFinite(num) && floor(num) === num); /** * Efficient schema-less binary encoding with support for variable length encoding. * * Use [lib0/encoding] with [lib0/decoding]. Every encoding function has a corresponding decoding function. * * Encodes numbers in little-endian order (least to most significant byte order) * and is compatible with Golang's binary encoding (https://golang.org/pkg/encoding/binary/) * which is also used in Protocol Buffers. * * ```js * // encoding step * const encoder = encoding.createEncoder() * encoding.writeVarUint(encoder, 256) * encoding.writeVarString(encoder, 'Hello world!') * const buf = encoding.toUint8Array(encoder) * ``` * * ```js * // decoding step * const decoder = decoding.createDecoder(buf) * decoding.readVarUint(decoder) // => 256 * decoding.readVarString(decoder) // => 'Hello world!' * decoding.hasContent(decoder) // => false - all data is read * ``` * * @module encoding */ /** * A BinaryEncoder handles the encoding to an Uint8Array. */ class Encoder { constructor () { this.cpos = 0; this.cbuf = new Uint8Array(100); /** * @type {Array<Uint8Array>} */ this.bufs = []; } } /** * @function * @return {Encoder} */ const createEncoder = () => new Encoder(); /** * The current length of the encoded data. * * @function * @param {Encoder} encoder * @return {number} */ const length = encoder => { let len = encoder.cpos; for (let i = 0; i < encoder.bufs.length; i++) { len += encoder.bufs[i].length; } return len }; /** * Transform to Uint8Array. * * @function * @param {Encoder} encoder * @return {Uint8Array} The created ArrayBuffer. */ const toUint8Array = encoder => { const uint8arr = new Uint8Array(length(encoder)); let curPos = 0; for (let i = 0; i < encoder.bufs.length; i++) { const d = encoder.bufs[i]; uint8arr.set(d, curPos); curPos += d.length; } uint8arr.set(createUint8ArrayViewFromArrayBuffer(encoder.cbuf.buffer, 0, encoder.cpos), curPos); return uint8arr }; /** * Verify that it is possible to write `len` bytes wtihout checking. If * necessary, a new Buffer with the required length is attached. * * @param {Encoder} encoder * @param {number} len */ const verifyLen = (encoder, len) => { const bufferLen = encoder.cbuf.length; if (bufferLen - encoder.cpos < len) { encoder.bufs.push(createUint8ArrayViewFromArrayBuffer(encoder.cbuf.buffer, 0, encoder.cpos)); encoder.cbuf = new Uint8Array(max(bufferLen, len) * 2); encoder.cpos = 0; } }; /** * Write one byte to the encoder. * * @function * @param {Encoder} encoder * @param {number} num The byte that is to be encoded. */ const write = (encoder, num) => { const bufferLen = encoder.cbuf.length; if (encoder.cpos === bufferLen) { encoder.bufs.push(encoder.cbuf); encoder.cbuf = new Uint8Array(bufferLen * 2); encoder.cpos = 0; } encoder.cbuf[encoder.cpos++] = num; }; /** * Write one byte as an unsigned integer. * * @function * @param {Encoder} encoder * @param {number} num The number that is to be encoded. */ const writeUint8 = write; /** * Write a variable length unsigned integer. Max encodable integer is 2^53. * * @function * @param {Encoder} encoder * @param {number} num The number that is to be encoded. */ const writeVarUint = (encoder, num) => { while (num > BITS7) { write(encoder, BIT8 | (BITS7 & num)); num = floor(num / 128); // shift >>> 7 } write(encoder, BITS7 & num); }; /** * Write a variable length integer. * * We use the 7th bit instead for signaling that this is a negative number. * * @function * @param {Encoder} encoder * @param {number} num The number that is to be encoded. */ const writeVarInt = (encoder, num) => { const isNegative = isNegativeZero(num); if (isNegative) { num = -num; } // |- whether to continue reading |- whether is negative |- number write(encoder, (num > BITS6 ? BIT8 : 0) | (isNegative ? BIT7 : 0) | (BITS6 & num)); num = floor(num / 64); // shift >>> 6 // We don't need to consider the case of num === 0 so we can use a different // pattern here than above. while (num > 0) { write(encoder, (num > BITS7 ? BIT8 : 0) | (BITS7 & num)); num = floor(num / 128); // shift >>> 7 } }; /** * A cache to store strings temporarily */ const _strBuffer = new Uint8Array(30000); const _maxStrBSize = _strBuffer.length / 3; /** * Write a variable length string. * * @function * @param {Encoder} encoder * @param {String} str The string that is to be encoded. */ const _writeVarStringNative = (encoder, str) => { if (str.length < _maxStrBSize) { // We can encode the string into the existing buffer /* c8 ignore next */ const written = utf8TextEncoder.encodeInto(str, _strBuffer).written || 0; writeVarUint(encoder, written); for (let i = 0; i < written; i++) { write(encoder, _strBuffer[i]); } } else { writeVarUint8Array(encoder, encodeUtf8(str)); } }; /** * Write a variable length string. * * @function * @param {Encoder} encoder * @param {String} str The string that is to be encoded. */ const _writeVarStringPolyfill = (encoder, str) => { const encodedString = unescape(encodeURIComponent(str)); const len = encodedString.length; writeVarUint(encoder, len); for (let i = 0; i < len; i++) { write(encoder, /** @type {number} */ (encodedString.codePointAt(i))); } }; /** * Write a variable length string. * * @function * @param {Encoder} encoder * @param {String} str The string that is to be encoded. */ /* c8 ignore next */ const writeVarString = (utf8TextEncoder && /** @type {any} */ (utf8TextEncoder).encodeInto) ? _writeVarStringNative : _writeVarStringPolyfill; /** * Append fixed-length Uint8Array to the encoder. * * @function * @param {Encoder} encoder * @param {Uint8Array} uint8Array */ const writeUint8Array = (encoder, uint8Array) => { const bufferLen = encoder.cbuf.length; const cpos = encoder.cpos; const leftCopyLen = min(bufferLen - cpos, uint8Array.length); const rightCopyLen = uint8Array.length - leftCopyLen; encoder.cbuf.set(uint8Array.subarray(0, leftCopyLen), cpos); encoder.cpos += leftCopyLen; if (rightCopyLen > 0) { // Still something to write, write right half.. // Append new buffer encoder.bufs.push(encoder.cbuf); // must have at least size of remaining buffer encoder.cbuf = new Uint8Array(max(bufferLen * 2, rightCopyLen)); // copy array encoder.cbuf.set(uint8Array.subarray(leftCopyLen)); encoder.cpos = rightCopyLen; } }; /** * Append an Uint8Array to Encoder. * * @function * @param {Encoder} encoder * @param {Uint8Array} uint8Array */ const writeVarUint8Array = (encoder, uint8Array) => { writeVarUint(encoder, uint8Array.byteLength); writeUint8Array(encoder, uint8Array); }; /** * Create an DataView of the next `len` bytes. Use it to write data after * calling this function. * * ```js * // write float32 using DataView * const dv = writeOnDataView(encoder, 4) * dv.setFloat32(0, 1.1) * // read float32 using DataView * const dv = readFromDataView(encoder, 4) * dv.getFloat32(0) // => 1.100000023841858 (leaving it to the reader to find out why this is the correct result) * ``` * * @param {Encoder} encoder * @param {number} len * @return {DataView} */ const writeOnDataView = (encoder, len) => { verifyLen(encoder, len); const dview = new DataView(encoder.cbuf.buffer, encoder.cpos, len); encoder.cpos += len; return dview }; /** * @param {Encoder} encoder * @param {number} num */ const writeFloat32 = (encoder, num) => writeOnDataView(encoder, 4).setFloat32(0, num, false); /** * @param {Encoder} encoder * @param {number} num */ const writeFloat64 = (encoder, num) => writeOnDataView(encoder, 8).setFloat64(0, num, false); /** * @param {Encoder} encoder * @param {bigint} num */ const writeBigInt64 = (encoder, num) => /** @type {any} */ (writeOnDataView(encoder, 8)).setBigInt64(0, num, false); const floatTestBed = new DataView(new ArrayBuffer(4)); /** * Check if a number can be encoded as a 32 bit float. * * @param {number} num * @return {boolean} */ const isFloat32 = num => { floatTestBed.setFloat32(0, num); return floatTestBed.getFloat32(0) === num }; /** * Encode data with efficient binary format. * * Differences to JSON: * • Transforms data to a binary format (not to a string) * • Encodes undefined, NaN, and ArrayBuffer (these can't be represented in JSON) * • Numbers are efficiently encoded either as a variable length integer, as a * 32 bit float, as a 64 bit float, or as a 64 bit bigint. * * Encoding table: * * | Data Type | Prefix | Encoding Method | Comment | * | ------------------- | -------- | ------------------ | ------- | * | undefined | 127 | | Functions, symbol, and everything that cannot be identified is encoded as undefined | * | null | 126 | | | * | integer | 125 | writeVarInt | Only encodes 32 bit signed integers | * | float32 | 124 | writeFloat32 | | * | float64 | 123 | writeFloat64 | | * | bigint | 122 | writeBigInt64 | | * | boolean (false) | 121 | | True and false are different data types so we save the following byte | * | boolean (true) | 120 | | - 0b01111000 so the last bit determines whether true or false | * | string | 119 | writeVarString | | * | object<string,any> | 118 | custom | Writes {length} then {length} key-value pairs | * | array<any> | 117 | custom | Writes {length} then {length} json values | * | Uint8Array | 116 | writeVarUint8Array | We use Uint8Array for any kind of binary data | * * Reasons for the decreasing prefix: * We need the first bit for extendability (later we may want to encode the * prefix with writeVarUint). The remaining 7 bits are divided as follows: * [0-30] the beginning of the data range is used for custom purposes * (defined by the function that uses this library) * [31-127] the end of the data range is used for data encoding by * lib0/encoding.js * * @param {Encoder} encoder * @param {undefined|null|number|bigint|boolean|string|Object<string,any>|Array<any>|Uint8Array} data */ const writeAny = (encoder, data) => { switch (typeof data) { case 'string': // TYPE 119: STRING write(encoder, 119); writeVarString(encoder, data); break case 'number': if (isInteger(data) && abs(data) <= BITS31) { // TYPE 125: INTEGER write(encoder, 125); writeVarInt(encoder, data); } else if (isFloat32(data)) { // TYPE 124: FLOAT32 write(encoder, 124); writeFloat32(encoder, data); } else { // TYPE 123: FLOAT64 write(encoder, 123); writeFloat64(encoder, data); } break case 'bigint': // TYPE 122: BigInt write(encoder, 122); writeBigInt64(encoder, data); break case 'object': if (data === null) { // TYPE 126: null write(encoder, 126); } else if (isArray(data)) { // TYPE 117: Array write(encoder, 117); writeVarUint(encoder, data.length); for (let i = 0; i < data.length; i++) { writeAny(encoder, data[i]); } } else if (data instanceof Uint8Array) { // TYPE 116: ArrayBuffer write(encoder, 116); writeVarUint8Array(encoder, data); } else { // TYPE 118: Object write(encoder, 118); const keys = Object.keys(data); writeVarUint(encoder, keys.length); for (let i = 0; i < keys.length; i++) { const key = keys[i]; writeVarString(encoder, key); writeAny(encoder, data[key]); } } break case 'boolean': // TYPE 120/121: boolean (true/false) write(encoder, data ? 120 : 121); break default: // TYPE 127: undefined write(encoder, 127); } }; /** * Now come a few stateful encoder that have their own classes. */ /** * Basic Run Length Encoder - a basic compression implementation. * * Encodes [1,1,1,7] to [1,3,7,1] (3 times 1, 1 time 7). This encoder might do more harm than good if there are a lot of values that are not repeated. * * It was originally used for image compression. Cool .. article http://csbruce.com/cbm/transactor/pdfs/trans_v7_i06.pdf * * @note T must not be null! * * @template T */ class RleEncoder extends Encoder { /** * @param {function(Encoder, T):void} writer */ constructor (writer) { super(); /** * The writer */ this.w = writer; /** * Current state * @type {T|null} */ this.s = null; this.count = 0; } /** * @param {T} v */ write (v) { if (this.s === v) { this.count++; } else { if (this.count > 0) { // flush counter, unless this is the first value (count = 0) writeVarUint(this, this.count - 1); // since count is always > 0, we can decrement by one. non-standard encoding ftw } this.count = 1; // write first value this.w(this, v); this.s = v; } } } /** * @param