y-protocols
Version:
Yjs encoding protocols
1,966 lines (1,776 loc) • 293 kB
JavaScript
(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 = ' ';
}
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