
Security News
CVE Volume Surges Past 48,000 in 2025 as WordPress Plugin Ecosystem Drives Growth
CVE disclosures hit a record 48,185 in 2025, driven largely by vulnerabilities in third-party WordPress plugins.
Monorepo of isomorphic utility functions
This library is meant to replace all global JavaScript functions with isomorphic module imports. Additionally, it implements several performance-oriented utility modules. Most noteworthy are the binary encoding/decoding modules [lib0/encoding] / [lib0/decoding], the randomized testing framework [lib0/testing], the fast Pseudo Random Number Generator [lib0/PRNG], the small socket.io alternative [lib0/websocket], and the logging module [lib0/logging] that allows colorized logging in all environments. Lib0 has only one dependency, which is also from the author of lib0. If lib0 is transpiled with rollup or webpack, very little code is produced because of the way that it is written. All exports are pure and are removed by transpilers that support dead code elimination. Here is an example of how dead code elemination and mangling optimizes code from lib0:
// How the code is optimized by transpilers:
// lib0/json.js
export const stringify = JSON.stringify
export const parse = JSON.parse
// index.js
import * as json from 'lib0/json'
export const f = (arg1, arg2) => json.stringify(arg1) + json.stringify(arg2)
// compiled with rollup and uglifyjs:
const s=JSON.stringify,f=(a,b)=>s(a)+s(b)
export {f}
Each function in this library is tested thoroughly and is not deoptimized by v8 (except some logging and comparison functions that can't be implemented without deoptimizations). This library implements its own test suite that is designed for randomized testing and inspecting performance issues.
node --trace-deopt and node --trace-optThe code style might be a bit different from what you are used to. Stay open. Most of the design choices have been thought through. The purpose of this code style is to create code that is optimized by the compiler and that results in small code bundles when used with common module bundlers. Keep that in mind when reading the library.
const x = someCondition ? A : B cannot be eleminated, because it is tied to a condition.class Coord { constructor (x, y) { this.x = x; this.y = y} } instead of { x: x, y: y }, because the compiler needs to be assured that the order of properties does not change. { y: y, x: x } has a different hidden class than { x: x, y: y }, which will lead to code deoptimizations if their use is alternated.new keyword. Prefer exporting factory functions like const createCoordinate = (x, y) => new Coord(x, y).class Duck { eat () { swallow() } } and class Cow { eat () { chew() } } have the
same signature, but implement it differently.const variable declarations. Use let only in loops. const always leads to easier code.lib0 contains isomorphic modules that work in nodejs, the browser, and other environments. It exports modules as the commonjs and the new esm module format.
If possible,
ESM module
import module from 'lib0/[module]' // automatically resolves to lib0/[module].js
CommonJS
require('lib0/[module]') // automatically resolves to lib0/dist/[module].cjs
Manual
Automatically resolving to commonjs and esm modules is implemented using conditional exports which is available in node>=v12. If support for older versions is required, then it is recommended to define the location of the module manually:
import module from 'lib0/[module].js'
// require('lib0/dist/[module].cjs')
import * as array from 'lib0/array'
array.last(arr: ArrayLike<L>): LReturn the last element of an array. The element must exist
array.create(): Array<C>array.copy(a: Array<D>): Array<D>array.appendTo(dest: Array<M>, src: Array<M>)Append elements from src to dest
array.from(arraylike: ArrayLike<T>|Iterable<T>): TTransforms something array-like to an actual Array.
array.every(arr: ARR, f: function(ITEM, number, ARR):boolean): booleanTrue iff condition holds on every element in the Array.
array.some(arr: ARR, f: function(S, number, ARR):boolean): booleanTrue iff condition holds on some element in the Array.
array.equalFlat(a: ArrayLike<ELEM>, b: ArrayLike<ELEM>): booleanarray.flatten(arr: Array<Array<ELEM>>): Array<ELEM>array.isArrayarray.unique(arr: Array<T>): Array<T>array.uniqueBy(arr: ArrayLike<T>, mapper: function(T):M): Array<T>import * as binary from 'lib0/binary'
binary.BIT1: numbern-th bit activated.
binary.BIT2binary.BIT3binary.BIT4binary.BIT5binary.BIT6binary.BIT7binary.BIT8binary.BIT9binary.BIT10binary.BIT11binary.BIT12binary.BIT13binary.BIT14binary.BIT15binary.BIT16binary.BIT17binary.BIT18binary.BIT19binary.BIT20binary.BIT21binary.BIT22binary.BIT23binary.BIT24binary.BIT25binary.BIT26binary.BIT27binary.BIT28binary.BIT29binary.BIT30binary.BIT31binary.BIT32binary.BITS0: numberFirst n bits activated.
binary.BITS1binary.BITS2binary.BITS3binary.BITS4binary.BITS5binary.BITS6binary.BITS7binary.BITS8binary.BITS9binary.BITS10binary.BITS11binary.BITS12binary.BITS13binary.BITS14binary.BITS15binary.BITS16binary.BITS17binary.BITS18binary.BITS19binary.BITS20binary.BITS21binary.BITS22binary.BITS23binary.BITS24binary.BITS25binary.BITS26binary.BITS27binary.BITS28binary.BITS29binary.BITS30binary.BITS31: numberbinary.BITS32: numberimport * as broadcastchannel from 'lib0/broadcastchannel'
// In browser window A:
broadcastchannel.subscribe('my events', data => console.log(data))
broadcastchannel.publish('my events', 'Hello world!') // => A: 'Hello world!' fires synchronously in same tab
// In browser window B:
broadcastchannel.publish('my events', 'hello from tab B') // => A: 'hello from tab B'
broadcastchannel.subscribe(room: string, f: function(any, any):any)Subscribe to global publish events.
broadcastchannel.unsubscribe(room: string, f: function(any, any):any)Unsubscribe from publish global events.
broadcastchannel.publish(room: string, data: any, origin: any)Publish data to all subscribers (including subscribers on this tab)
import * as buffer from 'lib0/buffer'
buffer.createUint8ArrayFromLen(len: number)buffer.createUint8ArrayViewFromArrayBuffer(buffer: ArrayBuffer, byteOffset: number, length: number)Create Uint8Array with initial content from buffer
buffer.createUint8ArrayFromArrayBuffer(buffer: ArrayBuffer)Create Uint8Array with initial content from buffer
buffer.toBase64buffer.fromBase64buffer.copyUint8Array(uint8Array: Uint8Array): Uint8ArrayCopy the content of an Uint8Array view to a new ArrayBuffer.
buffer.encodeAny(data: any): Uint8ArrayEncode anything as a UInt8Array. It's a pun on typescripts's any type.
See encoding.writeAny for more information.
buffer.decodeAny(buf: Uint8Array): anyDecode an any-encoded value.
import * as cache from 'lib0/cache'
new cache.Cache(timeout: number)cache.removeStale(cache: module:cache.Cache<K, V>): numbercache.set(cache: module:cache.Cache<K, V>, key: K, value: V)cache.get(cache: module:cache.Cache<K, V>, key: K): V | undefinedcache.refreshTimeout(cache: module:cache.Cache<K, V>, key: K)cache.getAsync(cache: module:cache.Cache<K, V>, key: K): V | Promise<V> | undefinedWorks well in conjunktion with setIfUndefined which has an async init function. Using getAsync & setIfUndefined ensures that the init function is only called once.
cache.remove(cache: module:cache.Cache<K, V>, key: K)cache.setIfUndefined(cache: module:cache.Cache<K, V>, key: K, init: function():Promise<V>, removeNull: boolean): Promise<V> | Vcache.create(timeout: number)import * as component from 'lib0/component'
component.registry: CustomElementRegistrycomponent.define(name: string, constr: any, opts: ElementDefinitionOptions)component.whenDefined(name: string): Promise<CustomElementConstructor>new component.Lib0Component(state: S)component.Lib0Component#state: S|nullcomponent.Lib0Component#setState(state: S, forceStateUpdate: boolean)component.Lib0Component#updateState(stateUpdate: any)component.createComponent(name: string, cnf: module:component~CONF<T>): Class<module:component.Lib0Component>component.createComponentDefiner(definer: function)component.defineListComponentcomponent.defineLazyLoadingComponentimport * as conditions from 'lib0/conditions'
conditions.undefinedToNullimport * as crypto from 'lib0/crypto'
y(data: string | Uint8Array): Uint8ArrayymmetricKey(secret: string | Uint8Array, salt: string | Uint8Array, opts: Object, opts.extractable: boolean, opts.usages: Array<'sign'|'verify'|'encrypt'|'decrypt'>): PromiseLike<CryptoKey>ymmetricKey()eAsymmetricKey(opts: Object, opts.extractable: boolean, opts.usages: Array<'sign'|'verify'|'encrypt'|'decrypt'>)eAsymmetricKey()ey(key: CryptoKey)ey()ymmetricKey(jwk: any, opts: Object, opts.extractable: boolean, opts.usages: Array<'sign'|'verify'|'encrypt'|'decrypt'>)ymmetricKey()symmetricKey(jwk: any, opts: Object, opts.extractable: boolean, opts.usages: Array<'sign'|'verify'|'encrypt'|'decrypt'>)symmetricKey()(data: Uint8Array, key: CryptoKey): PromiseLike<Uint8Array>()(data: Uint8Array, key: CryptoKey): PromiseLike<Uint8Array>()(data: Uint8Array, privateKey: CryptoKey): PromiseLike<Uint8Array>()(signature: Uint8Array, data: Uint8Array, publicKey: CryptoKey): PromiseLike<boolean>()import * as decoding from 'lib0/decoding'
Use [lib0/decoding] with [lib0/encoding]. 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.
// encoding step
const encoder = encoding.createEncoder()
encoding.writeVarUint(encoder, 256)
encoding.writeVarString(encoder, 'Hello world!')
const buf = encoding.toUint8Array(encoder)
// decoding step
const decoder = decoding.createDecoder(buf)
decoding.readVarUint(decoder) // => 256
decoding.readVarString(decoder) // => 'Hello world!'
decoding.hasContent(decoder) // => false - all data is read
new decoding.Decoder(uint8Array: Uint8Array)A Decoder handles the decoding of an Uint8Array.
decoding.Decoder#arr: Uint8ArrayDecoding target.
decoding.Decoder#pos: numberCurrent decoding position.
decoding.createDecoder(uint8Array: Uint8Array): module:decoding.Decoderdecoding.hasContent(decoder: module:decoding.Decoder): booleandecoding.clone(decoder: module:decoding.Decoder, newPos: number): module:decoding.DecoderClone a decoder instance. Optionally set a new position parameter.
decoding.readUint8Array(decoder: module:decoding.Decoder, len: number): Uint8ArrayCreate an Uint8Array view of the next len bytes and advance the position by len.
Important: The Uint8Array still points to the underlying ArrayBuffer. Make sure to discard the result as soon as possible to prevent any memory leaks.
Use buffer.copyUint8Array to copy the result into a new Uint8Array.
decoding.readVarUint8Array(decoder: module:decoding.Decoder): Uint8ArrayRead variable length Uint8Array.
Important: The Uint8Array still points to the underlying ArrayBuffer. Make sure to discard the result as soon as possible to prevent any memory leaks.
Use buffer.copyUint8Array to copy the result into a new Uint8Array.
decoding.readTailAsUint8Array(decoder: module:decoding.Decoder): Uint8ArrayRead the rest of the content as an ArrayBuffer
decoding.skip8(decoder: module:decoding.Decoder): numberSkip one byte, jump to the next position.
decoding.readUint8(decoder: module:decoding.Decoder): numberRead one byte as unsigned integer.
decoding.readUint16(decoder: module:decoding.Decoder): numberRead 2 bytes as unsigned integer.
decoding.readUint32(decoder: module:decoding.Decoder): numberRead 4 bytes as unsigned integer.
decoding.readUint32BigEndian(decoder: module:decoding.Decoder): numberRead 4 bytes as unsigned integer in big endian order. (most significant byte first)
decoding.peekUint8(decoder: module:decoding.Decoder): numberLook ahead without incrementing the position to the next byte and read it as unsigned integer.
decoding.peekUint16(decoder: module:decoding.Decoder): numberLook ahead without incrementing the position to the next byte and read it as unsigned integer.
decoding.peekUint32(decoder: module:decoding.Decoder): numberLook ahead without incrementing the position to the next byte and read it as unsigned integer.
decoding.readVarUint(decoder: module:decoding.Decoder): numberRead unsigned integer (32bit) with variable length. 1/8th of the storage is used as encoding overhead.
decoding.readVarInt(decoder: module:decoding.Decoder): numberRead signed integer (32bit) with variable length. 1/8th of the storage is used as encoding overhead.
decoding.peekVarUint(decoder: module:decoding.Decoder): numberLook ahead and read varUint without incrementing position
decoding.peekVarInt(decoder: module:decoding.Decoder): numberLook ahead and read varUint without incrementing position
decoding.readVarStringdecoding.peekVarString(decoder: module:decoding.Decoder): stringLook ahead and read varString without incrementing position
decoding.readFromDataView(decoder: module:decoding.Decoder, len: number): DataViewdecoding.readFloat32(decoder: module:decoding.Decoder)decoding.readFloat64(decoder: module:decoding.Decoder)decoding.readBigInt64(decoder: module:decoding.Decoder)decoding.readBigUint64(decoder: module:decoding.Decoder)decoding.readAny(decoder: module:decoding.Decoder)new decoding.RleDecoder(uint8Array: Uint8Array, reader: function(module:decoding.Decoder):T)T must not be null.
decoding.RleDecoder#s: T|nullCurrent state
decoding.RleDecoder#read()decoding.RleDecoder#s: Tnew decoding.IntDiffDecoder(uint8Array: Uint8Array, start: number)decoding.IntDiffDecoder#s: numberCurrent state
decoding.IntDiffDecoder#read(): numbernew decoding.RleIntDiffDecoder(uint8Array: Uint8Array, start: number)decoding.RleIntDiffDecoder#s: numberCurrent state
decoding.RleIntDiffDecoder#read(): numberdecoding.RleIntDiffDecoder#s: numbernew decoding.UintOptRleDecoder(uint8Array: Uint8Array)decoding.UintOptRleDecoder#s: numberdecoding.UintOptRleDecoder#read()decoding.UintOptRleDecoder#s: numbernew decoding.IncUintOptRleDecoder(uint8Array: Uint8Array)decoding.IncUintOptRleDecoder#s: numberdecoding.IncUintOptRleDecoder#read()new decoding.IntDiffOptRleDecoder(uint8Array: Uint8Array)decoding.IntDiffOptRleDecoder#s: numberdecoding.IntDiffOptRleDecoder#read(): numbernew decoding.StringDecoder(uint8Array: Uint8Array)decoding.StringDecoder#spos: numberdecoding.StringDecoder#read(): stringdecoding.RleDecoder#arr: Uint8ArrayDecoding target.
decoding.RleDecoder#pos: numberCurrent decoding position.
decoding.IntDiffDecoder#arr: Uint8ArrayDecoding target.
decoding.IntDiffDecoder#pos: numberCurrent decoding position.
decoding.RleIntDiffDecoder#arr: Uint8ArrayDecoding target.
decoding.RleIntDiffDecoder#pos: numberCurrent decoding position.
decoding.UintOptRleDecoder#arr: Uint8ArrayDecoding target.
decoding.UintOptRleDecoder#pos: numberCurrent decoding position.
decoding.IncUintOptRleDecoder#arr: Uint8ArrayDecoding target.
decoding.IncUintOptRleDecoder#pos: numberCurrent decoding position.
decoding.IntDiffOptRleDecoder#arr: Uint8ArrayDecoding target.
decoding.IntDiffOptRleDecoder#pos: numberCurrent decoding position.
import * as diff from 'lib0/diff'
diff.simpleDiffString(a: string, b: string): module:diff~SimpleDiff<string>Create a diff between two strings. This diff implementation is highly efficient, but not very sophisticated.
diff.simpleDiffdiff.simpleDiffArray(a: Array<T>, b: Array<T>, compare: function(T, T):boolean): module:diff~SimpleDiff<Array<T>>Create a diff between two arrays. This diff implementation is highly efficient, but not very sophisticated.
Note: This is basically the same function as above. Another function was created so that the runtime can better optimize these function calls.
diff.simpleDiffStringWithCursor(a: string, b: string, cursor: number)Diff text and try to diff at the current cursor position.
import * as dom from 'lib0/dom'
dom.doc: Documentdom.createElementdom.createDocumentFragmentdom.createTextNodedom.domParserdom.emitCustomEventdom.setAttributesdom.setAttributesMapdom.fragmentdom.appenddom.removedom.addEventListenerdom.removeEventListenerdom.addEventListenersdom.removeEventListenersdom.elementdom.canvasdom.textdom.pairToStyleStringdom.pairsToStyleStringdom.mapToStyleStringdom.querySelectordom.querySelectorAlldom.getElementByIddom.parseFragmentdom.childNodes: anydom.parseElementdom.replaceWithdom.insertBeforedom.appendChilddom.ELEMENT_NODEdom.TEXT_NODEdom.CDATA_SECTION_NODEdom.COMMENT_NODEdom.DOCUMENT_NODEdom.DOCUMENT_TYPE_NODEdom.DOCUMENT_FRAGMENT_NODEdom.checkNodeType(node: any, type: number)dom.isParentOf(parent: Node, child: HTMLElement)import * as encoding from 'lib0/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.
// encoding step
const encoder = encoding.createEncoder()
encoding.writeVarUint(encoder, 256)
encoding.writeVarString(encoder, 'Hello world!')
const buf = encoding.toUint8Array(encoder)
// decoding step
const decoder = decoding.createDecoder(buf)
decoding.readVarUint(decoder) // => 256
decoding.readVarString(decoder) // => 'Hello world!'
decoding.hasContent(decoder) // => false - all data is read
new encoding.Encoder()A BinaryEncoder handles the encoding to an Uint8Array.
encoding.Encoder#bufs: Array<Uint8Array>encoding.createEncoder(): module:encoding.Encoderencoding.length(encoder: module:encoding.Encoder): numberThe current length of the encoded data.
encoding.toUint8Array(encoder: module:encoding.Encoder): Uint8ArrayTransform to Uint8Array.
encoding.verifyLen(encoder: module:encoding.Encoder, len: number)Verify that it is possible to write len bytes wtihout checking. If
necessary, a new Buffer with the required length is attached.
encoding.write(encoder: module:encoding.Encoder, num: number)Write one byte to the encoder.
encoding.set(encoder: module:encoding.Encoder, pos: number, num: number)Write one byte at a specific position. Position must already be written (i.e. encoder.length > pos)
encoding.writeUint8(encoder: module:encoding.Encoder, num: number)Write one byte as an unsigned integer.
encoding.setUint8(encoder: module:encoding.Encoder, pos: number, num: number)Write one byte as an unsigned Integer at a specific location.
encoding.writeUint16(encoder: module:encoding.Encoder, num: number)Write two bytes as an unsigned integer.
encoding.setUint16(encoder: module:encoding.Encoder, pos: number, num: number)Write two bytes as an unsigned integer at a specific location.
encoding.writeUint32(encoder: module:encoding.Encoder, num: number)Write two bytes as an unsigned integer
encoding.writeUint32BigEndian(encoder: module:encoding.Encoder, num: number)Write two bytes as an unsigned integer in big endian order. (most significant byte first)
encoding.setUint32(encoder: module:encoding.Encoder, pos: number, num: number)Write two bytes as an unsigned integer at a specific location.
encoding.writeVarUint(encoder: module:encoding.Encoder, num: number)Write a variable length unsigned integer. Max encodable integer is 2^53.
encoding.writeVarInt(encoder: module:encoding.Encoder, num: number)Write a variable length integer.
We use the 7th bit instead for signaling that this is a negative number.
encoding.writeVarStringencoding.writeBinaryEncoder(encoder: module:encoding.Encoder, append: module:encoding.Encoder)Write the content of another Encoder.
encoding.writeUint8Array(encoder: module:encoding.Encoder, uint8Array: Uint8Array)Append fixed-length Uint8Array to the encoder.
encoding.writeVarUint8Array(encoder: module:encoding.Encoder, uint8Array: Uint8Array)Append an Uint8Array to Encoder.
encoding.writeOnDataView(encoder: module:encoding.Encoder, len: number): DataViewCreate an DataView of the next len bytes. Use it to write data after
calling this function.
// 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)
encoding.writeFloat32(encoder: module:encoding.Encoder, num: number)encoding.writeFloat64(encoder: module:encoding.Encoder, num: number)encoding.writeBigInt64(encoder: module:encoding.Encoder, num: bigint)encoding.writeBigUint64(encoder: module:encoding.Encoder, num: bigint)encoding.writeAny(encoder: module:encoding.Encoder, data: undefined|null|number|bigint|boolean|string|Object<string,any>|Array<any>|Uint8Array)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 | 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
new encoding.RleEncoder(writer: function(module:encoding.Encoder, T):void)Now come a few stateful encoder that have their own classes.
encoding.RleEncoder#s: T|nullCurrent state
encoding.RleEncoder#write(v: T)new encoding.IntDiffEncoder(start: number)Basic diff decoder using variable length encoding.
Encodes the values [3, 1100, 1101, 1050, 0] to [3, 1097, 1, -51, -1050] using writeVarInt.
encoding.IntDiffEncoder#s: numberCurrent state
encoding.IntDiffEncoder#write(v: number)new encoding.RleIntDiffEncoder(start: number)A combination of IntDiffEncoder and RleEncoder.
Basically first writes the IntDiffEncoder and then counts duplicate diffs using RleEncoding.
Encodes the values [1,1,1,2,3,4,5,6] as [1,1,0,2,1,5] (RLE([1,0,0,1,1,1,1,1]) ⇒ RleIntDiff[1,1,0,2,1,5])
encoding.RleIntDiffEncoder#s: numberCurrent state
encoding.RleIntDiffEncoder#write(v: number)new encoding.UintOptRleEncoder()Optimized Rle encoder that does not suffer from the mentioned problem of the basic Rle encoder.
Internally uses VarInt encoder to write unsigned integers. If the input occurs multiple times, we write write it as a negative number. The UintOptRleDecoder then understands that it needs to read a count.
Encodes [1,2,3,3,3] as [1,2,-3,3] (once 1, once 2, three times 3)
encoding.UintOptRleEncoder#s: numberencoding.UintOptRleEncoder#write(v: number)encoding.UintOptRleEncoder#toUint8Array()new encoding.IncUintOptRleEncoder()Increasing Uint Optimized RLE Encoder
The RLE encoder counts the number of same occurences of the same value. The IncUintOptRle encoder counts if the value increases. I.e. 7, 8, 9, 10 will be encoded as [-7, 4]. 1, 3, 5 will be encoded as [1, 3, 5].
encoding.IncUintOptRleEncoder#s: numberencoding.IncUintOptRleEncoder#write(v: number)encoding.IncUintOptRleEncoder#toUint8Array()new encoding.IntDiffOptRleEncoder()A combination of the IntDiffEncoder and the UintOptRleEncoder.
The count approach is similar to the UintDiffOptRleEncoder, but instead of using the negative bitflag, it encodes in the LSB whether a count is to be read. Therefore this Encoder only supports 31 bit integers!
Encodes [1, 2, 3, 2] as [3, 1, 6, -1] (more specifically [(1 << 1) | 1, (3 << 0) | 0, -1])
Internally uses variable length encoding. Contrary to normal UintVar encoding, the first byte contains:
Therefore, only five bits remain to encode diff ranges.
Use this Encoder only when appropriate. In most cases, this is probably a bad idea.
encoding.IntDiffOptRleEncoder#s: numberencoding.IntDiffOptRleEncoder#write(v: number)encoding.IntDiffOptRleEncoder#toUint8Array()new encoding.StringEncoder()Optimized String Encoder.
Encoding many small strings in a simple Encoder is not very efficient. The function call to decode a string takes some time and creates references that must be eventually deleted. In practice, when decoding several million small strings, the GC will kick in more and more often to collect orphaned string objects (or maybe there is another reason?).
This string encoder solves the above problem. All strings are concatenated and written as a single string using a single encoding call.
The lengths are encoded using a UintOptRleEncoder.
encoding.StringEncoder#sarr: Array<string>encoding.StringEncoder#write(string: string)encoding.StringEncoder#toUint8Array()encoding.RleEncoder#bufs: Array<Uint8Array>encoding.IntDiffEncoder#bufs: Array<Uint8Array>encoding.RleIntDiffEncoder#bufs: Array<Uint8Array>import * as env from 'lib0/environment'
env.isNodeenv.isBrowserenv.isMacenv.hasParamenv.getParamenv.getVariableenv.getConf(name: string): string|nullenv.hasConfenv.productionenv.supportsColorimport * as error from 'lib0/error'
error.create(s: string): Errorerror.methodUnimplemented(): nevererror.unexpectedCase(): neverimport * as eventloop from 'lib0/eventloop'
eventloop.enqueue(f: function():void)eventloop#destroy()eventloop.timeout(timeout: number, callback: function): module:eventloop~TimeoutObjecteventloop.interval(timeout: number, callback: function): module:eventloop~TimeoutObjecteventloop.Animationeventloop.animationFrame(cb: function(number):void): module:eventloop~TimeoutObjecteventloop.idleCallback(cb: function): module:eventloop~TimeoutObjectNote: this is experimental and is probably only useful in browsers.
eventloop.createDebouncer(timeout: number): function(function():void):voidimport * as function from 'lib0/function'
function.callAll(fs: Array<function>, args: Array<any>)Calls all functions in fs with args. Only throws after all functions were called.
function.nopfunction.apply(f: function():T): Tfunction.id(a: A): Afunction.equalityStrict(a: T, b: T): booleanfunction.equalityFlat(a: Array<T>|object, b: Array<T>|object): booleanfunction.equalityDeep(a: any, b: any): booleanfunction.isOneOf(value: V, options: Array<OPTS>)import * as lib0 from 'lib0/index'
Not recommended if the module bundler doesn't support dead code elimination.
import * as indexeddb from 'lib0/indexeddb'
indexeddb.rtop(request: IDBRequest): Promise<any>IDB Request to Promise transformer
indexeddb.openDB(name: string, initDB: function(IDBDatabase):any): Promise<IDBDatabase>indexeddb.deleteDB(name: string)indexeddb.createStores(db: IDBDatabase, definitions: Array<Array<string>|Array<string|IDBObjectStoreParameters|undefined>>)indexeddb.transact(db: IDBDatabase, stores: Array<string>, access: "readwrite"|"readonly"): Array<IDBObjectStore>indexeddb.count(store: IDBObjectStore, range: IDBKeyRange): Promise<number>indexeddb.get(store: IDBObjectStore, key: String | number | ArrayBuffer | Date | Array<any> ): Promise<String | number | ArrayBuffer | Date | Array<any>>indexeddb.del(store: IDBObjectStore, key: String | number | ArrayBuffer | Date | IDBKeyRange | Array<any> )indexeddb.put(store: IDBObjectStore, item: String | number | ArrayBuffer | Date | boolean, key: String | number | ArrayBuffer | Date | Array<any>)indexeddb.add(store: IDBObjectStore, item: String|number|ArrayBuffer|Date|boolean, key: String|number|ArrayBuffer|Date|Array.<any>): Promise<any>indexeddb.addAutoKey(store: IDBObjectStore, item: String|number|ArrayBuffer|Date): Promise<number>indexeddb.getAll(store: IDBObjectStore, range: IDBKeyRange, limit: number): Promise<Array<any>>indexeddb.getAllKeys(store: IDBObjectStore, range: IDBKeyRange, limit: number): Promise<Array<any>>indexeddb.queryFirst(store: IDBObjectStore, query: IDBKeyRange|null, direction: 'next'|'prev'|'nextunique'|'prevunique'): Promise<any>indexeddb.getLastKey(store: IDBObjectStore, range: IDBKeyRange?): Promise<any>indexeddb.getFirstKey(store: IDBObjectStore, range: IDBKeyRange?): Promise<any>indexeddb.getAllKeysValues(store: IDBObjectStore, range: IDBKeyRange, limit: number): Promise<Array<KeyValuePair>>indexeddb.iterate(store: IDBObjectStore, keyrange: IDBKeyRange|null, f: function(any,any):void|boolean|Promise<void|boolean>, direction: 'next'|'prev'|'nextunique'|'prevunique')Iterate on keys and values
indexeddb.iterateKeys(store: IDBObjectStore, keyrange: IDBKeyRange|null, f: function(any):void|boolean|Promise<void|boolean>, direction: 'next'|'prev'|'nextunique'|'prevunique')Iterate on the keys (no values)
indexeddb.getStore(t: IDBTransaction, store: String)IDBObjectStoreOpen store from transaction
indexeddb.createIDBKeyRangeBound(lower: any, upper: any, lowerOpen: boolean, upperOpen: boolean)indexeddb.createIDBKeyRangeUpperBound(upper: any, upperOpen: boolean)indexeddb.createIDBKeyRangeLowerBound(lower: any, lowerOpen: boolean)import * as isomorphic from 'lib0/isomorphic'
import * as iterator from 'lib0/iterator'
iterator.mapIterator(iterator: Iterator<T>, f: function(T):R): IterableIterator<R>iterator.createIterator(next: function():IteratorResult<T>): IterableIterator<T>iterator.iteratorFilter(iterator: Iterator<T>, filter: function(T):boolean)iterator.iteratorMap(iterator: Iterator<T>, fmap: function(T):M)import * as json from 'lib0/json'
json.stringify(object: any): stringTransform JavaScript object to JSON.
json.parse(json: string): anyParse JSON object.
import * as list from 'lib0/list'
new e#ListNode()e#next: this|nulle#prev: this|nullnew st()art: N | nulld: N | null(): module:list.List<N>()(queue: module:list.List<N>)()(queue: module:list.List<N>, node: N)Remove a single node from the queue. Only works with Queues that operate on Doubly-linked lists of nodes.
()odeode()etween(queue: module:list.List<N>, left: N| null, right: N| null, node: N)etween()(queue: module:list.List<N>, node: N, newNode: N)Remove a single node from the queue. Only works with Queues that operate on Doubly-linked lists of nodes.
()(queue: module:list.List<N>, n: N)()nt(queue: module:list.List<N>, n: N)nt()t(list: module:list.List<N>): N| nullt()(list: module:list.List<N>): N| null()(list: module:list.List<N>, f: function(N):M): Array<M>()(list: module:list.List<N>)()(list: module:list.List<N>, f: function(N):M)()import * as logging from 'lib0/logging'
logging.BOLDlogging.UNBOLDlogging.BLUElogging.GREYlogging.GREENlogging.REDlogging.PURPLElogging.ORANGElogging.UNCOLORlogging.print(args: Array<string|Symbol|Object|number>)logging.warn(args: Array<string|Symbol|Object|number>)logging.printError(err: Error)logging.printImg(url: string, height: number)logging.printImgBase64(base64: string, height: number)logging.group(args: Array<string|Symbol|Object|number>)logging.groupCollapsed(args: Array<string|Symbol|Object|number>)logging.groupEndlogging.printDom(createNode: function():Node)logging.printCanvas(canvas: HTMLCanvasElement, height: number)logging.vconsolesnew logging.VConsole(dom: Element)logging.VConsole#ccontainer: Elementlogging.VConsole#group(args: Array<string|Symbol|Object|number>, collapsed: boolean)logging.VConsole#groupCollapsed(args: Array<string|Symbol|Object|number>)logging.VConsole#groupEnd()logging.VConsole#print(args: Array<string|Symbol|Object|number>)logging.VConsole#printError(err: Error)logging.VConsole#printImg(url: string, height: number)logging.VConsole#printDom(node: Node)logging.VConsole#destroy()logging.createVConsole(dom: Element)logging.createModuleLogger(moduleName: string): function(...any):voidimport * as map from 'lib0/map'
map.create(): Map<any, any>Creates a new Map instance.
map.copy(m: Map<X,Y>): Map<X,Y>Copy a Map object into a fresh Map object.
map.setIfUndefined(map: Map<K, T>, key: K, createT: function():T): TGet map property. Create T if property is undefined and set T on map.
const listeners = map.setIfUndefined(events, 'eventName', set.create)
listeners.add(listener)
map.map(m: Map<K,V>, f: function(V,K):R): Array<R>Creates an Array and populates it with the content of all key-value pairs using the f(value, key) function.
map.any(m: Map<K,V>, f: function(V,K):boolean): booleanTests whether any key-value pairs pass the test implemented by f(value, key).
map.all(m: Map<K,V>, f: function(V,K):boolean): booleanTests whether all key-value pairs pass the test implemented by f(value, key).
import * as math from 'lib0/math'
math.floormath.ceilmath.absmath.imulmath.roundmath.log10math.log2math.logmath.sqrtmath.add(a: number, b: number): numbermath.min(a: number, b: number): numbermath.max(a: number, b: number): numbermath.isNaNmath.powmath.exp10(exp: number): numberBase 10 exponential function. Returns the value of 10 raised to the power of pow.
math.signmath.isNegativeZero(n: number): booleanimport * as metric from 'lib0/metric'
metric.yottametric.zettametric.exametric.petametric.terametric.gigametric.megametric.kilometric.hectometric.decametric.decimetric.centimetric.millimetric.micrometric.nanometric.picometric.femtometric.attometric.zeptometric.yoctometric.prefix(n: number, baseMultiplier: number): {n:number,prefix:string}Calculate the metric prefix for a number. Assumes E.g. prefix(1000) = { n: 1, prefix: 'k' }
import * as mutex from 'lib0/mutex'
mutex.createMutex(): mutexCreates a mutual exclude function with the following property:
const mutex = createMutex()
mutex(() => {
// This function is immediately executed
mutex(() => {
// This function is not executed, as the mutex is already active.
})
})
import * as number from 'lib0/number'
number.MAX_SAFE_INTEGERnumber.MIN_SAFE_INTEGERnumber.LOWEST_INT32number.HIGHEST_INT32: numbernumber.isIntegernumber.isNaNnumber.parseIntimport * as object from 'lib0/object'
object.create(): Object<string,any>object.assignObject.assign
object.keys(obj: Object<string,any>)object.forEach(obj: Object<string,any>, f: function(any,string):any)object.map(obj: Object<string,any>, f: function(any,string):R): Array<R>object.length(obj: Object<string,any>): numberobject.some(obj: Object<string,any>, f: function(any,string):boolean): booleanobject.isEmpty(obj: Object|undefined)object.every(obj: Object<string,any>, f: function(any,string):boolean): booleanobject.hasProperty(obj: any, key: string|symbol): booleanCalls Object.prototype.hasOwnProperty.
object.equalFlat(a: Object<string,any>, b: Object<string,any>): booleanimport * as observable from 'lib0/observable'
new observable.Observable()Handles named events.
observable.Observable#on(name: N, f: function)observable.Observable#once(name: N, f: function)observable.Observable#off(name: N, f: function)observable.Observable#emit(name: N, args: Array<any>)Emit a named event. All registered event listeners that listen to the specified name will receive the event.
observable.Observable#destroy()websocket.WebsocketClient#on(name: N, f: function)websocket.WebsocketClient#once(name: N, f: function)websocket.WebsocketClient#off(name: N, f: function)websocket.WebsocketClient#emit(name: N, args: Array<any>)Emit a named event. All registered event listeners that listen to the specified name will receive the event.
import * as pair from 'lib0/pair'
new pair.Pair(left: L, right: R)pair.create(left: L, right: R): module:pair.Pair<L,R>pair.createReversed(right: R, left: L): module:pair.Pair<L,R>pair.forEach(arr: Array<module:pair.Pair<L,R>>, f: function(L, R):any)pair.map(arr: Array<module:pair.Pair<L,R>>, f: function(L, R):X): Array<X>import * as prng from 'lib0/prng'
Given a seed a PRNG generates a sequence of numbers that cannot be reasonably predicted. Two PRNGs must generate the same random sequence of numbers if given the same seed.
prng.DefaultPRNGprng.create(seed: number): module:prng~PRNGCreate a Xoroshiro128plus Pseudo-Random-Number-Generator. This is the fastest full-period generator passing BigCrush without systematic failures. But there are more PRNGs available in ./PRNG/.
prng.bool(gen: module:prng~PRNG): BooleanGenerates a single random bool.
prng.int53(gen: module:prng~PRNG, min: Number, max: Number): NumberGenerates a random integer with 53 bit resolution.
prng.uint53(gen: module:prng~PRNG, min: Number, max: Number): NumberGenerates a random integer with 53 bit resolution.
prng.int32(gen: module:prng~PRNG, min: Number, max: Number): NumberGenerates a random integer with 32 bit resolution.
prng.uint32(gen: module:prng~PRNG, min: Number, max: Number): NumberGenerates a random integer with 53 bit resolution.
prng.int31(gen: module:prng~PRNG, min: Number, max: Number): Numberprng.real53(gen: module:prng~PRNG): NumberGenerates a random real on [0, 1) with 53 bit resolution.
prng.char(gen: module:prng~PRNG): stringGenerates a random character from char code 32 - 126. I.e. Characters, Numbers, special characters, and Space:
prng.letter(gen: module:prng~PRNG): stringprng.word(gen: module:prng~PRNG, minLen: number, maxLen: number): stringprng.utf16Rune(gen: module:prng~PRNG): stringTODO: this function produces invalid runes. Does not cover all of utf16!!
prng.utf16String(gen: module:prng~PRNG, maxlen: number)prng.oneOf(gen: module:prng~PRNG, array: Array<T>): TReturns one element of a given array.
prng.uint8Array(gen: module:prng~PRNG, len: number): Uint8Arrayprng.uint16Array(gen: module:prng~PRNG, len: number): Uint16Arrayprng.uint32Array(gen: module:prng~PRNG, len: number): Uint32Arrayimport * as promise from 'lib0/promise'
promise.create(f: function(PromiseResolve<T>,function(Error):void):any): Promise<T>promise.createEmpty(f: function(function():void,function(Error):void):void): Promise<void>promise.all(arrp: Array<Promise<T>>): Promise<Array<T>>Promise.all wait for all promises in the array to resolve and return the result
promise.reject(reason: Error): Promise<never>promise.resolve(res: T|void): Promise<T|void>promise.resolveWith(res: T): Promise<T>promise.until(timeout: number, check: function():boolean, intervalResolution: number): Promise<void>promise.wait(timeout: number): Promise<undefined>promise.isPromise(p: any): booleanChecks if an object is a promise using ducktyping.
Promises are often polyfilled, so it makes sense to add some additional guarantees if the user of this library has some insane environment where global Promise objects are overwritten.
import * as queue from 'lib0/queue'
new de#QueueNode()de#next: module:queue.QueueNode|nullnew ueue()tart: module:queue.QueueNode | nullnd: module:queue.QueueNode | null(): module:queue.Queue()(queue: module:queue.Queue)()(queue: module:queue.Queue, n: module:queue.QueueNode)()(queue: module:queue.Queue): module:queue.QueueNode | null()import * as random from 'lib0/random'
Attention: falls back to Math.random if the browser does not support crypto.
random.randrandom.uint32random.uint53random.oneOf(arr: Array<T>): Trandom.uuidv4import * as set from 'lib0/set'
set.createset.toArray(set: Set<T>): Array<T>set.first(set: Set<T>): Tset.from(entries: Iterable<T>): Set<T>import * as sort from 'lib0/sort'
Note: These sort implementations were created to compare different sorting algorithms in JavaScript. Don't use them if you don't know what you are doing. Native Array.sort is almost always a better choice.
sort.insertionSort(arr: Array<T>, compare: function(T,T):number): voidsort.quicksort(arr: Array<T>, compare: function(T,T):number): voidThis algorithm beats Array.prototype.sort in Chrome only with arrays with 10 million entries. In most cases [].sort will do just fine. Make sure to performance test your use-case before you integrate this algorithm.
Note that Chrome's sort is now a stable algorithm (Timsort). Quicksort is not stable.
import * as statistics from 'lib0/statistics'
statistics.median(arr: Array<number>): numberstatistics.average(arr: Array<number>): numberimport * as storage from 'lib0/storage'
Uses LocalStorage in the browser and falls back to in-memory storage.
storage.varStorageThis is basically localStorage in browser, or a polyfill in nodejs
storage.onChange(eventHandler: function({ key: string, newValue: string, oldValue: string }): void)A polyfill for addEventListener('storage', event => {..}) that does nothing if the polyfill is being used.
import * as string from 'lib0/string'
string.fromCharCodestring.fromCodePointstring.trimLeft(s: string): stringstring.fromCamelCase(s: string, separator: string): stringstring.utf8ByteLength(str: string): numberCompute the utf8ByteLength
string.utf8TextEncoderstring.encodeUtf8string.decodeUtf8string.splice(str: string, index: number, remove: number, insert: string)import * as symbol from 'lib0/symbol'
symbol.create(): SymbolReturn fresh symbol.
symbol.isSymbol(s: any): booleanimport * as testing from 'lib0/testing'
// test.js template for creating a test executable
import { runTests } from 'lib0/testing'
import * as log from 'lib0/logging'
import * as mod1 from './mod1.test.js'
import * as mod2 from './mod2.test.js'
import { isBrowser, isNode } from 'lib0/environment.js'
if (isBrowser) {
// optional: if this is ran in the browser, attach a virtual console to the dom
log.createVConsole(document.body)
}
runTests({
mod1,
mod2,
}).then(success => {
if (isNode) {
process.exit(success ? 0 : 1)
}
})
// mod1.test.js
/**
* runTests automatically tests all exported functions that start with "test".
* The name of the function should be in camelCase and is used for the logging output.
*
* @param {t.TestCase} tc
*\/
export const testMyFirstTest = tc => {
t.compare({ a: 4 }, { a: 4 }, 'objects are equal')
}
Now you can simply run node test.js to run your test or run test.js in the browser.
testing.extensivetesting.envSeednew testing.TestCase(moduleName: string, testName: string)testing.TestCase#moduleName: stringtesting.TestCase#testName: stringtesting.TestCase#resetSeed()testing.TestCase#prng: prng.PRNGA PRNG for this test case. Use only this PRNG for randomness to make the test case reproducible.
testing.repetitionTimetesting.run(moduleName: string, name: string, f: function(module:testing.TestCase):void|Promise<any>, i: number, numberOfTests: number)testing.describe(description: string, info: string)Describe what you are currently testing. The message will be logged.
export const testMyFirstTest = tc => {
t.describe('crunching numbers', 'already crunched 4 numbers!') // the optional second argument can describe the state.
}
testing.info(info: string)Describe the state of the current computation.
export const testMyFirstTest = tc => {
t.info(already crunched 4 numbers!') // the optional second argument can describe the state.
}
testing.printDomtesting.printCanvastesting.group(description: string, f: function(void):void)Group outputs in a collapsible category.
export const testMyFirstTest = tc => {
t.group('subtest 1', () => {
t.describe('this message is part of a collapsible section')
})
await t.groupAsync('subtest async 2', async () => {
await someaction()
t.describe('this message is part of a collapsible section')
})
}
testing.groupAsync(description: string, f: function(void):Promise<any>)Group outputs in a collapsible category.
export const testMyFirstTest = async tc => {
t.group('subtest 1', () => {
t.describe('this message is part of a collapsible section')
})
await t.groupAsync('subtest async 2', async () => {
await someaction()
t.describe('this message is part of a collapsible section')
})
}
testing.measureTime(message: string, f: function():void): numberMeasure the time that it takes to calculate something.
export const testMyFirstTest = async tc => {
t.measureTime('measurement', () => {
heavyCalculation()
})
await t.groupAsync('async measurement', async () => {
await heavyAsyncCalculation()
})
}
testing.measureTimeAsync(message: string, f: function():Promise<any>): Promise<number>Measure the time that it takes to calculate something.
export const testMyFirstTest = async tc => {
t.measureTimeAsync('measurement', async () => {
await heavyCalculation()
})
await t.groupAsync('async measurement', async () => {
await heavyAsyncCalculation()
})
}
testing.compareArrays(as: Array<T>, bs: Array<T>, m: string): booleantesting.compareStrings(a: string, b: string, m: string)testing.compareObjects(a: Object<K,V>, b: Object<K,V>, m: string)testing.compare(a: T, b: T, message: string?, customCompare: function(any,T,T,string,any):boolean)testing.assert(condition: boolean, message: string?)testing.promiseRejected(f: function():Promise<any>)testing.fails(f: function():void)testing.runTests(tests: Object<string, Object<string, function(module:testing.TestCase):void|Promise<any>>>)testing.fail(reason: string)testing.skip(cond: boolean)import * as time from 'lib0/time'
time.getDate(): DateReturn current time.
time.getUnixTime(): numberReturn current unix time.
time.humanizeDuration(d: number): stringTransform time (in ms) to a human readable format. E.g. 1100 => 1.1s. 60s => 1min. .001 => 10μs.
import * as tree from 'lib0/tree'
new tree.Tree()This is a Red Black Tree implementation
tree.Tree#findNext(id: K)tree.Tree#findPrev(id: K)tree.Tree#findNodeWithLowerBound(from: K)tree.Tree#findNodeWithUpperBound(to: K)tree.Tree#findSmallestNode(): Vtree.Tree#findWithLowerBound(from: K): Vtree.Tree#findWithUpperBound(to: K): Vtree.Tree#iterate(from: K, from: K, f: K)tree.Tree#find(id: K): V|nulltree.Tree#findNode(id: K): module:tree~N<V>|nulltree.Tree#delete(id: K)tree.Tree#put()import * as url from 'lib0/url'
url.decodeQueryParams(url: string): Object<string,string>Parse query parameters from an url.
url.encodeQueryParams(params: Object<string,string>): stringimport * as webcrypto.browser from 'lib0/webcrypto.browser'
()omValuesomValues()import * as webcrypto.node from 'lib0/webcrypto.node'
()to.subtle: anyomValuesomValues()import * as websocket from 'lib0/websocket'
Implements exponential backoff reconnects, ping/pong, and a nice event system using [lib0/observable].
new websocket.WebsocketClient(url: string, opts: object, opts.binaryType: 'arraybuffer' | 'blob' | null)websocket.WebsocketClient#ws: WebSocket?websocket.WebsocketClient#shouldConnect: booleanWhether to connect to other peers or not
websocket.WebsocketClient#send(message: any)websocket.WebsocketClient#destroy()websocket.WebsocketClient#disconnect()websocket.WebsocketClient#connect()React-native apps should be able to use lib0. You need to install a polyfill for webcrypto and enable package-exports support in react-native:
# install polyfill
npm i isomorphic-webcrypto@^2.3.8 # last known working version was 2.3.8
Add this to metro.config.js (see docs):
const config = {
// ...
resolver: {
unstable_enablePackageExports: true
}
}
The MIT License © Kevin Jahns
Lodash is a popular utility library that provides a wide range of functions for common programming tasks, such as manipulating arrays and objects, and working with functions and strings. Compared to lib0, Lodash has a larger community and more comprehensive documentation, but it is also larger in size.
Underscore is another utility library similar to Lodash, offering a variety of functions for functional programming. It is known for its simplicity and ease of use. While it provides many of the same utilities as lib0, it is not as lightweight and may not include some of the more specialized data structures found in lib0.
Ramda is a functional programming library for JavaScript that emphasizes immutability and pure functions. It provides a different approach compared to lib0, focusing more on functional programming paradigms. Ramda is a good choice for projects that require a functional programming style.
FAQs
> Monorepo of isomorphic utility functions
The npm package lib0 receives a total of 1,942,702 weekly downloads. As such, lib0 popularity was classified as popular.
We found that lib0 demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
CVE disclosures hit a record 48,185 in 2025, driven largely by vulnerabilities in third-party WordPress plugins.

Security News
Socket CEO Feross Aboukhadijeh joins Insecure Agents to discuss CVE remediation and why supply chain attacks require a different security approach.

Security News
Tailwind Labs laid off 75% of its engineering team after revenue dropped 80%, as LLMs redirect traffic away from documentation where developers discover paid products.