pax_global_header00006660000000000000000000000064151250455350014517gustar00rootroot0000000000000052 comment=bb5b3de7bf163d0b7521129d7a9a8fdcc8629499 dmonad-lib0-c7e7806/000077500000000000000000000000001512504553500141525ustar00rootroot00000000000000dmonad-lib0-c7e7806/.github/000077500000000000000000000000001512504553500155125ustar00rootroot00000000000000dmonad-lib0-c7e7806/.github/workflows/000077500000000000000000000000001512504553500175475ustar00rootroot00000000000000dmonad-lib0-c7e7806/.github/workflows/node.js.yml000066400000000000000000000012701512504553500216320ustar00rootroot00000000000000# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions name: Testing Lib0 on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build: runs-on: ubuntu-latest strategy: matrix: node-version: [16.x, 18.x] steps: - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - run: npm ci - run: npm test dmonad-lib0-c7e7806/.gitignore000066400000000000000000000001171512504553500161410ustar00rootroot00000000000000dist .nyc_output coverage node_modules *.d.ts *.d.ts.map */*.d.ts */*.d.ts.map dmonad-lib0-c7e7806/.jsdoc.json000066400000000000000000000004741512504553500162320ustar00rootroot00000000000000{ "plugins": [ "plugins/markdown", "jsdoc-plugin-typescript" ], "recurseDepth": 10, "source": { "excludePattern": "/\\.test\\.js/" }, "sourceType": "module", "tags": { "allowUnknownTags": true, "dictionaries": ["jsdoc", "closure"] }, "typescript": { "moduleRoot": "./" } }dmonad-lib0-c7e7806/.npmignore000066400000000000000000000001371512504553500161520ustar00rootroot00000000000000*.test.js dist/test.* tsconfig.json rollup.config.js .nyc_output .travis.yml .git node_modules dmonad-lib0-c7e7806/.travis.yml000066400000000000000000000000421512504553500162570ustar00rootroot00000000000000language: node_js node_js: - 14 dmonad-lib0-c7e7806/.vscode/000077500000000000000000000000001512504553500155135ustar00rootroot00000000000000dmonad-lib0-c7e7806/.vscode/launch.json000066400000000000000000000007001512504553500176550ustar00rootroot00000000000000{ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "Gen Docs", "program": "${workspaceFolder}/bin/gendocs.js", "skipFiles": [ "/**" ], } ] } dmonad-lib0-c7e7806/LICENSE000066400000000000000000000021241512504553500151560ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2019 Kevin Jahns . Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. dmonad-lib0-c7e7806/README.md000066400000000000000000002237401512504553500154410ustar00rootroot00000000000000 # Lib0 > 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: ```js // 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} ``` ## Performance resources 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-opt` * https://youtu.be/IFWulQnM5E0 Good intro video * https://github.com/thlorenz/v8-perf * https://github.com/thlorenz/deoptigate - A great tool for investigating deoptimizations * https://github.com/vhf/v8-bailout-reasons - Description of some deopt messages ## Code style The 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. * No polymorphism! * Modules should only export pure functions and constants. This way the module bundler can eliminate dead code. The statement `const x = someCondition ? A : B` cannot be eleminated, because it is tied to a condition. * Use Classes for structuring data. Classes are well supported by jsdoc and are immediately optimized by the compiler. I.e. prefer `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. * The user of your module should never create data objects with the `new` keyword. Prefer exporting factory functions like `const createCoordinate = (x, y) => new Coord(x, y)`. * The use of class methods is discouraged, because method names can't be mangled or removed by dead code elimination. * The only acceptable use of methods is when two objects implement functionality differently. E.g. `class Duck { eat () { swallow() } }` and `class Cow { eat () { chew() } }` have the same signature, but implement it differently. * Prefer `const` variable declarations. Use `let` only in loops. `const` always leads to easier code. * Keep the potential execution stack small and compartmentalized. Nobody wants to debug spaghetti code. * Give proper names to your functions and ask yourself if you would know what the function does if you saw it in the execution stack. * Avoid recursion. There is a stack limit in most browsers and not every recursive function is optimized by the compiler. * Semicolons are superfluous. Lint with https://standardjs.com/ ## Using lib0 `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** ```js import module from 'lib0/[module]' // automatically resolves to lib0/[module].js ``` **CommonJS** ```js 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: ```js import module from 'lib0/[module].js' // require('lib0/dist/[module].cjs') ``` ## Modules
[lib0/array] Utility module to work with Arrays.
import * as array from 'lib0/array'
array.last(arr: ArrayLike<L>): L

Return 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>): T

Transforms something array-like to an actual Array.

array.every(arr: ARR, f: function(ITEM, number, ARR):boolean): boolean

True iff condition holds on every element in the Array.

array.some(arr: ARR, f: function(S, number, ARR):boolean): boolean

True iff condition holds on some element in the Array.

array.equalFlat(a: ArrayLike<ELEM>, b: ArrayLike<ELEM>): boolean
array.flatten(arr: Array<Array<ELEM>>): Array<ELEM>
array.isArray
array.unique(arr: Array<T>): Array<T>
array.uniqueBy(arr: ArrayLike<T>, mapper: function(T):M): Array<T>
[lib0/binary] Binary data constants.
import * as binary from 'lib0/binary'
binary.BIT1: number

n-th bit activated.

binary.BIT2
binary.BIT3
binary.BIT4
binary.BIT5
binary.BIT6
binary.BIT7
binary.BIT8
binary.BIT9
binary.BIT10
binary.BIT11
binary.BIT12
binary.BIT13
binary.BIT14
binary.BIT15
binary.BIT16
binary.BIT17
binary.BIT18
binary.BIT19
binary.BIT20
binary.BIT21
binary.BIT22
binary.BIT23
binary.BIT24
binary.BIT25
binary.BIT26
binary.BIT27
binary.BIT28
binary.BIT29
binary.BIT30
binary.BIT31
binary.BIT32
binary.BITS0: number

First n bits activated.

binary.BITS1
binary.BITS2
binary.BITS3
binary.BITS4
binary.BITS5
binary.BITS6
binary.BITS7
binary.BITS8
binary.BITS9
binary.BITS10
binary.BITS11
binary.BITS12
binary.BITS13
binary.BITS14
binary.BITS15
binary.BITS16
binary.BITS17
binary.BITS18
binary.BITS19
binary.BITS20
binary.BITS21
binary.BITS22
binary.BITS23
binary.BITS24
binary.BITS25
binary.BITS26
binary.BITS27
binary.BITS28
binary.BITS29
binary.BITS30
binary.BITS31: number
binary.BITS32: number
[lib0/broadcastchannel] Helpers for cross-tab communication using broadcastchannel with LocalStorage fallback.
import * 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)

[lib0/buffer] Utility functions to work with buffers (Uint8Array).
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.toBase64
buffer.fromBase64
buffer.copyUint8Array(uint8Array: Uint8Array): Uint8Array

Copy the content of an Uint8Array view to a new ArrayBuffer.

buffer.encodeAny(data: any): Uint8Array

Encode anything as a UInt8Array. It's a pun on typescripts's any type. See encoding.writeAny for more information.

buffer.decodeAny(buf: Uint8Array): any

Decode an any-encoded value.

[lib0/cache] An implementation of a map which has keys that expire.
import * as cache from 'lib0/cache'
new cache.Cache(timeout: number)
cache.removeStale(cache: module:cache.Cache<K, V>): number
cache.set(cache: module:cache.Cache<K, V>, key: K, value: V)
cache.get(cache: module:cache.Cache<K, V>, key: K): V | undefined
cache.refreshTimeout(cache: module:cache.Cache<K, V>, key: K)
cache.getAsync(cache: module:cache.Cache<K, V>, key: K): V | Promise<V> | undefined

Works 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> | V
cache.create(timeout: number)
[lib0/component] Web components.
import * as component from 'lib0/component'
component.registry: CustomElementRegistry
component.define(name: string, constr: any, opts: ElementDefinitionOptions)
component.whenDefined(name: string): Promise<CustomElementConstructor>
new component.Lib0Component(state: S)
component.Lib0Component#state: S|null
component.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.defineListComponent
component.defineLazyLoadingComponent
[lib0/conditions] Often used conditions.
import * as conditions from 'lib0/conditions'
conditions.undefinedToNull
[lib0/crypto]
import * as crypto from 'lib0/crypto'
y(data: string | Uint8Array): Uint8Array
ymmetricKey(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>
()
[lib0/decoding] Efficient schema-less binary decoding with support for variable length encoding.
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: Uint8Array

Decoding target.

decoding.Decoder#pos: number

Current decoding position.

decoding.createDecoder(uint8Array: Uint8Array): module:decoding.Decoder
decoding.hasContent(decoder: module:decoding.Decoder): boolean
decoding.clone(decoder: module:decoding.Decoder, newPos: number): module:decoding.Decoder

Clone a decoder instance. Optionally set a new position parameter.

decoding.readUint8Array(decoder: module:decoding.Decoder, len: number): Uint8Array

Create 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): Uint8Array

Read 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): Uint8Array

Read the rest of the content as an ArrayBuffer

decoding.skip8(decoder: module:decoding.Decoder): number

Skip one byte, jump to the next position.

decoding.readUint8(decoder: module:decoding.Decoder): number

Read one byte as unsigned integer.

decoding.readUint16(decoder: module:decoding.Decoder): number

Read 2 bytes as unsigned integer.

decoding.readUint32(decoder: module:decoding.Decoder): number

Read 4 bytes as unsigned integer.

decoding.readUint32BigEndian(decoder: module:decoding.Decoder): number

Read 4 bytes as unsigned integer in big endian order. (most significant byte first)

decoding.peekUint8(decoder: module:decoding.Decoder): number

Look ahead without incrementing the position to the next byte and read it as unsigned integer.

decoding.peekUint16(decoder: module:decoding.Decoder): number

Look ahead without incrementing the position to the next byte and read it as unsigned integer.

decoding.peekUint32(decoder: module:decoding.Decoder): number

Look ahead without incrementing the position to the next byte and read it as unsigned integer.

decoding.readVarUint(decoder: module:decoding.Decoder): number

Read unsigned integer (32bit) with variable length. 1/8th of the storage is used as encoding overhead.

  • numbers < 2^7 is stored in one bytlength
  • numbers < 2^14 is stored in two bylength
decoding.readVarInt(decoder: module:decoding.Decoder): number

Read signed integer (32bit) with variable length. 1/8th of the storage is used as encoding overhead.

  • numbers < 2^7 is stored in one bytlength
  • numbers < 2^14 is stored in two bylength
decoding.peekVarUint(decoder: module:decoding.Decoder): number

Look ahead and read varUint without incrementing position

decoding.peekVarInt(decoder: module:decoding.Decoder): number

Look ahead and read varUint without incrementing position

decoding.readVarString
decoding.peekVarString(decoder: module:decoding.Decoder): string

Look ahead and read varString without incrementing position

decoding.readFromDataView(decoder: module:decoding.Decoder, len: number): DataView
decoding.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|null

Current state

decoding.RleDecoder#read()
decoding.RleDecoder#s: T
new decoding.IntDiffDecoder(uint8Array: Uint8Array, start: number)
decoding.IntDiffDecoder#s: number

Current state

decoding.IntDiffDecoder#read(): number
new decoding.RleIntDiffDecoder(uint8Array: Uint8Array, start: number)
decoding.RleIntDiffDecoder#s: number

Current state

decoding.RleIntDiffDecoder#read(): number
decoding.RleIntDiffDecoder#s: number
new decoding.UintOptRleDecoder(uint8Array: Uint8Array)
decoding.UintOptRleDecoder#s: number
decoding.UintOptRleDecoder#read()
decoding.UintOptRleDecoder#s: number
new decoding.IncUintOptRleDecoder(uint8Array: Uint8Array)
decoding.IncUintOptRleDecoder#s: number
decoding.IncUintOptRleDecoder#read()
new decoding.IntDiffOptRleDecoder(uint8Array: Uint8Array)
decoding.IntDiffOptRleDecoder#s: number
decoding.IntDiffOptRleDecoder#read(): number
new decoding.StringDecoder(uint8Array: Uint8Array)
decoding.StringDecoder#spos: number
decoding.StringDecoder#read(): string
decoding.RleDecoder#arr: Uint8Array

Decoding target.

decoding.RleDecoder#pos: number

Current decoding position.

decoding.IntDiffDecoder#arr: Uint8Array

Decoding target.

decoding.IntDiffDecoder#pos: number

Current decoding position.

decoding.RleIntDiffDecoder#arr: Uint8Array

Decoding target.

decoding.RleIntDiffDecoder#pos: number

Current decoding position.

decoding.UintOptRleDecoder#arr: Uint8Array

Decoding target.

decoding.UintOptRleDecoder#pos: number

Current decoding position.

decoding.IncUintOptRleDecoder#arr: Uint8Array

Decoding target.

decoding.IncUintOptRleDecoder#pos: number

Current decoding position.

decoding.IntDiffOptRleDecoder#arr: Uint8Array

Decoding target.

decoding.IntDiffOptRleDecoder#pos: number

Current decoding position.

[lib0/diff] Efficient diffs.
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.simpleDiff
diff.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.

[lib0/dom] Utility module to work with the DOM.
import * as dom from 'lib0/dom'
dom.doc: Document
dom.createElement
dom.createDocumentFragment
dom.createTextNode
dom.domParser
dom.emitCustomEvent
dom.setAttributes
dom.setAttributesMap
dom.fragment
dom.append
dom.remove
dom.addEventListener
dom.removeEventListener
dom.addEventListeners
dom.removeEventListeners
dom.element
dom.canvas
dom.text
dom.pairToStyleString
dom.pairsToStyleString
dom.mapToStyleString
dom.querySelector
dom.querySelectorAll
dom.getElementById
dom.parseFragment
dom.childNodes: any
dom.parseElement
dom.replaceWith
dom.insertBefore
dom.appendChild
dom.ELEMENT_NODE
dom.TEXT_NODE
dom.CDATA_SECTION_NODE
dom.COMMENT_NODE
dom.DOCUMENT_NODE
dom.DOCUMENT_TYPE_NODE
dom.DOCUMENT_FRAGMENT_NODE
dom.checkNodeType(node: any, type: number)
dom.isParentOf(parent: Node, child: HTMLElement)
[lib0/encoding] Efficient schema-less binary encoding with support for variable length encoding.
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.Encoder
encoding.length(encoder: module:encoding.Encoder): number

The current length of the encoded data.

encoding.toUint8Array(encoder: module:encoding.Encoder): Uint8Array

Transform 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.writeVarString
encoding.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): DataView

Create 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|null

Current 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: number

Current 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: number

Current 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: number
encoding.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: number
encoding.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:

  • 1 bit that denotes whether the next value is a count (LSB)
  • 1 bit that denotes whether this value is negative (MSB - 1)
  • 1 bit that denotes whether to continue reading the variable length integer (MSB)

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: number
encoding.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>
[lib0/environment] Isomorphic module to work access the environment (query params, env variables).
import * as env from 'lib0/environment'
env.isNode
env.isBrowser
env.isMac
env.hasParam
env.getParam
env.getVariable
env.getConf(name: string): string|null
env.hasConf
env.production
env.supportsColor
[lib0/error] Error helpers.
import * as error from 'lib0/error'
error.create(s: string): Error
error.methodUnimplemented(): never
error.unexpectedCase(): never
[lib0/eventloop] Utility module to work with EcmaScript's event loop.
import * as eventloop from 'lib0/eventloop'
eventloop.enqueue(f: function():void)
eventloop#destroy()
eventloop.timeout(timeout: number, callback: function): module:eventloop~TimeoutObject
eventloop.interval(timeout: number, callback: function): module:eventloop~TimeoutObject
eventloop.Animation
eventloop.animationFrame(cb: function(number):void): module:eventloop~TimeoutObject
eventloop.idleCallback(cb: function): module:eventloop~TimeoutObject

Note: this is experimental and is probably only useful in browsers.

eventloop.createDebouncer(timeout: number): function(function():void):void
[lib0/function] Common functions and function call helpers.
import * 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.nop
function.apply(f: function():T): T
function.id(a: A): A
function.equalityStrict(a: T, b: T): boolean
function.equalityFlat(a: Array<T>|object, b: Array<T>|object): boolean
function.equalityDeep(a: any, b: any): boolean
function.isOneOf(value: V, options: Array<OPTS>)
[lib0/lib0] Experimental method to import lib0.
import * as lib0 from 'lib0/index'

Not recommended if the module bundler doesn't support dead code elimination.

[lib0/indexeddb] Helpers to work with IndexedDB.
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)IDBObjectStore

Open 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)
[lib0/isomorphic] Isomorphic library exports from isomorphic.js.
import * as isomorphic from 'lib0/isomorphic'
[lib0/iterator] Utility module to create and manipulate Iterators.
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)
[lib0/json] JSON utility functions.
import * as json from 'lib0/json'
json.stringify(object: any): string

Transform JavaScript object to JSON.

json.parse(json: string): any

Parse JSON object.

[lib0/list]
import * as list from 'lib0/list'
new e#ListNode()
e#next: this|null
e#prev: this|null
new st()
art: N | null
d: 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.

()
ode
ode()
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| null
t()
(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)
()
[lib0/logging] Isomorphic logging module with support for colors!
import * as logging from 'lib0/logging'
logging.BOLD
logging.UNBOLD
logging.BLUE
logging.GREY
logging.GREEN
logging.RED
logging.PURPLE
logging.ORANGE
logging.UNCOLOR
logging.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.groupEnd
logging.printDom(createNode: function():Node)
logging.printCanvas(canvas: HTMLCanvasElement, height: number)
logging.vconsoles
new logging.VConsole(dom: Element)
logging.VConsole#ccontainer: Element
logging.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):void
[lib0/map] Utility module to work with key-value stores.
import * 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): T

Get 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): boolean

Tests whether any key-value pairs pass the test implemented by f(value, key).

map.all(m: Map<K,V>, f: function(V,K):boolean): boolean

Tests whether all key-value pairs pass the test implemented by f(value, key).

[lib0/math] Common Math expressions.
import * as math from 'lib0/math'
math.floor
math.ceil
math.abs
math.imul
math.round
math.log10
math.log2
math.log
math.sqrt
math.add(a: number, b: number): number
math.min(a: number, b: number): number
math.max(a: number, b: number): number
math.isNaN
math.pow
math.exp10(exp: number): number

Base 10 exponential function. Returns the value of 10 raised to the power of pow.

math.sign
math.isNegativeZero(n: number): boolean
[lib0/metric] Utility module to convert metric values.
import * as metric from 'lib0/metric'
metric.yotta
metric.zetta
metric.exa
metric.peta
metric.tera
metric.giga
metric.mega
metric.kilo
metric.hecto
metric.deca
metric.deci
metric.centi
metric.milli
metric.micro
metric.nano
metric.pico
metric.femto
metric.atto
metric.zepto
metric.yocto
metric.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' }

[lib0/mutex] Mutual exclude for JavaScript.
import * as mutex from 'lib0/mutex'
mutex.createMutex(): mutex

Creates 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.
  })
})
[lib0/number]
import * as number from 'lib0/number'
number.MAX_SAFE_INTEGER
number.MIN_SAFE_INTEGER
number.LOWEST_INT32
number.HIGHEST_INT32: number
number.isInteger
number.isNaN
number.parseInt
[lib0/object] Utility functions for working with EcmaScript objects.
import * as object from 'lib0/object'
object.create(): Object<string,any>
object.assign

Object.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>): number
object.some(obj: Object<string,any>, f: function(any,string):boolean): boolean
object.isEmpty(obj: Object|undefined)
object.every(obj: Object<string,any>, f: function(any,string):boolean): boolean
object.hasProperty(obj: any, key: string|symbol): boolean

Calls Object.prototype.hasOwnProperty.

object.equalFlat(a: Object<string,any>, b: Object<string,any>): boolean
[lib0/observable] Observable class prototype.
import * 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.

[lib0/pair] Working with value pairs.
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>
[lib0/prng] Fast Pseudo Random Number Generators.
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.DefaultPRNG
prng.create(seed: number): module:prng~PRNG

Create 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): Boolean

Generates a single random bool.

prng.int53(gen: module:prng~PRNG, min: Number, max: Number): Number

Generates a random integer with 53 bit resolution.

prng.uint53(gen: module:prng~PRNG, min: Number, max: Number): Number

Generates a random integer with 53 bit resolution.

prng.int32(gen: module:prng~PRNG, min: Number, max: Number): Number

Generates a random integer with 32 bit resolution.

prng.uint32(gen: module:prng~PRNG, min: Number, max: Number): Number

Generates a random integer with 53 bit resolution.

prng.int31(gen: module:prng~PRNG, min: Number, max: Number): Number
prng.real53(gen: module:prng~PRNG): Number

Generates a random real on [0, 1) with 53 bit resolution.

prng.char(gen: module:prng~PRNG): string

Generates a random character from char code 32 - 126. I.e. Characters, Numbers, special characters, and Space:

prng.letter(gen: module:prng~PRNG): string
prng.word(gen: module:prng~PRNG, minLen: number, maxLen: number): string
prng.utf16Rune(gen: module:prng~PRNG): string

TODO: 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>): T

Returns one element of a given array.

prng.uint8Array(gen: module:prng~PRNG, len: number): Uint8Array
prng.uint16Array(gen: module:prng~PRNG, len: number): Uint16Array
prng.uint32Array(gen: module:prng~PRNG, len: number): Uint32Array
[lib0/promise] Utility helpers to work with promises.
import * 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): boolean

Checks 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.

[lib0/queue]
import * as queue from 'lib0/queue'
new de#QueueNode()
de#next: module:queue.QueueNode|null
new ueue()
tart: module:queue.QueueNode | null
nd: 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
()
[lib0/random] Isomorphic module for true random numbers / buffers / uuids.
import * as random from 'lib0/random'

Attention: falls back to Math.random if the browser does not support crypto.

random.rand
random.uint32
random.uint53
random.oneOf(arr: Array<T>): T
random.uuidv4
[lib0/set] Utility module to work with sets.
import * as set from 'lib0/set'
set.create
set.toArray(set: Set<T>): Array<T>
set.first(set: Set<T>): T
set.from(entries: Iterable<T>): Set<T>
[lib0/sort] Efficient sort implementations.
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): void
sort.quicksort(arr: Array<T>, compare: function(T,T):number): void

This 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.

[lib0/statistics] Utility helpers for generating statistics.
import * as statistics from 'lib0/statistics'
statistics.median(arr: Array<number>): number
statistics.average(arr: Array<number>): number
[lib0/storage] Isomorphic variable storage.
import * as storage from 'lib0/storage'

Uses LocalStorage in the browser and falls back to in-memory storage.

storage.varStorage

This 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.

[lib0/string] Utility module to work with strings.
import * as string from 'lib0/string'
string.fromCharCode
string.fromCodePoint
string.trimLeft(s: string): string
string.fromCamelCase(s: string, separator: string): string
string.utf8ByteLength(str: string): number

Compute the utf8ByteLength

string.utf8TextEncoder
string.encodeUtf8
string.decodeUtf8
string.splice(str: string, index: number, remove: number, insert: string)
[lib0/symbol] Utility module to work with EcmaScript Symbols.
import * as symbol from 'lib0/symbol'
symbol.create(): Symbol

Return fresh symbol.

symbol.isSymbol(s: any): boolean
[lib0/testing] Testing framework with support for generating tests.
import * 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.extensive
testing.envSeed
new testing.TestCase(moduleName: string, testName: string)
testing.TestCase#moduleName: string
testing.TestCase#testName: string
testing.TestCase#resetSeed()
testing.TestCase#prng: prng.PRNG

A PRNG for this test case. Use only this PRNG for randomness to make the test case reproducible.

testing.repetitionTime
testing.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.printDom
testing.printCanvas
testing.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): number

Measure 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): boolean
testing.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)
[lib0/time] Utility module to work with time.
import * as time from 'lib0/time'
time.getDate(): Date

Return current time.

time.getUnixTime(): number

Return current unix time.

time.humanizeDuration(d: number): string

Transform time (in ms) to a human readable format. E.g. 1100 => 1.1s. 60s => 1min. .001 => 10μs.

[lib0/tree] Red-black-tree implementation.
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(): V
tree.Tree#findWithLowerBound(from: K): V
tree.Tree#findWithUpperBound(to: K): V
tree.Tree#iterate(from: K, from: K, f: K)
tree.Tree#find(id: K): V|null
tree.Tree#findNode(id: K): module:tree~N<V>|null
tree.Tree#delete(id: K)
tree.Tree#put()
[lib0/url] Utility module to work with urls.
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>): string
[lib0/webcrypto.browser]
import * as webcrypto.browser from 'lib0/webcrypto.browser'

()
omValues
omValues()
[lib0/webcrypto.node]
import * as webcrypto.node from 'lib0/webcrypto.node'

()
to.subtle: any
omValues
omValues()
[lib0/websocket] Tiny websocket connection handler.
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: boolean

Whether to connect to other peers or not

websocket.WebsocketClient#send(message: any)
websocket.WebsocketClient#destroy()
websocket.WebsocketClient#disconnect()
websocket.WebsocketClient#connect()
### React-Native support 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: ```sh # 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)](https://reactnative.dev/blog/2023/06/21/package-exports-support): ```js const config = { // ... resolver: { unstable_enablePackageExports: true } } ``` ### License [The MIT License](./LICENSE) © Kevin Jahns dmonad-lib0-c7e7806/array.js000066400000000000000000000111651512504553500156320ustar00rootroot00000000000000/** * Utility module to work with Arrays. * * @module array */ import * as set from './set.js' /** * Return the last element of an array. The element must exist * * @template L * @param {ArrayLike} arr * @return {L} */ export const last = arr => arr[arr.length - 1] /** * @template C * @return {Array} */ export const create = () => /** @type {Array} */ ([]) /** * @template D * @param {Array} a * @return {Array} */ export const copy = a => /** @type {Array} */ (a.slice()) /** * Append elements from src to dest * * @template M * @param {Array} dest * @param {Array} src */ export const appendTo = (dest, src) => { for (let i = 0; i < src.length; i++) { dest.push(src[i]) } } /** * Transforms something array-like to an actual Array. * * @function * @template T * @param {ArrayLike|Iterable} arraylike * @return {T} */ export const from = Array.from /** * True iff condition holds on every element in the Array. * * @function * @template {ArrayLike} ARR * * @param {ARR} arr * @param {ARR extends ArrayLike ? ((value:S, index:number, arr:ARR) => boolean) : any} f * @return {boolean} */ export const every = (arr, f) => { for (let i = 0; i < arr.length; i++) { if (!f(arr[i], i, arr)) { return false } } return true } /** * True iff condition holds on some element in the Array. * * @function * @template {ArrayLike} ARR * * @param {ARR} arr * @param {ARR extends ArrayLike ? ((value:S, index:number, arr:ARR) => boolean) : never} f * @return {boolean} */ export const some = (arr, f) => { for (let i = 0; i < arr.length; i++) { if (f(arr[i], i, arr)) { return true } } return false } /** * @template ELEM * * @param {ArrayLike} a * @param {ArrayLike} b * @return {boolean} */ export const equalFlat = (a, b) => a.length === b.length && every(a, (item, index) => item === b[index]) /** * @template ELEM * @param {Array>} arr * @return {Array} */ export const flatten = arr => fold(arr, /** @type {Array} */ ([]), (acc, val) => acc.concat(val)) /** * @template T * @param {number} len * @param {function(number, Array):T} f * @return {Array} */ export const unfold = (len, f) => { const array = new Array(len) for (let i = 0; i < len; i++) { array[i] = f(i, array) } return array } /** * @template T * @template RESULT * @param {Array} arr * @param {RESULT} seed * @param {function(RESULT, T, number):RESULT} folder */ export const fold = (arr, seed, folder) => arr.reduce(folder, seed) export const isArray = Array.isArray /** * @template T * @param {Array} arr * @return {Array} */ export const unique = arr => from(set.from(arr)) /** * @template T * @template M * @param {ArrayLike} arr * @param {function(T):M} mapper * @return {Array} */ export const uniqueBy = (arr, mapper) => { /** * @type {Set} */ const happened = set.create() /** * @type {Array} */ const result = [] for (let i = 0; i < arr.length; i++) { const el = arr[i] const mapped = mapper(el) if (!happened.has(mapped)) { happened.add(mapped) result.push(el) } } return result } /** * @template {ArrayLike} ARR * @template {function(ARR extends ArrayLike ? T : never, number, ARR):any} MAPPER * @param {ARR} arr * @param {MAPPER} mapper * @return {Array} */ export const map = (arr, mapper) => { /** * @type {Array} */ const res = Array(arr.length) for (let i = 0; i < arr.length; i++) { res[i] = mapper(/** @type {any} */ (arr[i]), i, /** @type {any} */ (arr)) } return /** @type {any} */ (res) } /** * This function bubble-sorts a single item to the correct position. The sort happens in-place and * might be useful to ensure that a single item is at the correct position in an otherwise sorted * array. * * @example * const arr = [3, 2, 5] * arr.sort((a, b) => a - b) * arr // => [2, 3, 5] * arr.splice(1, 0, 7) * array.bubbleSortItem(arr, 1, (a, b) => a - b) * arr // => [2, 3, 5, 7] * * @template T * @param {Array} arr * @param {number} i * @param {(a:T,b:T) => number} compareFn */ export const bubblesortItem = (arr, i, compareFn) => { const n = arr[i] let j = i // try to sort to the right while (j + 1 < arr.length && compareFn(n, arr[j + 1]) > 0) { arr[j] = arr[j + 1] arr[++j] = n } if (i === j && j > 0) { // no change yet // sort to the left while (j > 0 && compareFn(arr[j - 1], n) > 0) { arr[j] = arr[j - 1] arr[--j] = n } } return j } dmonad-lib0-c7e7806/array.test.js000066400000000000000000000074611512504553500166140ustar00rootroot00000000000000import * as array from './array.js' import * as t from './testing.js' import * as prng from './prng.js' /** * @param {t.TestCase} _tc */ export const testIsarrayPerformance = _tc => { const N = 100000 /** * @type {Array} */ const objects = [] for (let i = 0; i < N; i++) { if (i % 2 === 0) { objects.push([i]) } else { objects.push({ i }) } } const timeConstructor = t.measureTime('constructor check', () => { let collectedArrays = 0 objects.forEach(obj => { if (obj.constructor === Array) { collectedArrays++ } }) t.assert(collectedArrays === N / 2) }) const timeIsarray = t.measureTime('Array.isArray', () => { let collectedArrays = 0 objects.forEach(obj => { if (array.isArray(obj)) { collectedArrays++ } }) t.assert(collectedArrays === N / 2) }) t.assert(timeIsarray < timeConstructor * 2, 'Expecting that isArray is not much worse than a constructor check') } /** * @param {t.TestCase} _tc */ export const testAppend = _tc => { const arr = [1, 2, 3] array.appendTo(arr, array.copy(arr)) t.compareArrays(arr, [1, 2, 3, 1, 2, 3]) } /** * @param {t.TestCase} _tc */ export const testBasic = _tc => { const arr = array.create() array.appendTo(arr, array.from([1])) t.assert(array.last(arr) === 1) } /** * @param {t.TestCase} _tc */ export const testflatten = _tc => { const arr = [[1, 2, 3], [4]] t.compareArrays(array.flatten(arr), [1, 2, 3, 4]) } /** * @param {t.TestCase} _tc */ export const testFolding = _tc => { /** * @param {number} n */ const testcase = n => { // We mess with the seed (+/-1) to catch some edge cases without changing the result const result = -1 + array.fold(array.unfold(n, i => i), 1, (accumulator, item, index) => { t.assert(accumulator === index + 1) t.assert(accumulator === item + 1) return accumulator + 1 }) t.assert(result === n) } testcase(0) testcase(1) testcase(100) } /** * @param {t.TestCase} _tc */ export const testEvery = _tc => { const arr = [1, 2, 3] t.assert(array.every(arr, x => x <= 3)) t.assert(!array.every(arr, x => x < 3)) t.assert(array.some(arr, x => x === 2)) t.assert(!array.some(arr, x => x === 42)) } /** * @param {t.TestCase} _tc */ export const testIsArray = _tc => { t.assert(array.isArray([])) t.assert(array.isArray([1])) t.assert(array.isArray(Array.from(new Set([3])))) t.assert(!array.isArray(1)) t.assert(!array.isArray(0)) t.assert(!array.isArray('')) } /** * @param {t.TestCase} _tc */ export const testUnique = _tc => { t.compare([1, 2], array.unique([1, 2, 1, 2, 2, 1])) t.compare([], array.unique([])) t.compare([{ el: 1 }], array.uniqueBy([{ el: 1 }, { el: 1 }], o => o.el)) t.compare([], array.uniqueBy([], o => o)) } /** * @param {t.TestCase} tc */ export const testBubblesortItemEdgeCases = tc => { // does not throw.. array.bubblesortItem([1], 0, (a, b) => a - b) array.bubblesortItem([2, 1], 1, (a, b) => a - b) array.bubblesortItem([2, 1], 0, (a, b) => a - b) } /** * @param {t.TestCase} tc */ export const testRepeatBubblesortItem = tc => { const arr = Array.from(prng.uint8Array(tc.prng, 10)) arr.sort((a, b) => a - b) const newItem = prng.uint32(tc.prng, 0, 256) const pos = prng.uint32(tc.prng, 0, arr.length) arr.splice(pos, newItem) const arrCopySorted = arr.slice().sort((a, b) => a - b) array.bubblesortItem(arr, pos, (a, b) => a - b) t.compare(arr, arrCopySorted) } /** * @param {t.TestCase} tc */ export const testRepeatBubblesort = tc => { const arr = Array.from(prng.uint8Array(tc.prng, 10)) const arrCopySorted = arr.slice().sort((a, b) => a - b) for (let i = arr.length - 1; i >= 0; i--) { while (array.bubblesortItem(arr, i, (a, b) => a - b) !== i) { /* nop */ } } t.compare(arr, arrCopySorted) } dmonad-lib0-c7e7806/bin/000077500000000000000000000000001512504553500147225ustar00rootroot00000000000000dmonad-lib0-c7e7806/bin/0ecdsa-generate-keypair.js000077500000000000000000000010511512504553500216510ustar00rootroot00000000000000#!/usr/bin/env node import * as ecdsa from 'lib0/crypto/ecdsa' import * as json from 'lib0/json' import * as env from 'lib0/environment' const prefix = env.getConf('name') const keypair = await ecdsa.generateKeyPair({ extractable: true }) const privateJwk = json.stringify(await ecdsa.exportKeyJwk(keypair.privateKey)) const publicJwk = json.stringify(await ecdsa.exportKeyJwk(keypair.publicKey)) console.log(` ${prefix ? prefix.toUpperCase() + '_' : ''}PUBLIC_KEY=${publicJwk} ${prefix ? prefix.toUpperCase() + '_' : ''}PRIVATE_KEY=${privateJwk} `) dmonad-lib0-c7e7806/bin/0serve.js000077500000000000000000000055131512504553500164730ustar00rootroot00000000000000#!/usr/bin/env node /** * Simple http server implementation. * Optionally, you may set `DEBUG_BROWSER` environment variable to use a different browser to debug * web apps. */ import * as http from 'http' import * as path from 'path' import * as fs from 'fs' import * as env from '../environment.js' import * as number from '../number.js' import * as logging from 'lib0/logging' const host = env.getParam('--host', 'localhost') const port = number.parseInt(env.getParam('--port', '8000')) const paramOpenFile = env.getParam('-o', '') const debugBrowser = env.getConf('DEBUG_BROWSER') /** * @type {Object} */ const types = { html: 'text/html', css: 'text/css', js: 'application/javascript', mjs: 'application/javascript', png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', json: 'application/json', xml: 'application/xml', wasm: 'application/wasm' } const root = path.normalize(path.resolve('./')) const server = http.createServer((req, res) => { const url = (req.url || '/index.html').split('?')[0] logging.print(logging.ORANGE, logging.BOLD, req.method || '', ' ', logging.GREY, logging.UNBOLD, url) const extension = path.extname(url).slice(1) /** * @type {string} */ const type = (extension && types[extension]) || types.html const supportedExtension = Boolean(type) if (!supportedExtension) { res.writeHead(404, { 'Content-Type': 'text/html' }) res.end('404: File not found') return } let fileName = url if (url === '/') fileName = 'index.html' else if (!extension) { try { fs.accessSync(path.join(root, url + '.html'), fs.constants.F_OK) fileName = url + '.html' } catch (e) { fileName = path.join(url, 'index.html') } } const filePath = path.join(root, fileName) const isPathUnderRoot = path .normalize(path.resolve(filePath)) .startsWith(root) if (!isPathUnderRoot) { res.writeHead(404, { 'Content-Type': 'text/html' }) res.end('404: File not found') logging.print(logging.RED, logging.BOLD, 'Not Found: ', logging.GREY, logging.UNBOLD, url) return } fs.readFile(filePath, (err, data) => { if (err) { logging.print(logging.RED, logging.BOLD, 'Cannot read file: ', logging.GREY, logging.UNBOLD, url) res.writeHead(404, { 'Content-Type': 'text/html' }) res.end('404: File not found') } else { res.writeHead(200, { 'Content-Type': type }) res.end(data) } }) }) server.listen(port, host, () => { logging.print(logging.BOLD, logging.ORANGE, `Server is running on http://${host}:${port}`) if (paramOpenFile) { const start = debugBrowser || (process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open') import('child_process').then(cp => { cp.exec(`${start} http://${host}:${port}/${paramOpenFile}`) }) } }) dmonad-lib0-c7e7806/bin/gendocs.js000066400000000000000000000110011512504553500166730ustar00rootroot00000000000000#!/usr/bin/env node // @ts-ignore import jsdoc from 'jsdoc-api' import * as fs from 'fs' const firstTagContentRegex = /<\w>([^<]+)<\/\w>([^]*)/ const jsdocReturnRegex = /\* @return {(.*)}/ const jsdocTypeRegex = /\* @type {(.*)}/ const files = fs.readdirSync('./').filter(file => /(?/g /** * @param {string} s */ const toSafeHtml = s => s.replace(_ltregex, '<').replace(_rtregex, '>') const READMEcontent = fs.readFileSync('./README.md', 'utf8') jsdoc.explain({ files, configure: '.jsdoc.json' }).then(/** @param {Array} json */ json => { const strBuilder = [] /** * @type {Object, name: string, description: string }>} */ const modules = {} json.forEach(item => { if (item.meta && item.meta.filename) { const mod = modules[item.meta.filename] || (modules[item.meta.filename] = { items: [], name: item.meta.filename.slice(0, -3), description: '' }) if (item.kind === 'module') { mod.name = item.name mod.description = item.description || '' } else { mod.items.push(item) } } }) /** * @type {Object} */ const classDescriptions = {} for (const fileName in modules) { const mod = modules[fileName] const items = mod.items const desc = firstTagContentRegex.exec(mod.description) const descHead = desc ? desc[1] : '' const descRest = desc ? desc[2] : '' strBuilder.push(`
[lib0/${mod.name}] ${descHead}`) strBuilder.push(`
import * as ${mod.name} from 'lib0/${fileName.slice(0, -3)}'
`) if (descRest.length > 0) { strBuilder.push(descRest) } strBuilder.push('
') for (let i = 0; i < items.length; i++) { const item = items[i] if (!item.ignore && item.scope !== 'inner' && item.name[0] !== '_' && item.longname.indexOf('~') < 0) { // strBuilder.push(JSON.stringify(item)) // output json for debugging switch (item.kind) { case 'class': { if (item.params == null) { classDescriptions[item.longname] = item.classdesc break } } // eslint-disable-next-line case 'constant': { if (item.params == null && item.returns == null) { const typeEval = jsdocTypeRegex.exec(item.comment) strBuilder.push(`${item.longname.slice(7)}${typeEval ? (': ' + toSafeHtml(typeEval[1])) : ''}
`) if (item.description) { strBuilder.push(`
${item.description}
`) } break } } // eslint-disable-next-line case 'function': { /** * @param {string} name */ const getOriginalParamTypeDecl = name => { const regval = new RegExp('@param {(.*)} \\[?' + name + '\\]?[^\\w]*').exec(item.comment) return regval ? regval[1] : null } if (item.params == null && item.returns == null) { break } const paramVal = (item.params || []).map(/** @param {any} ret */ ret => `${ret.name}: ${getOriginalParamTypeDecl(ret.name) || ret.type.names.join('|')}`).join(', ') const evalReturnRegex = jsdocReturnRegex.exec(item.comment) const returnVal = evalReturnRegex ? `: ${evalReturnRegex[1]}` : (item.returns ? item.returns.map(/** @param {any} r */ r => r.type.names.join('|')).join('|') : '') strBuilder.push(`${item.kind === 'class' ? 'new ' : ''}${item.longname.slice(7)}(${toSafeHtml(paramVal)})${toSafeHtml(returnVal)}
`) const desc = item.description || item.classdesc || classDescriptions[item.longname] || null if (desc) { strBuilder.push(`
${desc}
`) } break } case 'member': { if (item.type) { strBuilder.push(`${item.longname.slice(7)}: ${toSafeHtml(/** @type {RegExpExecArray} */ (jsdocTypeRegex.exec(item.comment))[1])}
`) if (item.description) { strBuilder.push(`
${item.description}
`) } } } } } } strBuilder.push('
') strBuilder.push('
') } const replaceReadme = READMEcontent.replace(/
[^]*<\/details>/, strBuilder.join('\n')) fs.writeFileSync('./README.md', replaceReadme) }) dmonad-lib0-c7e7806/bin/gentesthtml.js000077500000000000000000000052421512504553500176240ustar00rootroot00000000000000#!/usr/bin/env node import * as fs from 'fs' import * as object from '../object.js' import * as env from '../environment.js' const script = env.getParam('--script', './test.js') const includeDeps = env.getParam('--include-dependencies', '').split(',').filter(d => d.length) const customImportsPath = env.getParam('--custom-imports', '') /** * @type {Object} */ const exports = {} /** * @type {Object>} */ const scopes = {} /** * @param {any} v * @param {string} k * @param {string} pkgName * @param {string} pathPrefix * @param {Object} importMap */ const extractModMap = (v, k, pkgName, pathPrefix, importMap) => { if (k[0] !== '.') return if (typeof v === 'object') { extractModMap(v.browser || v.import || v.module || v.default, k, pkgName, pathPrefix, importMap) } else if (v && v[0] === '.') { importMap[pkgName + k.slice(1)] = pathPrefix + v.slice(1) } } /** * @param {string} s */ const _maybeAddRelPrefix = s => (s[0] !== '.' ? './' : '') + s /** * @param {any} pkgJson * @param {string} pathPrefix * @param {Object} importMap */ const readPkg = (pkgJson, pathPrefix, importMap) => { if (pkgJson.exports == null && pkgJson.main != null) { importMap[pkgJson.name] = pathPrefix + _maybeAddRelPrefix(pkgJson.main).slice(1) } object.forEach(pkgJson.exports, (v, k) => extractModMap(v, k, pkgJson.name, pathPrefix, importMap)) object.forEach(pkgJson.dependencies, (_v, depName) => { const nextImportMap = pathPrefix === '.' ? exports : (scopes[pathPrefix + '/'] || (scopes[pathPrefix + '/'] = {})) const prefix = `./node_modules/${depName}` const depPkgJson = JSON.parse(fs.readFileSync(prefix + '/package.json', { encoding: 'utf8' })) readPkg(depPkgJson, prefix, nextImportMap) }) } const rootPkgJson = JSON.parse(fs.readFileSync('./package.json', { encoding: 'utf8' })) readPkg(rootPkgJson, '.', exports) includeDeps.forEach(depName => { const prefix = `./node_modules/${depName}` const depPkgJson = JSON.parse(fs.readFileSync(`${prefix}/package.json`, { encoding: 'utf8' })) readPkg(depPkgJson, prefix, exports) }) const customImports = {} if (customImportsPath !== '') { object.assign(customImports, JSON.parse(fs.readFileSync(customImportsPath, { encoding: 'utf8' }))) } const testHtml = ` Testing ${rootPkgJson.name} ` console.log(testHtml) dmonad-lib0-c7e7806/binary.js000066400000000000000000000037661512504553500160100ustar00rootroot00000000000000/* eslint-env browser */ /** * Binary data constants. * * @module binary */ /** * n-th bit activated. * * @type {number} */ export const BIT1 = 1 export const BIT2 = 2 export const BIT3 = 4 export const BIT4 = 8 export const BIT5 = 16 export const BIT6 = 32 export const BIT7 = 64 export const BIT8 = 128 export const BIT9 = 256 export const BIT10 = 512 export const BIT11 = 1024 export const BIT12 = 2048 export const BIT13 = 4096 export const BIT14 = 8192 export const BIT15 = 16384 export const BIT16 = 32768 export const BIT17 = 65536 export const BIT18 = 1 << 17 export const BIT19 = 1 << 18 export const BIT20 = 1 << 19 export const BIT21 = 1 << 20 export const BIT22 = 1 << 21 export const BIT23 = 1 << 22 export const BIT24 = 1 << 23 export const BIT25 = 1 << 24 export const BIT26 = 1 << 25 export const BIT27 = 1 << 26 export const BIT28 = 1 << 27 export const BIT29 = 1 << 28 export const BIT30 = 1 << 29 export const BIT31 = 1 << 30 export const BIT32 = 1 << 31 /** * First n bits activated. * * @type {number} */ export const BITS0 = 0 export const BITS1 = 1 export const BITS2 = 3 export const BITS3 = 7 export const BITS4 = 15 export const BITS5 = 31 export const BITS6 = 63 export const BITS7 = 127 export const BITS8 = 255 export const BITS9 = 511 export const BITS10 = 1023 export const BITS11 = 2047 export const BITS12 = 4095 export const BITS13 = 8191 export const BITS14 = 16383 export const BITS15 = 32767 export const BITS16 = 65535 export const BITS17 = BIT18 - 1 export const BITS18 = BIT19 - 1 export const BITS19 = BIT20 - 1 export const BITS20 = BIT21 - 1 export const BITS21 = BIT22 - 1 export const BITS22 = BIT23 - 1 export const BITS23 = BIT24 - 1 export const BITS24 = BIT25 - 1 export const BITS25 = BIT26 - 1 export const BITS26 = BIT27 - 1 export const BITS27 = BIT28 - 1 export const BITS28 = BIT29 - 1 export const BITS29 = BIT30 - 1 export const BITS30 = BIT31 - 1 /** * @type {number} */ export const BITS31 = 0x7FFFFFFF /** * @type {number} */ export const BITS32 = 0xFFFFFFFF dmonad-lib0-c7e7806/binary.test.js000066400000000000000000000011461512504553500167540ustar00rootroot00000000000000import * as binary from './binary.js' import * as t from './testing.js' /** * @param {t.TestCase} tc */ export const testBitx = tc => { for (let i = 1; i <= 32; i++) { // @ts-ignore t.assert(binary[`BIT${i}`] === (1 << (i - 1)), `BIT${i}=${1 << (i - 1)}`) } } /** * @param {t.TestCase} tc */ export const testBitsx = tc => { t.assert(binary.BITS0 === 0) for (let i = 1; i < 32; i++) { const expected = ((1 << i) - 1) >>> 0 // @ts-ignore const have = binary[`BITS${i}`] t.assert(have === expected, `BITS${i}=${have}=${expected}`) } t.assert(binary.BITS32 === 0xFFFFFFFF) } dmonad-lib0-c7e7806/broadcastchannel.js000066400000000000000000000057411512504553500200120ustar00rootroot00000000000000/* eslint-env browser */ /** * Helpers for cross-tab communication using broadcastchannel with LocalStorage fallback. * * ```js * // 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' * ``` * * @module broadcastchannel */ // @todo before next major: use Uint8Array instead as buffer object import * as map from './map.js' import * as set from './set.js' import * as buffer from './buffer.js' import * as storage from './storage.js' /** * @typedef {Object} Channel * @property {Set} Channel.subs * @property {any} Channel.bc */ /** * @type {Map} */ const channels = new Map() /* c8 ignore start */ class LocalStoragePolyfill { /** * @param {string} room */ constructor (room) { this.room = room /** * @type {null|function({data:Uint8Array}):void} */ this.onmessage = null /** * @param {any} e */ this._onChange = e => e.key === room && this.onmessage !== null && this.onmessage({ data: buffer.fromBase64(e.newValue || '') }) storage.onChange(this._onChange) } /** * @param {ArrayBuffer} buf */ postMessage (buf) { storage.varStorage.setItem(this.room, buffer.toBase64(buffer.createUint8ArrayFromArrayBuffer(buf))) } close () { storage.offChange(this._onChange) } } /* c8 ignore stop */ // Use BroadcastChannel or Polyfill /* c8 ignore next */ const BC = typeof BroadcastChannel === 'undefined' ? LocalStoragePolyfill : BroadcastChannel /** * @param {string} room * @return {Channel} */ const getChannel = room => map.setIfUndefined(channels, room, () => { const subs = set.create() const bc = new BC(room) /** * @param {{data:ArrayBuffer}} e */ /* c8 ignore next */ bc.onmessage = e => subs.forEach(sub => sub(e.data, 'broadcastchannel')) return { bc, subs } }) /** * Subscribe to global `publish` events. * * @function * @param {string} room * @param {function(any, any):any} f */ export const subscribe = (room, f) => { getChannel(room).subs.add(f) return f } /** * Unsubscribe from `publish` global events. * * @function * @param {string} room * @param {function(any, any):any} f */ export const unsubscribe = (room, f) => { const channel = getChannel(room) const unsubscribed = channel.subs.delete(f) if (unsubscribed && channel.subs.size === 0) { channel.bc.close() channels.delete(room) } return unsubscribed } /** * Publish data to all subscribers (including subscribers on this tab) * * @function * @param {string} room * @param {any} data * @param {any} [origin] */ export const publish = (room, data, origin = null) => { const c = getChannel(room) c.bc.postMessage(data) c.subs.forEach(sub => sub(data, origin)) } dmonad-lib0-c7e7806/broadcastchannel.test.js000066400000000000000000000012321512504553500207570ustar00rootroot00000000000000import * as t from './testing.js' import * as bc from './broadcastchannel.js' /** * @param {t.TestCase} tc */ export const testBroadcastChannel = tc => { bc.publish('test', 'test1', tc) /** * @type {any} */ const messages = [] const sub = bc.subscribe('test', (data, origin) => { messages.push({ data, origin }) }) t.compare(messages, []) bc.publish('test', 'test2', tc) bc.publish('test', 'test3') t.compare(messages, [{ data: 'test2', origin: tc }, { data: 'test3', origin: null }]) bc.unsubscribe('test', sub) bc.publish('test', 'test4') t.compare(messages, [{ data: 'test2', origin: tc }, { data: 'test3', origin: null }]) } dmonad-lib0-c7e7806/buffer.js000066400000000000000000000100451512504553500157610ustar00rootroot00000000000000/** * Utility functions to work with buffers (Uint8Array). * * @module buffer */ import * as string from './string.js' import * as env from './environment.js' import * as array from './array.js' import * as math from './math.js' import * as encoding from './encoding.js' import * as decoding from './decoding.js' /** * @param {number} len */ export const createUint8ArrayFromLen = len => new Uint8Array(len) /** * Create Uint8Array with initial content from buffer * * @param {ArrayBuffer} buffer * @param {number} byteOffset * @param {number} length */ export const createUint8ArrayViewFromArrayBuffer = (buffer, byteOffset, length) => new Uint8Array(buffer, byteOffset, length) /** * Create Uint8Array with initial content from buffer * * @param {ArrayBuffer} buffer */ export const createUint8ArrayFromArrayBuffer = buffer => new Uint8Array(buffer) /* c8 ignore start */ /** * @param {Uint8Array} bytes * @return {string} */ const toBase64Browser = bytes => { let s = '' for (let i = 0; i < bytes.byteLength; i++) { s += string.fromCharCode(bytes[i]) } // eslint-disable-next-line no-undef return btoa(s) } /* c8 ignore stop */ /** * @param {Uint8Array} bytes * @return {string} */ const toBase64Node = bytes => Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString('base64') /* c8 ignore start */ /** * @param {string} s * @return {Uint8Array} */ const fromBase64Browser = s => { // eslint-disable-next-line no-undef const a = atob(s) const bytes = createUint8ArrayFromLen(a.length) for (let i = 0; i < a.length; i++) { bytes[i] = a.charCodeAt(i) } return bytes } /* c8 ignore stop */ /** * @param {string} s */ const fromBase64Node = s => { const buf = Buffer.from(s, 'base64') return createUint8ArrayViewFromArrayBuffer(buf.buffer, buf.byteOffset, buf.byteLength) } /* c8 ignore next */ export const toBase64 = env.isBrowser ? toBase64Browser : toBase64Node /* c8 ignore next */ export const fromBase64 = env.isBrowser ? fromBase64Browser : fromBase64Node /** * Implements base64url - see https://datatracker.ietf.org/doc/html/rfc4648#section-5 * @param {Uint8Array} buf */ export const toBase64UrlEncoded = buf => toBase64(buf).replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '') /** * @param {string} base64 */ export const fromBase64UrlEncoded = base64 => fromBase64(base64.replaceAll('-', '+').replaceAll('_', '/')) /** * Base64 is always a more efficient choice. This exists for utility purposes only. * * @param {Uint8Array} buf */ export const toHexString = buf => array.map(buf, b => b.toString(16).padStart(2, '0')).join('') /** * Note: This function expects that the hex doesn't start with 0x.. * * @param {string} hex */ export const fromHexString = hex => { const hlen = hex.length const buf = new Uint8Array(math.ceil(hlen / 2)) for (let i = 0; i < hlen; i += 2) { buf[buf.length - i / 2 - 1] = Number.parseInt(hex.slice(hlen - i - 2, hlen - i), 16) } return buf } /** * Copy the content of an Uint8Array view to a new ArrayBuffer. * * @param {Uint8Array} uint8Array * @return {Uint8Array} */ export const copyUint8Array = uint8Array => { const newBuf = createUint8ArrayFromLen(uint8Array.byteLength) newBuf.set(uint8Array) return newBuf } /** * Encode anything as a UInt8Array. It's a pun on typescripts's `any` type. * See encoding.writeAny for more information. * * @param {any} data * @return {Uint8Array} */ export const encodeAny = data => encoding.encode(encoder => encoding.writeAny(encoder, data)) /** * Decode an any-encoded value. * * @param {Uint8Array} buf * @return {any} */ export const decodeAny = buf => decoding.readAny(decoding.createDecoder(buf)) /** * Shift Byte Array {N} bits to the left. Does not expand byte array. * * @param {Uint8Array} bs * @param {number} N should be in the range of [0-7] */ export const shiftNBitsLeft = (bs, N) => { if (N === 0) return bs bs = new Uint8Array(bs) bs[0] <<= N for (let i = 1; i < bs.length; i++) { bs[i - 1] |= bs[i] >>> (8 - N) bs[i] <<= N } return bs } dmonad-lib0-c7e7806/buffer.test.js000066400000000000000000000026131512504553500167410ustar00rootroot00000000000000import * as t from './testing.js' import * as buffer from './buffer.js' import * as prng from './prng.js' /** * @param {t.TestCase} tc * @param {function(Uint8Array):string} encoder * @param {function(string):Uint8Array} decoder */ const testEncodingHelper = (tc, encoder, decoder) => { const gen = tc.prng const barr = prng.uint8Array(gen, prng.uint32(gen, 0, 47)) const copied = buffer.copyUint8Array(barr) const encoded = encoder(barr) t.assert(encoded.constructor === String) const decoded = decoder(encoded) t.assert(decoded.constructor === Uint8Array) t.assert(decoded.byteLength === barr.byteLength) for (let i = 0; i < barr.length; i++) { t.assert(barr[i] === decoded[i]) } t.compare(copied, decoded) } /** * @param {t.TestCase} tc */ export const testRepeatBase64urlEncoding = tc => { testEncodingHelper(tc, buffer.toBase64UrlEncoded, buffer.fromBase64UrlEncoded) } /** * @param {t.TestCase} tc */ export const testRepeatBase64Encoding = tc => { testEncodingHelper(tc, buffer.toBase64, buffer.fromBase64) } /** * @param {t.TestCase} tc */ export const testRepeatHexEncoding = tc => { testEncodingHelper(tc, buffer.toHexString, buffer.fromHexString) } /** * @param {t.TestCase} _tc */ export const testAnyEncoding = _tc => { const obj = { val: 1, arr: [1, 2], str: '409231dtrnä' } const res = buffer.decodeAny(buffer.encodeAny(obj)) t.compare(obj, res) } dmonad-lib0-c7e7806/cache.js000066400000000000000000000076021512504553500155600ustar00rootroot00000000000000/* eslint-env browser */ /** * An implementation of a map which has keys that expire. * * @module cache */ import * as list from './list.js' import * as map from './map.js' import * as time from './time.js' /** * @template K, V * * @implements {list.ListNode} */ class Entry { /** * @param {K} key * @param {V | Promise} val */ constructor (key, val) { /** * @type {this | null} */ this.prev = null /** * @type {this | null} */ this.next = null this.created = time.getUnixTime() this.val = val this.key = key } } /** * @template K, V */ export class Cache { /** * @param {number} timeout */ constructor (timeout) { this.timeout = timeout /** * @type list.List> */ this._q = list.create() /** * @type {Map>} */ this._map = map.create() } } /** * @template K, V * * @param {Cache} cache * @return {number} Returns the current timestamp */ export const removeStale = cache => { const now = time.getUnixTime() const q = cache._q while (q.start && now - q.start.created > cache.timeout) { cache._map.delete(q.start.key) list.popFront(q) } return now } /** * @template K, V * * @param {Cache} cache * @param {K} key * @param {V} value */ export const set = (cache, key, value) => { const now = removeStale(cache) const q = cache._q const n = cache._map.get(key) if (n) { list.removeNode(q, n) list.pushEnd(q, n) n.created = now n.val = value } else { const node = new Entry(key, value) list.pushEnd(q, node) cache._map.set(key, node) } } /** * @template K, V * * @param {Cache} cache * @param {K} key * @return {Entry | undefined} */ const getNode = (cache, key) => { removeStale(cache) const n = cache._map.get(key) if (n) { return n } } /** * @template K, V * * @param {Cache} cache * @param {K} key * @return {V | undefined} */ export const get = (cache, key) => { const n = getNode(cache, key) return n && !(n.val instanceof Promise) ? n.val : undefined } /** * @template K, V * * @param {Cache} cache * @param {K} key */ export const refreshTimeout = (cache, key) => { const now = time.getUnixTime() const q = cache._q const n = cache._map.get(key) if (n) { list.removeNode(q, n) list.pushEnd(q, n) n.created = now } } /** * Works well in conjunktion with setIfUndefined which has an async init function. * Using getAsync & setIfUndefined ensures that the init function is only called once. * * @template K, V * * @param {Cache} cache * @param {K} key * @return {V | Promise | undefined} */ export const getAsync = (cache, key) => { const n = getNode(cache, key) return n ? n.val : undefined } /** * @template K, V * * @param {Cache} cache * @param {K} key */ export const remove = (cache, key) => { const n = cache._map.get(key) if (n) { list.removeNode(cache._q, n) cache._map.delete(key) return n.val && !(n.val instanceof Promise) ? n.val : undefined } } /** * @template K, V * * @param {Cache} cache * @param {K} key * @param {function():Promise} init * @param {boolean} removeNull Optional argument that automatically removes values that resolve to null/undefined from the cache. * @return {Promise | V} */ export const setIfUndefined = (cache, key, init, removeNull = false) => { removeStale(cache) const q = cache._q const n = cache._map.get(key) if (n) { return n.val } else { const p = init() const node = new Entry(key, p) list.pushEnd(q, node) cache._map.set(key, node) p.then(v => { if (p === node.val) { node.val = v } if (removeNull && v == null) { remove(cache, key) } }) return p } } /** * @param {number} timeout */ export const create = timeout => new Cache(timeout) dmonad-lib0-c7e7806/cache.test.js000066400000000000000000000053341512504553500165360ustar00rootroot00000000000000import * as t from './testing.js' import * as cache from './cache.js' import * as promise from './promise.js' /** * @param {t.TestCase} tc */ export const testCache = async tc => { /** * @type {cache.Cache} */ const c = cache.create(50) cache.set(c, 'a', '1') t.assert(cache.get(c, 'a') === '1') t.assert(await cache.getAsync(c, 'a') === '1') const p = cache.setIfUndefined(c, 'b', () => promise.resolveWith('2')) const q = cache.setIfUndefined(c, 'b', () => promise.resolveWith('3')) t.assert(p === q) t.assert(cache.get(c, 'b') == null) t.assert(cache.getAsync(c, 'b') === p) t.assert(await p === '2') t.assert(cache.get(c, 'b') === '2') t.assert(cache.getAsync(c, 'b') === '2') await promise.wait(5) // keys shouldn't be timed out yet t.assert(cache.get(c, 'a') === '1') t.assert(cache.get(c, 'b') === '2') /** * @type {any} */ const m = c._map const aTimestamp1 = m.get('a').created const bTimestamp1 = m.get('b').created // write new values and check later if the creation-timestamp was updated cache.set(c, 'a', '11') cache.set(c, 'b', '22') await promise.wait(5) // keys should be updated and not timed out. Hence the creation time should be updated t.assert(cache.get(c, 'a') === '11') t.assert(cache.get(c, 'b') === '22') cache.set(c, 'a', '11') cache.set(c, 'b', '22') // timestamps should be updated t.assert(aTimestamp1 !== m.get('a').created) t.assert(bTimestamp1 !== m.get('b').created) await promise.wait(60) // now the keys should be timed-out t.assert(cache.get(c, 'a') == null) t.assert(cache.getAsync(c, 'b') == null) t.assert(c._map.size === 0) t.assert(c._q.start === null && c._q.end === null) // test edge case of setIfUndefined const xp = cache.setIfUndefined(c, 'a', () => promise.resolve('x')) cache.set(c, 'a', 'y') await xp // we override the Entry.val property in cache when p resolves. However, we must prevent that when the value is overriden before p is resolved. t.assert(cache.get(c, 'a') === 'y') // test that we can remove properties cache.remove(c, 'a') cache.remove(c, 'does not exist') // remove a non-existent property to achieve full test-coverage t.assert(cache.get(c, 'a') === undefined) // test that the optional property in setifUndefined works const yp = cache.setIfUndefined(c, 'a', () => promise.resolveWith(null), true) t.assert(await yp === null) t.assert(cache.get(c, 'a') === undefined) // check manual updating of timeout cache.set(c, 'a', '3') const ts1 = m.get('a').created await promise.wait(30) cache.refreshTimeout(c, 'a') const ts2 = m.get('a').created t.assert(ts1 !== ts2) cache.refreshTimeout(c, 'x') // for full test coverage t.assert(m.get('x') == null) } dmonad-lib0-c7e7806/component.js000066400000000000000000000310601512504553500165120ustar00rootroot00000000000000/* eslint-env browser */ /** * Web components. * * @module component */ import * as dom from './dom.js' import * as diff from './diff.js' import * as object from './object.js' import * as json from './json.js' import * as string from './string.js' import * as array from './array.js' import * as number from './number.js' import * as func from './function.js' /** * @type {CustomElementRegistry} */ export const registry = customElements /** * @param {string} name * @param {any} constr * @param {ElementDefinitionOptions} [opts] */ export const define = (name, constr, opts) => registry.define(name, constr, opts) /** * @param {string} name * @return {Promise} */ export const whenDefined = name => registry.whenDefined(name) const upgradedEventName = 'upgraded' const connectedEventName = 'connected' const disconnectedEventName = 'disconnected' /** * @template S */ export class Lib0Component extends HTMLElement { /** * @param {S} [state] */ constructor (state) { super() /** * @type {S|null} */ this.state = /** @type {any} */ (state) /** * @type {any} */ this._internal = {} } /** * @param {S} _state * @param {boolean} [_forceStateUpdate] Force that the state is rerendered even if state didn't change */ setState (_state, _forceStateUpdate = true) {} /** * @param {any} _stateUpdate */ updateState (_stateUpdate) { } } /** * @param {any} val * @param {"json"|"string"|"number"} type * @return {string} */ const encodeAttrVal = (val, type) => { if (type === 'json') { val = json.stringify(val) } return val + '' } /** * @param {any} val * @param {"json"|"string"|"number"|"bool"} type * @return {any} */ const parseAttrVal = (val, type) => { switch (type) { case 'json': return json.parse(val) case 'number': return Number.parseFloat(val) case 'string': return val case 'bool': return val != null default: return null } } /** * @template S * @typedef {Object} CONF * @property {string?} [CONF.template] Template for the shadow dom. * @property {string} [CONF.style] shadow dom style. Is only used when * `CONF.template` is defined * @property {S} [CONF.state] Initial component state. * @property {function(S,S|null,Lib0Component):void} [CONF.onStateChange] Called when * the state changes. * @property {Object} [CONF.childStates] maps from * CSS-selector to transformer function. The first element that matches the * CSS-selector receives state updates via the transformer function. * @property {Object} [CONF.attrs] * attrs-keys and state-keys should be camelCase, but the DOM uses kebap-case. I.e. * `attrs = { myAttr: 4 }` is represeted as `` in the DOM * @property {Object):boolean|void>} [CONF.listeners] Maps from dom-event-name * to event listener. * @property {function(S, S, Lib0Component):Object} [CONF.slots] Fill slots * automatically when state changes. Maps from slot-name to slot-html. */ /** * @template T * @param {string} name * @param {CONF} cnf * @return {typeof Lib0Component} */ export const createComponent = (name, { template, style = '', state: defaultState, onStateChange = () => {}, childStates = { }, attrs = {}, listeners = {}, slots = () => ({}) }) => { /** * Maps from camelCase attribute name to kebap-case attribute name. * @type {Object} */ const normalizedAttrs = {} for (const key in attrs) { normalizedAttrs[string.fromCamelCase(key, '-')] = key } const templateElement = template ? /** @type {HTMLTemplateElement} */ (dom.parseElement(` `)) : null class _Lib0Component extends HTMLElement { /** * @param {T} [state] */ constructor (state) { super() /** * @type {Array<{d:Lib0Component, s:function(any, any):Object}>} */ this._childStates = [] /** * @type {Object} */ this._slots = {} this._init = false /** * @type {any} */ this._internal = {} /** * @type {any} */ this.state = state || null this.connected = false // init shadow dom if (templateElement) { const shadow = /** @type {ShadowRoot} */ (this.attachShadow({ mode: 'open' })) shadow.appendChild(templateElement.content.cloneNode(true)) // fill child states for (const key in childStates) { this._childStates.push({ d: /** @type {Lib0Component} */ (dom.querySelector(/** @type {any} */ (shadow), key)), s: childStates[key] }) } } dom.emitCustomEvent(this, upgradedEventName, { bubbles: true }) } connectedCallback () { this.connected = true if (!this._init) { this._init = true const shadow = this.shadowRoot if (shadow) { dom.addEventListener(shadow, upgradedEventName, event => { this.setState(this.state, true) event.stopPropagation() }) } /** * @type {Object} */ const startState = this.state || object.assign({}, defaultState) if (attrs) { for (const key in attrs) { const normalizedKey = string.fromCamelCase(key, '-') const val = parseAttrVal(this.getAttribute(normalizedKey), attrs[key]) if (val) { startState[key] = val } } } // add event listeners for (const key in listeners) { dom.addEventListener(shadow || this, key, event => { if (listeners[key](/** @type {CustomEvent} */ (event), this) !== false) { event.stopPropagation() event.preventDefault() return false } }) } // first setState call this.state = null this.setState(startState) } dom.emitCustomEvent(/** @type {any} */ (this.shadowRoot || this), connectedEventName, { bubbles: true }) } disconnectedCallback () { this.connected = false dom.emitCustomEvent(/** @type {any} */ (this.shadowRoot || this), disconnectedEventName, { bubbles: true }) this.setState(null) } static get observedAttributes () { return object.keys(normalizedAttrs) } /** * @param {string} name * @param {string} oldVal * @param {string} newVal * * @private */ attributeChangedCallback (name, oldVal, newVal) { const curState = /** @type {any} */ (this.state) const camelAttrName = normalizedAttrs[name] const type = attrs[camelAttrName] const parsedVal = parseAttrVal(newVal, type) if (curState && (type !== 'json' || json.stringify(curState[camelAttrName]) !== newVal) && curState[camelAttrName] !== parsedVal && !number.isNaN(parsedVal)) { this.updateState({ [camelAttrName]: parsedVal }) } } /** * @param {any} stateUpdate */ updateState (stateUpdate) { this.setState(object.assign({}, this.state, stateUpdate)) } /** * @param {any} state */ setState (state, forceStateUpdates = false) { const prevState = this.state this.state = state if (this._init && (!func.equalityFlat(state, prevState) || forceStateUpdates)) { // fill slots if (state) { const slotElems = slots(state, prevState, this) for (const key in slotElems) { const slotContent = slotElems[key] if (this._slots[key] !== slotContent) { this._slots[key] = slotContent const currentSlots = /** @type {Array} */ (key !== 'default' ? array.from(dom.querySelectorAll(this, `[slot="${key}"]`)) : array.from(this.childNodes).filter(/** @param {any} child */ child => !dom.checkNodeType(child, dom.ELEMENT_NODE) || !child.hasAttribute('slot'))) currentSlots.slice(1).map(dom.remove) const nextSlot = dom.parseFragment(slotContent) if (key !== 'default') { array.from(nextSlot.children).forEach(c => c.setAttribute('slot', key)) } if (currentSlots.length > 0) { dom.replaceWith(currentSlots[0], nextSlot) } else { dom.appendChild(this, nextSlot) } } } } onStateChange(state, prevState, this) if (state != null) { this._childStates.forEach(cnf => { const d = cnf.d if (d.updateState) { d.updateState(cnf.s(state, this)) } }) } for (const key in attrs) { const normalizedKey = string.fromCamelCase(key, '-') if (state == null) { this.removeAttribute(normalizedKey) } else { const stateVal = state[key] const attrsType = attrs[key] if (!prevState || prevState[key] !== stateVal) { if (attrsType === 'bool') { if (stateVal) { this.setAttribute(normalizedKey, '') } else { this.removeAttribute(normalizedKey) } } else if (stateVal == null && (attrsType === 'string' || attrsType === 'number')) { this.removeAttribute(normalizedKey) } else { this.setAttribute(normalizedKey, encodeAttrVal(stateVal, attrsType)) } } } } } } } define(name, _Lib0Component) // @ts-ignore return _Lib0Component } /** * @param {function} definer function that defines a component when executed */ export const createComponentDefiner = definer => { /** * @type {any} */ let defined = null return () => { if (!defined) { defined = definer() } return defined } } export const defineListComponent = createComponentDefiner(() => { const ListItem = createComponent('lib0-list-item', { template: '', slots: state => ({ content: `
${state}
` }) }) return createComponent('lib0-list', { state: { list: /** @type {Array} */ ([]), Item: ListItem }, onStateChange: (state, prevState, component) => { if (state == null) { return } const { list = /** @type {Array} */ ([]), Item = ListItem } = state // @todo deep compare here by providing another parameter to simpleDiffArray let { index, remove, insert } = diff.simpleDiffArray(prevState ? prevState.list : [], list, func.equalityFlat) if (remove === 0 && insert.length === 0) { return } let child = /** @type {Lib0Component} */ (component.firstChild) while (index-- > 0) { child = /** @type {Lib0Component} */ (child.nextElementSibling) } let insertStart = 0 while (insertStart < insert.length && remove-- > 0) { // update existing state child.setState(insert[insertStart++]) child = /** @type {Lib0Component} */ (child.nextElementSibling) } while (remove-- > 0) { // remove remaining const prevChild = child child = /** @type {Lib0Component} */ (child.nextElementSibling) component.removeChild(prevChild) } // insert remaining component.insertBefore(dom.fragment(insert.slice(insertStart).map(/** @param {any} insState */ insState => { const el = new Item() el.setState(insState) return el })), child) } }) }) export const defineLazyLoadingComponent = createComponentDefiner(() => createComponent('lib0-lazy', { state: /** @type {{component:null|String,import:null|function():Promise,state:null|object}} */ ({ component: null, import: null, state: null }), attrs: { component: 'string' }, onStateChange: ({ component, state, import: getImport }, prevState, componentEl) => { if (component !== null) { if (getImport) { getImport() } if (!prevState || component !== prevState.component) { const el = /** @type {any} */ (dom.createElement(component)) componentEl.innerHTML = '' componentEl.insertBefore(el, null) } const el = /** @type {any} */ (componentEl.firstElementChild) // @todo generalize setting state and check if setState is defined if (el.setState) { el.setState(state) } } } })) dmonad-lib0-c7e7806/conditions.js000066400000000000000000000003321512504553500166570ustar00rootroot00000000000000/** * Often used conditions. * * @module conditions */ /** * @template T * @param {T|null|undefined} v * @return {T|null} */ /* c8 ignore next */ export const undefinedToNull = v => v === undefined ? null : v dmonad-lib0-c7e7806/crypto.test.js000066400000000000000000000344611512504553500170160ustar00rootroot00000000000000import * as time from './time.js' import * as jose from 'lib0/crypto/jwt' import * as aes from 'lib0/crypto/aes-gcm' import * as rsa from 'lib0/crypto/rsa-oaep' import * as ecdsa from 'lib0/crypto/ecdsa' import * as t from './testing.js' import * as prng from './prng.js' import * as json from './json.js' /** * @param {t.TestCase} _tc */ export const testJwt = async _tc => { const publicJwk = json.parse('{"key_ops":["verify"],"ext":true,"kty":"EC","x":"X7xPIgxWOHmKPv2PtrxGvaQUJ3LiUXQTVLExwPGBvanD3kAc9sEY9FwKxp8NVJ3j","y":"SIBaHLE1fvW_O-xOdzmbkU5M_M7cGHULZHrXOo_exCKBIbV2pJm3MH87gAXkZvoD","crv":"P-384"}') const privateJwk = json.parse('{"key_ops":["sign"],"ext":true,"kty":"EC","x":"X7xPIgxWOHmKPv2PtrxGvaQUJ3LiUXQTVLExwPGBvanD3kAc9sEY9FwKxp8NVJ3j","y":"SIBaHLE1fvW_O-xOdzmbkU5M_M7cGHULZHrXOo_exCKBIbV2pJm3MH87gAXkZvoD","crv":"P-384","d":"3BdPp9LSWOl36bJuwEIun14Y17dgV7AK8RKqOuTJAbG080kemtr7qmZgTiCE_K_o"}') const privateKey = await ecdsa.importKeyJwk(privateJwk) const publicKey = await ecdsa.importKeyJwk(publicJwk) const payload = { sub: '1234567890', name: 'John Doe', iat: 1516239022 } const jwt = await jose.encodeJwt(privateKey, payload) console.log('jwt: ', jwt) const verified = await jose.verifyJwt(publicKey, jwt) t.compare(verified.payload, payload) const unverified = jose.unsafeDecode(jwt) t.compare(verified, unverified) t.info('expired jwt should not parse') const payloadExpired = { sub: '1234567890', name: 'John Doe', iat: 1516239022, exp: time.getUnixTime() - 10 } const jwtExpired = await jose.encodeJwt(privateKey, payloadExpired) jose.unsafeDecode(jwtExpired) t.failsAsync(async () => { await jose.verifyJwt(publicKey, jwtExpired) }) } /** * @param {t.TestCase} tc */ export const testEncryption = async tc => { const secret = prng.word(tc.prng, 1, 30) const salt = tc.testName const data = prng.uint8Array(tc.prng, 400) await t.groupAsync('symmetric', async () => { const key = await aes.deriveKey(secret, salt) const enc = await aes.encrypt(key, data) const dec = await aes.decrypt(key, enc) t.compare(data, dec) }) await t.groupAsync('RSA', async () => { const keypair = await rsa.generateKeyPair() const enc = await rsa.encrypt(keypair.publicKey, data) const dec = await rsa.decrypt(keypair.privateKey, enc) t.compare(data, dec) }) await t.groupAsync('symmetric can fail', async () => { await t.failsAsync(async () => { const key = await aes.deriveKey(secret, salt) const key2 = await aes.deriveKey(secret + '2', salt) const enc = await aes.encrypt(key, data) const dec = await aes.decrypt(key2, enc) t.compare(data, dec) }) }) await t.groupAsync('asymmetric can fail', async () => { await t.failsAsync(async () => { const keypair = await rsa.generateKeyPair() const keypair2 = await rsa.generateKeyPair() const enc = await rsa.encrypt(keypair.privateKey, data) const dec = await rsa.decrypt(keypair2.publicKey, enc) t.compare(data, dec) }) }) } /** * @param {t.TestCase} tc */ export const testReapeatEncryption = async tc => { const secret = prng.word(tc.prng, 1, 30) const salt = prng.word(tc.prng) const data = prng.uint8Array(tc.prng, 1000000) /** * @type {any} */ let encrypted /** * @type {any} */ let decrypted /** * @type {CryptoKey} */ let key await t.measureTimeAsync('Key generation', async () => { key = await aes.deriveKey(secret, salt) }) await t.measureTimeAsync('Encryption', async () => { encrypted = await aes.encrypt(key, data) }) t.info(`Byte length: ${data.byteLength}b`) t.info(`Encrypted length: ${encrypted.length}b`) await t.measureTimeAsync('Decryption', async () => { decrypted = await aes.decrypt(key, encrypted) }) t.compare(data, decrypted) } /** * @param {t.TestCase} tc */ export const testImportExport = async tc => { const secret = prng.word(tc.prng, 1, 30) const salt = prng.word(tc.prng) await t.groupAsync('aes-gcm (jwk))', async () => { const key = await aes.deriveKey(secret, salt, { extractable: true }) const jwk = await aes.exportKeyJwk(key) const ekey = await aes.importKeyJwk(jwk, { extractable: true }) const ejwk = await aes.exportKeyJwk(ekey) t.compare(jwk, ejwk) }) await t.groupAsync('aes-gcm (raw))', async () => { const key = await aes.deriveKey(secret, salt, { extractable: true }) const raw = await aes.exportKeyRaw(key) const ekey = await aes.importKeyRaw(raw, { extractable: true }) const eraw = await aes.exportKeyRaw(ekey) t.compare(raw, eraw) }) await t.groupAsync('ecdsa (jwk))', async () => { const keypair = await ecdsa.generateKeyPair({ extractable: true }) const jwkPrivate = await ecdsa.exportKeyJwk(keypair.privateKey) const jwkPublic = await ecdsa.exportKeyJwk(keypair.publicKey) const ekeyPrivate = await ecdsa.importKeyJwk(jwkPrivate, { extractable: true }) const ekeyPublic = await ecdsa.importKeyJwk(jwkPublic, { extractable: true }) const ejwkPrivate = await ecdsa.exportKeyJwk(ekeyPrivate) const ejwkPublic = await ecdsa.exportKeyJwk(ekeyPublic) t.compare(jwkPrivate, ejwkPrivate) t.compare(jwkPublic, ejwkPublic) }) await t.groupAsync('ecdsa (raw))', async () => { const keypair = await ecdsa.generateKeyPair({ extractable: true, usages: ['sign', 'verify'] }) const rawPublic = await ecdsa.exportKeyRaw(keypair.publicKey) const ekeyPublic = await ecdsa.importKeyRaw(rawPublic, { extractable: true, usages: ['verify'] }) const erawPublic = await ecdsa.exportKeyRaw(ekeyPublic) t.compare(rawPublic, erawPublic) }) await t.groupAsync('rsa-oaep (jwk))', async () => { const keypair = await rsa.generateKeyPair({ extractable: true }) const jwkPrivate = await rsa.exportKeyJwk(keypair.privateKey) const jwkPublic = await rsa.exportKeyJwk(keypair.publicKey) const ekeyPrivate = await rsa.importKeyJwk(jwkPrivate, { extractable: true }) const ekeyPublic = await rsa.importKeyJwk(jwkPublic, { extractable: true }) const ejwkPrivate = await rsa.exportKeyJwk(ekeyPrivate) const ejwkPublic = await rsa.exportKeyJwk(ekeyPublic) t.compare(jwkPrivate, ejwkPrivate) t.compare(jwkPublic, ejwkPublic) }) } /** * @param {t.TestCase} tc */ export const testEncryptionPerformance = async tc => { const N = 1000 const BLen = 1000 const secret = prng.word(tc.prng, 1, 30) const salt = prng.word(tc.prng) /** * @type {CryptoKey} */ let key await t.measureTimeAsync('Key generation', async () => { key = await aes.deriveKey(secret, salt) }) /** * @type {Array>} */ const data = [] for (let i = 0; i < N; i++) { data.push(prng.uint8Array(tc.prng, BLen)) } /** * @type {Array>} */ const encryptedData = [] await t.measureTimeAsync(`Encrypt ${N / 1000}k blocks of size ${BLen}byte`, async () => { for (let i = 0; i < data.length; i++) { encryptedData.push(await aes.encrypt(key, data[i])) } }) /** * @type {Array} */ const decryptedData = [] await t.measureTimeAsync(`Decrypt ${N / 1000}k blocks of size ${BLen}byte`, async () => { for (let i = 0; i < encryptedData.length; i++) { decryptedData.push(await aes.decrypt(key, encryptedData[i])) } }) t.compare(data, decryptedData) } /** * @param {t.TestCase} _tc */ export const testConsistentKeyGeneration = async _tc => { await t.groupAsync('Key generation (AES))', async () => { const secret = 'qfycncpxhjktawlqkhc' const salt = 'my nonce' const expectedJwk = { key_ops: ['decrypt', 'encrypt'], kty: 'oct', k: 'psAqoMh9apefdr8y1tdbNMVTLxb-tFekEFipYIOX5n8', alg: 'A256GCM' } const key = await aes.deriveKey(secret, salt, { extractable: true }) const jwk = await aes.exportKeyJwk(key) delete jwk.ext jwk.key_ops?.sort() t.compare(jwk, expectedJwk) }) await t.groupAsync('key generation (ECDSA))', async () => { const jwkPublic = { key_ops: ['verify'], ext: true, kty: 'EC', x: 'zfklq8SI_XEZlBawiRmkuv1vwPqGXd456SAHvv_aH4_4v17qcnmFkChaRqCGgXKo', y: 'YAt3r7fiB6j_RVKpcnokpEXE6r7XTcOzUxb3VmvkYcC5WfqDi6S7E3HzifOjeYjI', crv: 'P-384' } const jwkPrivate = { key_ops: ['sign'], ext: true, kty: 'EC', x: 'zfklq8SI_XEZlBawiRmkuv1vwPqGXd456SAHvv_aH4_4v17qcnmFkChaRqCGgXKo', y: 'YAt3r7fiB6j_RVKpcnokpEXE6r7XTcOzUxb3VmvkYcC5WfqDi6S7E3HzifOjeYjI', crv: 'P-384', d: 'z1bahlvHj7dWLYGr_oGGSNT_o01JdmnOoG79vLEm2LCG5Arl-4UZPFKpIWhmnZZU' } const privateKey = await ecdsa.importKeyJwk(jwkPrivate, { extractable: true, usages: ['sign'] }) const publicKey = await ecdsa.importKeyJwk(jwkPublic, { extractable: true, usages: ['verify'] }) const exportedPublic = await ecdsa.exportKeyJwk(publicKey) const exportedPrivate = await ecdsa.exportKeyJwk(privateKey) delete exportedPublic.alg // for firefox compat delete exportedPrivate.alg // for firefox compat t.compare(jwkPublic, /** @type {any} */ (exportedPublic)) t.compare(jwkPrivate, /** @type {any} */ (exportedPrivate)) }) await t.groupAsync('key generation (RSA))', async () => { const jwkPublic = { key_ops: ['encrypt'], ext: true, kty: 'RSA', n: '2M17DkhswailS2qGzpuoyGFxk193-18OSgNGVYAB_rPk8gN0CdLCyW8z0Ya8LpBgLNDht7vPdsXOZOAoIvgJZvt3mSGSJQj-gFy8l9DTQrt3rtiKKCX_tGmLeN0eN-TMemIG8Wkd8Ebqpkj-mQAgIPdsukO0capWyr0RM3C7ByESb3Fk0Z9v9p06kk8BuTz-5B5yyeDI_tfALRo_ZaWAN1rCXF8Sdjoipw1OUVJjzBLDDZ_znAPkZgTl9IZJMs_l97kv4OEl4xpq4lgobBYkwa8fWmqSljC9z99hws1hcgc3OQo43vbGT_80DW5GbeAWXAdoMJjqpG9Slc-ZfRVGen9DZpTXHkFmcmI9KrNhc_HFdWQPIUc7BUkX06nW5T_-t1OcgkMzFJhtxhlh8Ah7KcOJ7f2P7kziCr8uj7MPPz05-FbFJfUJgbQgX9wo4JbwtLKtqtTqfRaI8lCCbx_k8NC_RIi4YMqtF31O1WGEL7TX4pSJyPSLnkWzniJmG_v1_CAFhenoNZPgNY_kin-5DPtFVrpEkaJygySEs1JhII33ecJXc951yAEuEiaFdqcHNVryhesDJo6P4UfvuaGW6UQW2o6g0uNXC71emvK7dWt4K1r9QYH0SslHD-amJvgbFestBxAR4M_ldeTxQMRXMnYU-iCzgwKaPvSLSSNn8kk', e: 'AQAB', alg: 'RSA-OAEP-256' } const jwkPrivate = { key_ops: ['decrypt'], ext: true, kty: 'RSA', n: '2M17DkhswailS2qGzpuoyGFxk193-18OSgNGVYAB_rPk8gN0CdLCyW8z0Ya8LpBgLNDht7vPdsXOZOAoIvgJZvt3mSGSJQj-gFy8l9DTQrt3rtiKKCX_tGmLeN0eN-TMemIG8Wkd8Ebqpkj-mQAgIPdsukO0capWyr0RM3C7ByESb3Fk0Z9v9p06kk8BuTz-5B5yyeDI_tfALRo_ZaWAN1rCXF8Sdjoipw1OUVJjzBLDDZ_znAPkZgTl9IZJMs_l97kv4OEl4xpq4lgobBYkwa8fWmqSljC9z99hws1hcgc3OQo43vbGT_80DW5GbeAWXAdoMJjqpG9Slc-ZfRVGen9DZpTXHkFmcmI9KrNhc_HFdWQPIUc7BUkX06nW5T_-t1OcgkMzFJhtxhlh8Ah7KcOJ7f2P7kziCr8uj7MPPz05-FbFJfUJgbQgX9wo4JbwtLKtqtTqfRaI8lCCbx_k8NC_RIi4YMqtF31O1WGEL7TX4pSJyPSLnkWzniJmG_v1_CAFhenoNZPgNY_kin-5DPtFVrpEkaJygySEs1JhII33ecJXc951yAEuEiaFdqcHNVryhesDJo6P4UfvuaGW6UQW2o6g0uNXC71emvK7dWt4K1r9QYH0SslHD-amJvgbFestBxAR4M_ldeTxQMRXMnYU-iCzgwKaPvSLSSNn8kk', e: 'AQAB', d: 'IXXVKtHStzDSvLOm3_b2MwxBqH_GLMLxmaACbZUMDxtZ7QrLZe4Op2LE70Q0LEZBZv6grOgM5O_dItnVrU_1Y4d8Czjl2AFuBgb0tG7uVrudhRwLy-vRbdlm793F7tUebzpMiAWz29mWGC6RMgaVmYDrrvO0GGGJyPsqecie6kLDGEq6nKpWxWEOy8tAdi5hHc6f42MQolmkt1FNvN-wzNpHv6niAjfcEUbkciNHyRSRi2YcWv_eyEAJSEFRcB4rXUhKM2g282NTC7aUxoRvDSrR_p-5b_n7JudQV0MLabs4sqej5LAsJec6nrgI9qbsdyz4JPKfhDFfiu-Mvi6tFFdv5-e15QywqJIKgzHDxREcWXA2EF5CAfeD2rt0k1EsaoBUVII3k6VXH3Ufz5-94GialafmDdWLwr_kusT43nFnNpBVLlscyi1hnLwmbyHQo7Le0bt8umFMfFRqGiFCkkOdtUecH6xt1wfvC0vqT2aSppo8DQpYzQWxfDnzhW4-8yDAjmwOkeAcH3YDpMrxmUtLm6CXGeYrcyeoPwQz39ezE8m5BqqdLTdD8IHiypon_0bdyOkvENVCcm9fcMU3isx57w-4Higrn-yvS-v-17jouPFgYmWiu9uvKQNvvszhPnxmpTcil3PXXW25JLyXPb3NEQOa51o3lFFUH-03pWE', p: '71aSuZ1fH-Ly4NSAqSWSc_gMjXVR_QbapyBlB0tr7b1SiJRwS_Yqv1jNZfFSHbx63YNaJDDi5pJaENbYYoKSHvA8--fZJ6GbVIzYSOnntW__nDz22PwpIyoJVXqXqsRygc3DX0XiFM0pQ2QaLoZzvdxv2RgBMTdMWnhelk3SA6oVe3mMarSh316PCjE5TDApWlGCFHFEHji8FDXsYKEfgXRrjSIEJW9RbufIIAdeTH2guesezguVVPoQQ9Y0KtjQ3vsCxjrHdvAzK8IqWikOEJgJZZkBNXenPFwNkZwadjz9EpR-FVwRWKRiVECjTjy-xzD_LenISpgk_SN4evfTyw', q: '5-VI90pui_C0A5eBtL8vDVLYfahw5KJxqfxeqBjEbiL_GK2B59BN4f0GbfDeLm-T8J0hYtPdR0qwLv87sT318_Y-gT2T7qrr2lEyudbQt2SgNgfWe2VwakESIN3DU7voUV-ykSXoqPqjOwunSAq-cI1fGmg29tBArciHiuLq9n-QzK3geaz7NHSz5jQ5jA71HtZdFE85VncYUFb6MZetEJ9htbpjJPvfAVagzv9dedsTV3f8gC6XH1Ck5a40SYO2suAoQoOGdJc8Yfsd5ssjdqvYrQF7M9ttqe5VFH86Jnsz1dVwq65avjgRWBmBCWgk_nX_QDzuoEcduRk4w-sXuw', dp: '3Y96QqRBlCYnCyUNeghTHFIrZKSP4rl-nqppfChA4JObnN41Wsym4_4UHuQYTXjXEMrxHoG2-xXOlLofFIqlNEjXW6dUqtB7F_lOm6kVHCxzJzJ0nYhJmMjoXR4g2zAChNFzpHXwBaurIDzB1AIZkVBIpmMHb4UuhK3bei7OVSAVxPlPmNRg6YQCzL-muDX5gifkUIJOOd_xlJAao5VkshWRHtS3m-QCMbYV2DiZ_htqN9JF8R5d_o2DkxjvsB6ItXMPLWzqi9tus3qKdG5_G7NzN3891D5RLZpV4U7uXDi3WoTmd2WElVePw0kXJG0tev6Lq_g4t31C-Kfmd4eGow', dq: '1Mjns0JxPaeZBtK3CguEOU2TqXouXR1R_xC8KrLPS-CBAzvyv6u8S2nJxIgI18M6lMcaI30Uxp4aHIXHWFPqo_mIUT8XxyC_Woy3Zx9eVWnYOLvoa0IhbN5YrB_RY7xA6KpPSDDo1GVn8n42-Twik1Slt615AfEF6HDhLugZgiZ7z9Sc7gl0WCXeDZZOV95BvhIlRsWLb3PIs6-b1HXBMEePeRmWcBFOCARdepOISpBjpxdKcrRNp0ZwiPDYubxKoMhfKOlXLxS3K5EpVuV_nR0CrX12d5cZgZxYJX649SaH4ecAhAhw66q2_4gnh2Iwz-2mUmOW8ytOctJZ7CyEkQ', qi: 'ggdoo4EYrtNl-sSNwF8PjtmJxv8GA5df__iKMnN0A5yHlbcdfBH4zCGoDQW8K4QKAsBldI7zkuYx08VCYrSQL6xkowXctjXprxkROHsWxN8cDC2kg6lUOO88GeRT9s96wgzZoHcPNxTrZiJDsAGgPr4OdiGk1feu3BpWPitSZL2TnWfEqlmIdq99-0wTCgn_-kzXrRnZEAOsEyZk2FXwBA7d22fJ5fbyvVQ98pj7ih-ieKUZWyzR5E-EEYDfQjJ8A49F1ejcNSIr2zPb7vCiEpdtLlRak9dfuetmYNfrqaVhAKLqzE2ErtZ6ZLfvJcp9d4ZHsbppR8WckKEyA-L7_A', alg: 'RSA-OAEP-256' } const privateKey = await rsa.importKeyJwk(jwkPrivate, { extractable: true, usages: ['decrypt'] }) const publicKey = await rsa.importKeyJwk(jwkPublic, { extractable: true, usages: ['encrypt'] }) const exportedPublic = await rsa.exportKeyJwk(publicKey) const exportedPrivate = await rsa.exportKeyJwk(privateKey) t.compare(jwkPublic, /** @type {any} */ (exportedPublic)) t.compare(jwkPrivate, /** @type {any} */ (exportedPrivate)) }) } /** * @param {t.TestCase} tc */ export const testSigning = async tc => { await t.measureTimeAsync('time to sign & verify 2 messages (ECDSA))', async () => { const keypair = await ecdsa.generateKeyPair({ extractable: true }) const keypair2 = await ecdsa.generateKeyPair({ extractable: true }) const data = prng.uint8Array(tc.prng, 100) const signature = await ecdsa.sign(keypair.privateKey, data) const result = await ecdsa.verify(keypair.publicKey, signature, data) const result2 = await ecdsa.verify(keypair2.publicKey, signature, data) t.assert(result, 'verification works using the correct key') t.assert(!result2, 'verification fails using the incorrect key') }) } dmonad-lib0-c7e7806/crypto/000077500000000000000000000000001512504553500154725ustar00rootroot00000000000000dmonad-lib0-c7e7806/crypto/aes-gcm.js000066400000000000000000000066521512504553500173550ustar00rootroot00000000000000/** * AES-GCM is a symmetric key for encryption */ import * as encoding from '../encoding.js' import * as decoding from '../decoding.js' import * as webcrypto from 'lib0/webcrypto' import * as string from '../string.js' export { exportKeyJwk, exportKeyRaw } from './common.js' /** * @typedef {Array<'encrypt'|'decrypt'>} Usages */ /** * @type {Usages} */ const defaultUsages = ['encrypt', 'decrypt'] /** * @param {CryptoKey} key * @param {Uint8Array} data */ export const encrypt = (key, data) => { const iv = webcrypto.getRandomValues(new Uint8Array(16)) // 92bit is enough. 128bit is recommended if space is not an issue. return webcrypto.subtle.encrypt( { name: 'AES-GCM', iv }, key, data ).then(cipher => { const encryptedDataEncoder = encoding.createEncoder() // iv may be sent in the clear to the other peers encoding.writeUint8Array(encryptedDataEncoder, iv) encoding.writeVarUint8Array(encryptedDataEncoder, new Uint8Array(cipher)) return encoding.toUint8Array(encryptedDataEncoder) }) } /** * @experimental The API is not final! * * Decrypt some data using AES-GCM method. * * @param {CryptoKey} key * @param {Uint8Array} data * @return {PromiseLike} decrypted buffer */ export const decrypt = (key, data) => { const dataDecoder = decoding.createDecoder(data) const iv = decoding.readUint8Array(dataDecoder, 16) const cipher = decoding.readVarUint8Array(dataDecoder) return webcrypto.subtle.decrypt( { name: 'AES-GCM', iv }, key, cipher ).then(data => new Uint8Array(data)) } const aesAlgDef = { name: 'AES-GCM', length: 256 } /** * @param {any} jwk * @param {Object} opts * @param {Usages} [opts.usages] * @param {boolean} [opts.extractable] */ export const importKeyJwk = (jwk, { usages, extractable = false } = {}) => { if (usages == null) { /* c8 ignore next */ usages = jwk.key_ops || defaultUsages } return webcrypto.subtle.importKey('jwk', jwk, 'AES-GCM', extractable, /** @type {Usages} */ (usages)) } /** * Only suited for importing public keys. * * @param {Uint8Array} raw * @param {Object} opts * @param {Usages} [opts.usages] * @param {boolean} [opts.extractable] */ export const importKeyRaw = (raw, { usages = defaultUsages, extractable = false } = {}) => webcrypto.subtle.importKey('raw', raw, aesAlgDef, extractable, /** @type {Usages} */ (usages)) /** * @param {Uint8Array | string} data */ /* c8 ignore next */ const toBinary = data => typeof data === 'string' ? string.encodeUtf8(data) : data /** * @experimental The API is not final! * * Derive an symmetric key using the Password-Based-Key-Derivation-Function-2. * * @param {Uint8Array|string} secret * @param {Uint8Array|string} salt * @param {Object} opts * @param {boolean} [opts.extractable] * @param {Usages} [opts.usages] */ export const deriveKey = (secret, salt, { extractable = false, usages = defaultUsages } = {}) => webcrypto.subtle.importKey( 'raw', toBinary(secret), 'PBKDF2', false, ['deriveKey'] ).then(keyMaterial => webcrypto.subtle.deriveKey( { name: 'PBKDF2', salt: toBinary(salt), // NIST recommends at least 64 bits iterations: 600000, // OWASP recommends 600k iterations hash: 'SHA-256' }, keyMaterial, aesAlgDef, extractable, usages ) ) dmonad-lib0-c7e7806/crypto/common.js000066400000000000000000000007121512504553500173200ustar00rootroot00000000000000import * as webcrypto from 'lib0/webcrypto' /** * @param {CryptoKey} key */ export const exportKeyJwk = async key => { const jwk = await webcrypto.subtle.exportKey('jwk', key) jwk.key_ops = key.usages return jwk } /** * Only suited for exporting public keys. * * @param {CryptoKey} key * @return {Promise>} */ export const exportKeyRaw = key => webcrypto.subtle.exportKey('raw', key).then(key => new Uint8Array(key)) dmonad-lib0-c7e7806/crypto/ecdsa.js000066400000000000000000000042241512504553500171110ustar00rootroot00000000000000/** * ECDSA is an asymmetric key for signing */ import * as webcrypto from 'lib0/webcrypto' export { exportKeyJwk, exportKeyRaw } from './common.js' /** * @typedef {Array<'sign'|'verify'>} Usages */ /** * @type {Usages} */ const defaultUsages = ['sign', 'verify'] const defaultSignAlgorithm = { name: 'ECDSA', hash: 'SHA-384' } /** * @experimental The API is not final! * * Sign a message * * @param {CryptoKey} key * @param {Uint8Array} data * @return {PromiseLike>} signature */ export const sign = (key, data) => webcrypto.subtle.sign( defaultSignAlgorithm, key, data ).then(signature => new Uint8Array(signature)) /** * @experimental The API is not final! * * Sign a message * * @param {CryptoKey} key * @param {Uint8Array} signature * @param {Uint8Array} data * @return {PromiseLike} signature */ export const verify = (key, signature, data) => webcrypto.subtle.verify( defaultSignAlgorithm, key, signature, data ) const defaultKeyAlgorithm = { name: 'ECDSA', namedCurve: 'P-384' } /* c8 ignore next */ /** * @param {Object} opts * @param {boolean} [opts.extractable] * @param {Usages} [opts.usages] */ export const generateKeyPair = ({ extractable = false, usages = defaultUsages } = {}) => webcrypto.subtle.generateKey( defaultKeyAlgorithm, extractable, usages ) /** * @param {any} jwk * @param {Object} opts * @param {boolean} [opts.extractable] * @param {Usages} [opts.usages] */ export const importKeyJwk = (jwk, { extractable = false, usages } = {}) => { if (usages == null) { /* c8 ignore next 2 */ usages = jwk.key_ops || defaultUsages } return webcrypto.subtle.importKey('jwk', jwk, defaultKeyAlgorithm, extractable, /** @type {Usages} */ (usages)) } /** * Only suited for importing public keys. * * @param {any} raw * @param {Object} opts * @param {boolean} [opts.extractable] * @param {Usages} [opts.usages] */ export const importKeyRaw = (raw, { extractable = false, usages = defaultUsages } = {}) => webcrypto.subtle.importKey('raw', raw, defaultKeyAlgorithm, extractable, usages) dmonad-lib0-c7e7806/crypto/jwt.js000066400000000000000000000037541512504553500166450ustar00rootroot00000000000000import * as error from '../error.js' import * as buffer from '../buffer.js' import * as string from '../string.js' import * as json from '../json.js' import * as ecdsa from '../crypto/ecdsa.js' import * as time from '../time.js' /** * @param {Object} data */ const _stringify = data => buffer.toBase64UrlEncoded(string.encodeUtf8(json.stringify(data))) /** * @param {string} base64url */ const _parse = base64url => json.parse(string.decodeUtf8(buffer.fromBase64UrlEncoded(base64url))) /** * @param {CryptoKey} privateKey * @param {Object} payload */ export const encodeJwt = (privateKey, payload) => { const { name: algName, namedCurve: algCurve } = /** @type {any} */ (privateKey.algorithm) /* c8 ignore next 3 */ if (algName !== 'ECDSA' || algCurve !== 'P-384') { error.unexpectedCase() } const header = { alg: 'ES384', typ: 'JWT' } const jwt = _stringify(header) + '.' + _stringify(payload) return ecdsa.sign(privateKey, string.encodeUtf8(jwt)).then(signature => jwt + '.' + buffer.toBase64UrlEncoded(signature) ) } /** * @param {CryptoKey} publicKey * @param {string} jwt */ export const verifyJwt = async (publicKey, jwt) => { const [headerBase64, payloadBase64, signatureBase64] = jwt.split('.') const verified = await ecdsa.verify(publicKey, buffer.fromBase64UrlEncoded(signatureBase64), string.encodeUtf8(headerBase64 + '.' + payloadBase64)) /* c8 ignore next 3 */ if (!verified) { throw new Error('Invalid JWT') } const payload = _parse(payloadBase64) if (payload.exp != null && time.getUnixTime() > payload.exp) { throw new Error('Expired JWT') } return { header: _parse(headerBase64), payload } } /** * Decode a jwt without verifying it. Probably a bad idea to use this. Only use if you know the jwt was already verified! * * @param {string} jwt */ export const unsafeDecode = jwt => { const [headerBase64, payloadBase64] = jwt.split('.') return { header: _parse(headerBase64), payload: _parse(payloadBase64) } } dmonad-lib0-c7e7806/crypto/rsa-oaep.js000066400000000000000000000035231512504553500175420ustar00rootroot00000000000000/** * RSA-OAEP is an asymmetric keypair used for encryption */ import * as webcrypto from 'lib0/webcrypto' export { exportKeyJwk } from './common.js' /** * @typedef {Array<'encrypt'|'decrypt'>} Usages */ /** * @type {Usages} */ const defaultUsages = ['encrypt', 'decrypt'] /** * Note that the max data size is limited by the size of the RSA key. * * @param {CryptoKey} key * @param {Uint8Array} data * @return {PromiseLike>} */ export const encrypt = (key, data) => webcrypto.subtle.encrypt( { name: 'RSA-OAEP' }, key, data ).then(buf => new Uint8Array(buf)) /** * @experimental The API is not final! * * Decrypt some data using AES-GCM method. * * @param {CryptoKey} key * @param {Uint8Array} data * @return {PromiseLike} decrypted buffer */ export const decrypt = (key, data) => webcrypto.subtle.decrypt( { name: 'RSA-OAEP' }, key, data ).then(data => new Uint8Array(data)) /** * @param {Object} opts * @param {boolean} [opts.extractable] * @param {Usages} [opts.usages] * @return {Promise} */ export const generateKeyPair = ({ extractable = false, usages = defaultUsages } = {}) => webcrypto.subtle.generateKey( { name: 'RSA-OAEP', modulusLength: 4096, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256' }, extractable, usages ) /** * @param {any} jwk * @param {Object} opts * @param {boolean} [opts.extractable] * @param {Usages} [opts.usages] */ export const importKeyJwk = (jwk, { extractable = false, usages } = {}) => { if (usages == null) { /* c8 ignore next */ usages = jwk.key_ops || defaultUsages } return webcrypto.subtle.importKey('jwk', jwk, { name: 'RSA-OAEP', hash: 'SHA-256' }, extractable, /** @type {Usages} */ (usages)) } dmonad-lib0-c7e7806/decoding.js000066400000000000000000000426671512504553500163030ustar00rootroot00000000000000/** * Efficient schema-less binary decoding with support for variable length encoding. * * 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. * * ```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 decoding */ import * as binary from './binary.js' import * as math from './math.js' import * as number from './number.js' import * as string from './string.js' import * as error from './error.js' import * as encoding from './encoding.js' const errorUnexpectedEndOfArray = error.create('Unexpected end of array') const errorIntegerOutOfRange = error.create('Integer out of Range') /** * A Decoder handles the decoding of an Uint8Array. * @template {ArrayBufferLike} [Buf=ArrayBufferLike] */ export class Decoder { /** * @param {Uint8Array} uint8Array Binary data to decode */ constructor (uint8Array) { /** * Decoding target. * * @type {Uint8Array} */ this.arr = uint8Array /** * Current decoding position. * * @type {number} */ this.pos = 0 } } /** * @function * @template {ArrayBufferLike} Buf * @param {Uint8Array} uint8Array * @return {Decoder} */ export const createDecoder = uint8Array => new Decoder(uint8Array) /** * @function * @param {Decoder} decoder * @return {boolean} */ export const hasContent = decoder => decoder.pos !== decoder.arr.length /** * Clone a decoder instance. * Optionally set a new position parameter. * * @function * @param {Decoder} decoder The decoder instance * @param {number} [newPos] Defaults to current position * @return {Decoder} A clone of `decoder` */ export const clone = (decoder, newPos = decoder.pos) => { const _decoder = createDecoder(decoder.arr) _decoder.pos = newPos return _decoder } /** * Create 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. * * @function * @template {ArrayBufferLike} Buf * @param {Decoder} decoder The decoder instance * @param {number} len The length of bytes to read * @return {Uint8Array} */ export const readUint8Array = (decoder, len) => { const view = new Uint8Array(decoder.arr.buffer, decoder.pos + decoder.arr.byteOffset, len) decoder.pos += len return view } /** * Read 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. * * @function * @template {ArrayBufferLike} Buf * @param {Decoder} decoder * @return {Uint8Array} */ export const readVarUint8Array = decoder => readUint8Array(decoder, readVarUint(decoder)) /** * Read the rest of the content as an ArrayBuffer * @function * @param {Decoder} decoder * @return {Uint8Array} */ export const readTailAsUint8Array = decoder => readUint8Array(decoder, decoder.arr.length - decoder.pos) /** * Skip one byte, jump to the next position. * @function * @param {Decoder} decoder The decoder instance * @return {number} The next position */ export const skip8 = decoder => decoder.pos++ /** * Read one byte as unsigned integer. * @function * @param {Decoder} decoder The decoder instance * @return {number} Unsigned 8-bit integer */ export const readUint8 = decoder => decoder.arr[decoder.pos++] /** * Read 2 bytes as unsigned integer. * * @function * @param {Decoder} decoder * @return {number} An unsigned integer. */ export const readUint16 = decoder => { const uint = decoder.arr[decoder.pos] + (decoder.arr[decoder.pos + 1] << 8) decoder.pos += 2 return uint } /** * Read 4 bytes as unsigned integer. * * @function * @param {Decoder} decoder * @return {number} An unsigned integer. */ export const readUint32 = decoder => { const uint = (decoder.arr[decoder.pos] + (decoder.arr[decoder.pos + 1] << 8) + (decoder.arr[decoder.pos + 2] << 16) + (decoder.arr[decoder.pos + 3] << 24)) >>> 0 decoder.pos += 4 return uint } /** * Read 4 bytes as unsigned integer in big endian order. * (most significant byte first) * * @function * @param {Decoder} decoder * @return {number} An unsigned integer. */ export const readUint32BigEndian = decoder => { const uint = (decoder.arr[decoder.pos + 3] + (decoder.arr[decoder.pos + 2] << 8) + (decoder.arr[decoder.pos + 1] << 16) + (decoder.arr[decoder.pos] << 24)) >>> 0 decoder.pos += 4 return uint } /** * Look ahead without incrementing the position * to the next byte and read it as unsigned integer. * * @function * @param {Decoder} decoder * @return {number} An unsigned integer. */ export const peekUint8 = decoder => decoder.arr[decoder.pos] /** * Look ahead without incrementing the position * to the next byte and read it as unsigned integer. * * @function * @param {Decoder} decoder * @return {number} An unsigned integer. */ export const peekUint16 = decoder => decoder.arr[decoder.pos] + (decoder.arr[decoder.pos + 1] << 8) /** * Look ahead without incrementing the position * to the next byte and read it as unsigned integer. * * @function * @param {Decoder} decoder * @return {number} An unsigned integer. */ export const peekUint32 = decoder => ( decoder.arr[decoder.pos] + (decoder.arr[decoder.pos + 1] << 8) + (decoder.arr[decoder.pos + 2] << 16) + (decoder.arr[decoder.pos + 3] << 24) ) >>> 0 /** * Read unsigned integer (32bit) with variable length. * 1/8th of the storage is used as encoding overhead. * * numbers < 2^7 is stored in one bytlength * * numbers < 2^14 is stored in two bylength * * @function * @param {Decoder} decoder * @return {number} An unsigned integer.length */ export const readVarUint = decoder => { let num = 0 let mult = 1 const len = decoder.arr.length while (decoder.pos < len) { const r = decoder.arr[decoder.pos++] // num = num | ((r & binary.BITS7) << len) num = num + (r & binary.BITS7) * mult // shift $r << (7*#iterations) and add it to num mult *= 128 // next iteration, shift 7 "more" to the left if (r < binary.BIT8) { return num } /* c8 ignore start */ if (num > number.MAX_SAFE_INTEGER) { throw errorIntegerOutOfRange } /* c8 ignore stop */ } throw errorUnexpectedEndOfArray } /** * Read signed integer (32bit) with variable length. * 1/8th of the storage is used as encoding overhead. * * numbers < 2^7 is stored in one bytlength * * numbers < 2^14 is stored in two bylength * @todo This should probably create the inverse ~num if number is negative - but this would be a breaking change. * * @function * @param {Decoder} decoder * @return {number} An unsigned integer.length */ export const readVarInt = decoder => { let r = decoder.arr[decoder.pos++] let num = r & binary.BITS6 let mult = 64 const sign = (r & binary.BIT7) > 0 ? -1 : 1 if ((r & binary.BIT8) === 0) { // don't continue reading return sign * num } const len = decoder.arr.length while (decoder.pos < len) { r = decoder.arr[decoder.pos++] // num = num | ((r & binary.BITS7) << len) num = num + (r & binary.BITS7) * mult mult *= 128 if (r < binary.BIT8) { return sign * num } /* c8 ignore start */ if (num > number.MAX_SAFE_INTEGER) { throw errorIntegerOutOfRange } /* c8 ignore stop */ } throw errorUnexpectedEndOfArray } /** * Look ahead and read varUint without incrementing position * * @function * @param {Decoder} decoder * @return {number} */ export const peekVarUint = decoder => { const pos = decoder.pos const s = readVarUint(decoder) decoder.pos = pos return s } /** * Look ahead and read varUint without incrementing position * * @function * @param {Decoder} decoder * @return {number} */ export const peekVarInt = decoder => { const pos = decoder.pos const s = readVarInt(decoder) decoder.pos = pos return s } /** * We don't test this function anymore as we use native decoding/encoding by default now. * Better not modify this anymore.. * * Transforming utf8 to a string is pretty expensive. The code performs 10x better * when String.fromCodePoint is fed with all characters as arguments. * But most environments have a maximum number of arguments per functions. * For effiency reasons we apply a maximum of 10000 characters at once. * * @function * @param {Decoder} decoder * @return {String} The read String. */ /* c8 ignore start */ export const _readVarStringPolyfill = decoder => { let remainingLen = readVarUint(decoder) if (remainingLen === 0) { return '' } else { let encodedString = String.fromCodePoint(readUint8(decoder)) // remember to decrease remainingLen if (--remainingLen < 100) { // do not create a Uint8Array for small strings while (remainingLen--) { encodedString += String.fromCodePoint(readUint8(decoder)) } } else { while (remainingLen > 0) { const nextLen = remainingLen < 10000 ? remainingLen : 10000 // this is dangerous, we create a fresh array view from the existing buffer const bytes = decoder.arr.subarray(decoder.pos, decoder.pos + nextLen) decoder.pos += nextLen // Starting with ES5.1 we can supply a generic array-like object as arguments encodedString += String.fromCodePoint.apply(null, /** @type {any} */ (bytes)) remainingLen -= nextLen } } return decodeURIComponent(escape(encodedString)) } } /* c8 ignore stop */ /** * @function * @param {Decoder} decoder * @return {String} The read String */ export const _readVarStringNative = decoder => /** @type any */ (string.utf8TextDecoder).decode(readVarUint8Array(decoder)) /** * Read string of variable length * * varUint is used to store the length of the string * * @function * @param {Decoder} decoder * @return {String} The read String * */ /* c8 ignore next */ export const readVarString = string.utf8TextDecoder ? _readVarStringNative : _readVarStringPolyfill /** * @param {Decoder} decoder * @return {Uint8Array} */ export const readTerminatedUint8Array = decoder => { const encoder = encoding.createEncoder() let b while (true) { b = readUint8(decoder) if (b === 0) { return encoding.toUint8Array(encoder) } if (b === 1) { b = readUint8(decoder) } encoding.write(encoder, b) } } /** * @param {Decoder} decoder * @return {string} */ export const readTerminatedString = decoder => string.decodeUtf8(readTerminatedUint8Array(decoder)) /** * Look ahead and read varString without incrementing position * * @function * @param {Decoder} decoder * @return {string} */ export const peekVarString = decoder => { const pos = decoder.pos const s = readVarString(decoder) decoder.pos = pos return s } /** * @param {Decoder} decoder * @param {number} len * @return {DataView} */ export const readFromDataView = (decoder, len) => { const dv = new DataView(decoder.arr.buffer, decoder.arr.byteOffset + decoder.pos, len) decoder.pos += len return dv } /** * @param {Decoder} decoder */ export const readFloat32 = decoder => readFromDataView(decoder, 4).getFloat32(0, false) /** * @param {Decoder} decoder */ export const readFloat64 = decoder => readFromDataView(decoder, 8).getFloat64(0, false) /** * @param {Decoder} decoder */ export const readBigInt64 = decoder => /** @type {any} */ (readFromDataView(decoder, 8)).getBigInt64(0, false) /** * @param {Decoder} decoder */ export const readBigUint64 = decoder => /** @type {any} */ (readFromDataView(decoder, 8)).getBigUint64(0, false) /** * @type {Array} */ const readAnyLookupTable = [ decoder => undefined, // CASE 127: undefined decoder => null, // CASE 126: null readVarInt, // CASE 125: integer readFloat32, // CASE 124: float32 readFloat64, // CASE 123: float64 readBigInt64, // CASE 122: bigint decoder => false, // CASE 121: boolean (false) decoder => true, // CASE 120: boolean (true) readVarString, // CASE 119: string decoder => { // CASE 118: object const len = readVarUint(decoder) /** * @type {Object} */ const obj = {} for (let i = 0; i < len; i++) { const key = readVarString(decoder) obj[key] = readAny(decoder) } return obj }, decoder => { // CASE 117: array const len = readVarUint(decoder) const arr = [] for (let i = 0; i < len; i++) { arr.push(readAny(decoder)) } return arr }, readVarUint8Array // CASE 116: Uint8Array ] /** * @param {Decoder} decoder */ export const readAny = decoder => readAnyLookupTable[127 - readUint8(decoder)](decoder) /** * T must not be null. * * @template T */ export class RleDecoder extends Decoder { /** * @param {Uint8Array} uint8Array * @param {function(Decoder):T} reader */ constructor (uint8Array, reader) { super(uint8Array) /** * The reader */ this.reader = reader /** * Current state * @type {T|null} */ this.s = null this.count = 0 } read () { if (this.count === 0) { this.s = this.reader(this) if (hasContent(this)) { this.count = readVarUint(this) + 1 // see encoder implementation for the reason why this is incremented } else { this.count = -1 // read the current value forever } } this.count-- return /** @type {T} */ (this.s) } } export class IntDiffDecoder extends Decoder { /** * @param {Uint8Array} uint8Array * @param {number} start */ constructor (uint8Array, start) { super(uint8Array) /** * Current state * @type {number} */ this.s = start } /** * @return {number} */ read () { this.s += readVarInt(this) return this.s } } export class RleIntDiffDecoder extends Decoder { /** * @param {Uint8Array} uint8Array * @param {number} start */ constructor (uint8Array, start) { super(uint8Array) /** * Current state * @type {number} */ this.s = start this.count = 0 } /** * @return {number} */ read () { if (this.count === 0) { this.s += readVarInt(this) if (hasContent(this)) { this.count = readVarUint(this) + 1 // see encoder implementation for the reason why this is incremented } else { this.count = -1 // read the current value forever } } this.count-- return /** @type {number} */ (this.s) } } export class UintOptRleDecoder extends Decoder { /** * @param {Uint8Array} uint8Array */ constructor (uint8Array) { super(uint8Array) /** * @type {number} */ this.s = 0 this.count = 0 } read () { if (this.count === 0) { this.s = readVarInt(this) // if the sign is negative, we read the count too, otherwise count is 1 const isNegative = math.isNegativeZero(this.s) this.count = 1 if (isNegative) { this.s = -this.s this.count = readVarUint(this) + 2 } } this.count-- return /** @type {number} */ (this.s) } } export class IncUintOptRleDecoder extends Decoder { /** * @param {Uint8Array} uint8Array */ constructor (uint8Array) { super(uint8Array) /** * @type {number} */ this.s = 0 this.count = 0 } read () { if (this.count === 0) { this.s = readVarInt(this) // if the sign is negative, we read the count too, otherwise count is 1 const isNegative = math.isNegativeZero(this.s) this.count = 1 if (isNegative) { this.s = -this.s this.count = readVarUint(this) + 2 } } this.count-- return /** @type {number} */ (this.s++) } } export class IntDiffOptRleDecoder extends Decoder { /** * @param {Uint8Array} uint8Array */ constructor (uint8Array) { super(uint8Array) /** * @type {number} */ this.s = 0 this.count = 0 this.diff = 0 } /** * @return {number} */ read () { if (this.count === 0) { const diff = readVarInt(this) // if the first bit is set, we read more data const hasCount = diff & 1 this.diff = math.floor(diff / 2) // shift >> 1 this.count = 1 if (hasCount) { this.count = readVarUint(this) + 2 } } this.s += this.diff this.count-- return this.s } } export class StringDecoder { /** * @param {Uint8Array} uint8Array */ constructor (uint8Array) { this.decoder = new UintOptRleDecoder(uint8Array) this.str = readVarString(this.decoder) /** * @type {number} */ this.spos = 0 } /** * @return {string} */ read () { const end = this.spos + this.decoder.read() const res = this.str.slice(this.spos, end) this.spos = end return res } } dmonad-lib0-c7e7806/delta/000077500000000000000000000000001512504553500152435ustar00rootroot00000000000000dmonad-lib0-c7e7806/delta/binding.js000066400000000000000000000251141512504553500172160ustar00rootroot00000000000000/* eslint-disable */ // @ts-nocheck // @todo remove all @ts-nocheck and eslint-disable /* global MutationObserver */ import { ObservableV2 } from '../observable.js' import * as delta from './delta.js' import * as dt from './t3.test.js' // eslint-disable-line import * as dom from '../dom.js' import * as set from '../set.js' import * as map from '../map.js' import * as error from '../error.js' import * as math from '../math.js' import * as mux from '../mutex.js' import * as s from '../schema.js' /** * @template T * @typedef {import('../schema.js').Schema} Schema */ /** * @template {delta.AbstractDelta} DeltaA * @template {delta.AbstractDelta} DeltaB */ export class Binding { /** * @param {RDT} a * @param {RDT} b * @param {dt.Template} template */ constructor (a, b, template) { /** * @type {dt.Transformer} */ this.t = template.init() this.a = a this.b = b this._mux = mux.createMutex() this._achanged = this.a.on('change', d => this._mux(() => { const tres = this.t.applyA(d) if (tres.a) { a.update(tres.a) } if (tres.b) { b.update(tres.b) } })) this._bchanged = this.b.on('change', d => this._mux(() => { const tres = this.t.applyB(d) if (tres.b) { this.b.update(tres.b) } if (tres.a) { a.update(tres.a) } })) } destroy = () => { this.a.off('destroy', this.destroy) this.b.off('destroy', this.destroy) this.a.off('change', this._achanged) this.b.off('change', this._bchanged) } } /** * Abstract Interface for a delta-based Replicated Data Type. * * @template {delta.AbstractDelta} Delta * @typedef {ObservableV2<{ 'change': (delta: Delta) => void, 'destroy': (rdt:RDT)=>void }> & { update: (delta: Delta) => any, destroy: () => void }} RDT */ /** * @template {delta.AbstractDelta} DeltaA * @template {dt.Template} Transformer * @param {RDT} a * @param {RDT ? DeltaB : never>} b * @param {dt.Template} template */ export const bind = (a, b, template) => new Binding(a, b, template) /** * @template {delta.AbstractDelta} Delta * @implements RDT * @extends {ObservableV2<{ change: (delta: Delta) => void, 'destroy': (rdt:DeltaRDT)=>void }>} */ class DeltaRDT extends ObservableV2 { /** * @param {Schema} $delta */ constructor ($delta) { super() this.$delta = $delta /** * @type {Delta?} */ this.state = null this._mux = mux.createMutex() } /** * @param {Delta} delta */ update = delta => delta.isEmpty() || this._mux(() => { if (this.state != null) { this.state.apply(delta) } else { this.state = delta } this.emit('change', [delta]) }) destroy () { this.emit('destroy', [this]) super.destroy() } } /** * @template {delta.AbstractDelta} Delta * @param {Schema} $delta */ export const deltaRDT = $delta => new DeltaRDT($delta) /** * @param {Node} domNode */ const domToDelta = domNode => { if (dom.$element.check(domNode)) { const d = delta.node(domNode.nodeName.toLowerCase()) for (let i = 0; i < domNode.attributes.length; i++) { const attr = /** @type {Attr} */ (domNode.attributes.item(i)) d.attributes.set(attr.nodeName, attr.value) } domNode.childNodes.forEach(child => { d.children.insert(dom.$text.check(child) ? child.textContent : [domToDelta(child)]) }) return d } error.unexpectedCase() } /** * @param {DomDelta} d */ const deltaToDom = d => { if (delta.$nodeAny.check(d)) { const n = dom.element(d.name) d.attributes.forEach(change => { if (delta.$insertOp.check(change)) { n.setAttribute(change.key, change.value) } }) d.children.forEach(child => { if (delta.$insertOp.check(child)) { n.append(...child.insert.map(deltaToDom)) } else if (delta.$textOp.check(child)) { n.append(dom.text(child.insert)) } }) return n } error.unexpectedCase() } /** * @param {Element} el * @param {delta.Node} d */ const applyDeltaToDom = (el, d) => { d.attributes.forEach(change => { if (delta.$deleteOp.check(change)) { el.removeAttribute(change.key) } else { el.setAttribute(change.key, change.value) } }) let childIndex = 0 let childOffset = 0 d.children.forEach(change => { let child = el.childNodes[childIndex] || null if (delta.$deleteOp.check(change)) { let len = change.length while (len > 0) { if (dom.$element.check(child)) { child.remove() len-- } else if (dom.$text.check(child)) { const childLen = child.length if (childOffset === 0 && childLen <= len) { child.remove() len -= childLen } else { const spliceLen = math.min(len, childLen - childOffset) child.deleteData(childOffset, spliceLen) if (child.length <= childOffset) { childOffset = 0 childIndex++ } } } } } else if (delta.$insertOp.check(change)) { if (childOffset > 0) { const tchild = dom.$text.cast(child) child = tchild.splitText(childOffset) childIndex++ childOffset = 0 } el.insertBefore(dom.fragment(change.insert.map(deltaToDom)), child) } else if (delta.$modifyOp.check(change)) { applyDeltaToDom(dom.$element.cast(child), change.modify) } else if (delta.$textOp.check(change)) { el.insertBefore(dom.text(change.insert), child) } else { error.unexpectedCase() } }) } export const $domDelta = delta.$node(s.$string, s.$record(s.$string, s.$string), s.$never, { recursive: true, withText: true }) /** * @param {Element} observedNode * @param {MutationRecord[]} mutations * @param {any} origin assign this origin to the generated delta */ const _mutationsToDelta = (observedNode, mutations, origin) => { /** * @typedef {{ removedBefore: Map, added: Set, modified: number, d: delta.Node }} ChangedNodeInfo */ /** * Compute all deltas without recursion. * * 1. mark all changed parents in parentsChanged * 2. fill out necessary information for each changed parent () */ // /** * @type {Map} */ const changedNodes = map.create() /** * @param {Node} node * @return {ChangedNodeInfo} */ const getChangedNodeInfo = node => map.setIfUndefined(changedNodes, node, () => ({ removedBefore: map.create(), added: set.create(), modified: 0, d: delta.node(node.nodeName.toLowerCase()) })) const observedNodeInfo = getChangedNodeInfo(observedNode) mutations.forEach(mutation => { const target = /** @type {HTMLElement} */ (mutation.target) const parent = target.parentNode const attrName = /** @type {string} */ (mutation.attributeName) const newVal = target.getAttribute(attrName) const info = getChangedNodeInfo(target) const d = info.d // go up the tree and mark that a child has been modified for (let changedParent = parent; changedParent != null && getChangedNodeInfo(changedParent).modified++ > 1 && changedParent !== observedNode; changedParent = changedParent.parentNode) { // nop } switch (mutation.type) { case 'attributes': { const attrs = /** @type {delta.Node} */ (d).attributes if (newVal == null) { attrs.delete(attrName) } else { attrs.set(/** @type {string} */ (attrName), newVal) } break } case 'characterData': { error.methodUnimplemented() break } case 'childList': { const targetInfo = getChangedNodeInfo(target) mutation.addedNodes.forEach(node => { targetInfo.added.add(node) }) const removed = mutation.removedNodes.length if (removed > 0) { // @todo this can't work because next can be null targetInfo.removedBefore.set(mutation.nextSibling, removed) } break } } }) changedNodes.forEach((info, node) => { const numOfChildChanges = info.modified + info.removedBefore.size + info.added.size const d = /** @type {delta.Node} */ (info.d) if (numOfChildChanges > 0) { node.childNodes.forEach(nchild => { if (info.removedBefore.has(nchild)) { // can happen separately d.children.delete(/** @type {number} */ (info.removedBefore.get(nchild))) } if (info.added.has(nchild)) { d.children.insert(dom.$text.check(nchild) ? nchild.textContent : [domToDelta(nchild)]) } else if (changedNodes.has(nchild)) { d.children.modify(getChangedNodeInfo(nchild).d) } }) // remove items to the end, if necessary d.children.delete(info.removedBefore.get(null) ?? 0) } d.done() }) observedNodeInfo.d.origin = origin return observedNodeInfo.d } /** * @typedef {delta.RecursiveNode} DomDelta */ /** * @template {DomDelta} [D=DomDelta] * @implements RDT * @extends {ObservableV2<{ change: (delta: D)=>void, destroy: (rdt:DomRDT)=>void }>}>} */ class DomRDT extends ObservableV2 { /** * @param {Element} observedNode */ constructor (observedNode) { super() this.observedNode = observedNode this._mux = mux.createMutex() this.observer = new MutationObserver(this._mutationHandler) this.observer.observe(observedNode, { subtree: true, childList: true, attributes: true, characterDataOldValue: true }) } /** * @param {MutationRecord[]} mutations */ _mutationHandler = mutations => mutations.length > 0 && this._mux(() => { this.emit('change', [/** @type {D} */(_mutationsToDelta(this.observedNode, mutations, this))]) }) /** * @param {D} delta */ update = delta => { if (delta.origin !== this) { // @todo the retrieved changes must be transformed agains the updated changes. need a proper // transaction system this._mutationHandler(this.observer.takeRecords()) this._mux(() => { applyDeltaToDom(this.observedNode, delta) const mutations = this.observer.takeRecords() this.emit('change', [/** @type {D} */(_mutationsToDelta(this.observedNode, mutations, delta.origin))]) }) } } destroy () { this.emit('destroy', [this]) super.destroy() this.observer.disconnect() } } /** * @param {Element} dom */ export const domRDT = dom => new DomRDT(dom) dmonad-lib0-c7e7806/delta/binding.test.js000066400000000000000000000076461512504553500202060ustar00rootroot00000000000000/* eslint-disable */ // @ts-nocheck import * as t from '../testing.js' import * as Λ from './transformer.js' import * as Δ from './index.js' import * as binding from './binding.js' import * as env from '../environment.js' import * as dom from '../dom.js' import * as $ from '../schema.js' export const testBinding = () => { if (!env.isBrowser) t.skip() const el = dom.element('div') const domRDT = binding.domRDT(el) const $deltaRDT = Δ.$node($.$literal('test'), $.$object({ x: $.$string, y: $.$string }), $.$any, { withText: true }) const deltaRDT = binding.deltaRDT($deltaRDT) const template = Λ.node('div', { height: Λ.query('x')($deltaRDT) }, []) const b = binding.bind(deltaRDT, domRDT, template) deltaRDT.update(Δ.node('test')) deltaRDT.update(Δ.node('test', { x: 'some val' }, 'hi there')) console.log(b) console.log('dom html content:', domRDT.observedNode.outerHTML) console.log('delta rdt content:', deltaRDT.state?.toJSON()) console.log('delta rdt content:', deltaRDT.state) } export const testDomBindingBasics = () => { if (!env.isBrowser) t.skip() const el = dom.element('div') const domRDT = binding.domRDT(el) const $deltaRDT = Δ.$node($.$literal('test'), $.$object({ x: $.$string }), $.$any, { withText: true }) const deltaRDT = binding.deltaRDT($deltaRDT) const template = Λ.node('div', { height: Λ.query('x')($deltaRDT) }, []) const b = binding.bind(deltaRDT, domRDT, template) deltaRDT.update(Δ.node('test')) deltaRDT.update(Δ.node('test', { x: 'xval' }, 'hi there')) console.log(b) console.log('dom html content:', domRDT.observedNode.outerHTML) console.log('delta rdt content:', deltaRDT.state?.toJSON()) console.log('delta rdt content:', deltaRDT.state) } export const testDomBindingBackAndForth = () => { if (!env.isBrowser) t.skip() const $deltaRDT = binding.$domDelta const el1 = dom.element('div') const domRDT1 = binding.domRDT(el1) const el2 = dom.element('div') const domRDT2 = binding.domRDT(el2) const deltaRDT1 = binding.deltaRDT($deltaRDT) const deltaRDT2 = binding.deltaRDT($deltaRDT) binding.bind(deltaRDT1, domRDT1, Λ.id($deltaRDT)) binding.bind(domRDT1, deltaRDT2, Λ.id($deltaRDT)) binding.bind(deltaRDT2, domRDT2, Λ.id($deltaRDT)) /** * @param {string} description * @param {() => void} f */ const test = (description, f) => t.group(description, () => { f() t.compare(el1.outerHTML, el2.outerHTML, 'dom nodes match') t.compare(deltaRDT1.state, deltaRDT2.state, 'generated deltas match') }) test('insert paragraph', () => { deltaRDT1.update(Δ.node('div', { id: '43' }, [Δ.node('p', {}, 'text')])) }) test('modify paragraph attr & paragraph content', () => { // @todo fix typings below deltaRDT1.update(Δ.node('div', { id: '42' }, Δ.array(binding.$domDelta).modify(/** @type {never} */ (Δ.node('p', {}, 'new text & old '))))) }) console.log('el1', el1.outerHTML) console.log('el2', el2.outerHTML) console.log('d1', deltaRDT1.state?.toJSON()) console.log('d2', deltaRDT2.state?.toJSON()) } export const testDataToDom = () => { if (!env.isBrowser) t.skip() const $data = Δ.$node($.$literal('data'), $.$object({ version: $.$number, description: $.$string }), $.$any, { withText: true }) const Λdata = Λ.transform($data, $d => Λ.node('h1', Λ.map({ bold: 'true', content: Λ.query('description')($d) }), [])) const dataRDT = binding.deltaRDT($data) const domRDT = binding.domRDT(dom.element('div')) binding.bind(dataRDT, domRDT, Λdata($data)) dataRDT.update(Δ.node('data', { version: 42, description: 'good description' })) console.log('el1', domRDT.observedNode.outerHTML) console.log('d1', dataRDT.state?.toJSON()) domRDT.update(Δ.node('h1', { content: 'new description' })) console.log('el1', domRDT.observedNode.outerHTML) console.log('d1', dataRDT.state?.toJSON()) t.compare( dataRDT.state, Δ.node('data', { version: 42, description: 'new description' }) ) } dmonad-lib0-c7e7806/delta/delta-pitch.test.js000066400000000000000000000105551512504553500207630ustar00rootroot00000000000000import * as delta from 'lib0/delta' import * as t from 'lib0/testing' import * as s from 'lib0/schema' /** * Delta is a versatile format enabling you to efficiently describe changes. It is part of lib0, so * that non-yjs applications can use it without consuming the full Yjs package. It is well suited * for efficiently describing state & changesets. * * Assume we start with the text "hello world". Now we want to delete " world" and add an * exclamation mark. The final content should be "hello!" ("hello world" => "hello!") * * In most editors, you would describe the necessary changes as replace operations using indexes. * However, this might become ambiguous when many changes are involved. * * - delete range 5-11 * - insert "!" at position 11 * * Using the delta format, you can describe the changes similar to what you would do in an text editor. * The "|" describes the current cursor position. * * - d.retain(5) - "|hello world" => "hello| world" - jump over the next five characters * - d.delete(6) - "hello| world" => "hello|" - delete the next 6 characres * - d.insert('!') - "hello!|" - insert "!" at the current position * => compact form: d.retain(5).delete(6).insert('!') * * You can also apply the changes in two distinct steps and then rebase the op so that you can apply * them in two distinct steps. * - delete " world": d1 = delta.create().retain(5).delete(6) * - insert "!": d2 = delta.create().retain(11).insert('!') * - rebase d2 on-top of d1: d2.rebase(d1) == delta.create().retain(5).insert('!') * - merge into a single change: d1.apply(d2) == delta.create().retain(5).delete(6).insert(!) * * @param {t.TestCase} _tc */ export const testDeltaBasics = _tc => { // the state of our text document const state = delta.create().insert('hello world') // describe changes: delete " world" & insert "!" const change = delta.create().retain(5).delete(6).insert('!') // apply changes to state state.apply(change) // compare state to expected state t.assert(state.equals(delta.create().insert('hello!'))) } /** * lib0 also ships a schema library that can be used to validate JSON objects and custom data types, * like Yjs types. * * As a convention, schemas are usually prefixed with a $ sign. This clarifies the difference * between a schema, and an instance of a schema. * * const $myobj = s.$object({ key: s.$number }) * let inputValue: any * if ($myobj.check(inputValue)) { * inputValue // is validated and of type $myobj * } * * We can also define the expected values on a delta. * * @param {t.TestCase} _tc */ export const testDeltaBasicSchema = _tc => { const $d = delta.$delta({ attrs: { key: s.$string }, children: s.$number, text: false }) const d = delta.create($d) // @ts-expect-error d.set('key', false) // invalid change: will throw a type error t.fails(() => { // @ts-expect-error d.apply(delta.create().set('key', false)) // invalid delta: will throw a type error }) } /** * Deltas can describe changes on attributes and children. Textual insertions are children. But we * may also insert json-objects and other deltas as children. * Key-value pairs can be represented as attributes. This "convoluted" changeset enables us to * describe many changes in the same breath: * * delta.create().set('a', 42).retain(5).delete(6).insert('!').unset('b') * * @param {t.TestCase} _tc */ export const testDeltaValues = _tc => { const change = delta.create().set('a', 42).unset('b').retain(5).delete(6).insert('!').insert([{ my: 'custom object' }]) // iterate through attribute changes for (const attrChange of change.attrs) { if (delta.$insertOp.check(attrChange)) { console.log(`set ${attrChange.key} to ${attrChange.value}`) } else if (delta.$deleteOp.check(attrChange)) { console.log(`delete ${attrChange.key}`) } } // iterate through child changes for (const childChange of change.children) { if (delta.$retainOp.check(childChange)) { console.log(`retain ${childChange.retain} child items`) } else if (delta.$deleteOp.check(childChange)) { console.log(`delete ${childChange.delete} child items`) } else if (delta.$insertOp.check(childChange)) { console.log('insert child items:', childChange.insert) } else if (delta.$textOp.check(childChange)) { console.log('insert textual content', childChange.insert) } } } dmonad-lib0-c7e7806/delta/delta.js000066400000000000000000002214111512504553500166730ustar00rootroot00000000000000/** * @beta this API is about to change * * ## Mutability * * Deltas are mutable by default. But references are often shared, by marking a Delta as "done". You * may only modify deltas by applying other deltas to them. Casting a Delta to a DeltaBuilder * manually, will likely modify "shared" state. */ import * as list from '../list.js' import * as object from '../object.js' import * as equalityTrait from '../trait/equality.js' import * as fingerprintTrait from '../trait/fingerprint.js' import * as arr from '../array.js' import * as fun from '../function.js' import * as s from '../schema.js' import * as error from '../error.js' import * as math from '../math.js' import * as rabin from '../hash/rabin.js' import * as encoding from '../encoding.js' import * as buffer from '../buffer.js' import * as patience from '../diff/patience.js' import * as prng from '../prng.js' /** * @typedef {{ * insert?: string[] * insertAt?: number * delete?: string[] * deleteAt?: number * format?: Record * formatAt?: number * }} Attribution */ /** * @type {s.Schema} */ export const $attribution = s.$object({ insert: s.$array(s.$string).optional, insertAt: s.$number.optional, delete: s.$array(s.$string).optional, deleteAt: s.$number.optional, format: s.$record(s.$string, s.$array(s.$string)).optional, formatAt: s.$number.optional }) /** * @typedef {s.Unwrap<$anyOp>} DeltaOps */ /** * @typedef {{ [key: string]: any }} FormattingAttributes */ /** * @typedef {{ * type: 'delta', * name?: string, * attrs?: { [Key in string|number]: DeltaAttrOpJSON }, * children?: Array * }} DeltaJSON */ /** * @typedef {{ type: 'insert', insert: string|Array, format?: { [key: string]: any }, attribution?: Attribution } | { delete: number } | { type: 'retain', retain: number, format?: { [key:string]: any }, attribution?: Attribution } | { type: 'modify', value: object }} DeltaListOpJSON */ /** * @typedef {{ type: 'insert', value: any, prevValue?: any, attribution?: Attribution } | { type: 'delete', prevValue?: any, attribution?: Attribution } | { type: 'modify', value: DeltaJSON }} DeltaAttrOpJSON */ /** * @typedef {TextOp|InsertOp|DeleteOp|RetainOp|ModifyOp} ChildrenOpAny */ /** * @typedef {AttrInsertOp|AttrDeleteOp|AttrModifyOp} AttrOpAny */ /** * @typedef {ChildrenOpAny|AttrOpAny} _OpAny */ /** * @type {s.Schema} */ export const $deltaMapChangeJson = s.$union( s.$object({ type: s.$literal('insert'), value: s.$any, prevValue: s.$any.optional, attribution: $attribution.optional }), s.$object({ type: s.$literal('modify'), value: s.$any }), s.$object({ type: s.$literal('delete'), prevValue: s.$any.optional, attribution: $attribution.optional }) ) /** * @template {{[key:string]: any} | null} Attrs * @param {Attrs} attrs * @return {Attrs} */ const _cloneAttrs = attrs => attrs == null ? attrs : { ...attrs } /** * @template {any} MaybeDelta * @param {MaybeDelta} maybeDelta * @return {MaybeDelta} */ const _markMaybeDeltaAsDone = maybeDelta => $deltaAny.check(maybeDelta) ? /** @type {MaybeDelta} */ (maybeDelta.done()) : maybeDelta export class TextOp extends list.ListNode { /** * @param {string} insert * @param {FormattingAttributes|null} format * @param {Attribution?} attribution */ constructor (insert, format, attribution) { super() // Whenever this is modified, make sure to clear _fingerprint /** * @readonly * @type {string} */ this.insert = insert /** * @readonly * @type {FormattingAttributes|null} */ this.format = format this.attribution = attribution /** * @type {string?} */ this._fingerprint = null } /** * @param {string} newVal */ _updateInsert (newVal) { // @ts-ignore this.insert = newVal this._fingerprint = null } /** * @return {'insert'} */ get type () { return 'insert' } get length () { return this.insert.length } get fingerprint () { return this._fingerprint || (this._fingerprint = buffer.toBase64(encoding.encode(encoder => { encoding.writeVarUint(encoder, 0) // textOp type: 0 encoding.writeVarString(encoder, this.insert) encoding.writeAny(encoder, this.format) }))) } /** * Remove a part of the operation (similar to Array.splice) * * @param {number} offset * @param {number} len */ _splice (offset, len) { this._fingerprint = null // @ts-ignore this.insert = this.insert.slice(0, offset) + this.insert.slice(offset + len) return this } /** * @return {DeltaListOpJSON} */ toJSON () { const { insert, format, attribution } = this return object.assign(/** @type {{type: 'insert', insert: string}} */ ({ type: 'insert', insert }), format != null ? { format } : ({}), attribution != null ? { attribution } : ({})) } /** * @param {TextOp} other */ [equalityTrait.EqualityTraitSymbol] (other) { return fun.equalityDeep(this.insert, other.insert) && fun.equalityDeep(this.format, other.format) && fun.equalityDeep(this.attribution, other.attribution) } /** * @return {TextOp} */ clone (start = 0, end = this.length) { return new TextOp(this.insert.slice(start, end), _cloneAttrs(this.format), _cloneAttrs(this.attribution)) } } /** * @template {fingerprintTrait.Fingerprintable} ArrayContent */ export class InsertOp extends list.ListNode { /** * @param {Array} insert * @param {FormattingAttributes|null} format * @param {Attribution?} attribution */ constructor (insert, format, attribution) { super() /** * @readonly * @type {Array} */ this.insert = insert /** * @readonly * @type {FormattingAttributes?} */ this.format = format /** * @readonly * @type {Attribution?} */ this.attribution = attribution /** * @type {string?} */ this._fingerprint = null } /** * @param {ArrayContent} newVal */ _updateInsert (newVal) { // @ts-ignore this.insert = newVal this._fingerprint = null } /** * @return {'insert'} */ get type () { return 'insert' } get length () { return this.insert.length } /** * @param {number} i * @return {Extract} */ _modValue (i) { /** * @type {any} */ let d = this.insert[i] this._fingerprint = null $deltaAny.expect(d) if (d.isDone) { // @ts-ignore this.insert[i] = (d = clone(d)) return d } return d } get fingerprint () { return this._fingerprint || (this._fingerprint = buffer.toBase64(encoding.encode(encoder => { encoding.writeVarUint(encoder, 1) // insertOp type: 1 encoding.writeVarUint(encoder, this.insert.length) this.insert.forEach(ins => { encoding.writeVarString(encoder, fingerprintTrait.fingerprint(ins)) }) encoding.writeAny(encoder, this.format) }))) } /** * Remove a part of the operation (similar to Array.splice) * * @param {number} offset * @param {number} len */ _splice (offset, len) { this._fingerprint = null this.insert.splice(offset, len) return this } /** * @return {DeltaListOpJSON} */ toJSON () { const { insert, format, attribution } = this return object.assign({ type: /** @type {'insert'} */ ('insert'), insert: insert.map(ins => $deltaAny.check(ins) ? ins.toJSON() : ins) }, format ? { format } : ({}), attribution != null ? { attribution } : ({})) } /** * @param {InsertOp} other */ [equalityTrait.EqualityTraitSymbol] (other) { return fun.equalityDeep(this.insert, other.insert) && fun.equalityDeep(this.format, other.format) && fun.equalityDeep(this.attribution, other.attribution) } /** * @return {InsertOp} */ clone (start = 0, end = this.length) { return new InsertOp(this.insert.slice(start, end).map(_markMaybeDeltaAsDone), _cloneAttrs(this.format), _cloneAttrs(this.attribution)) } } /** * @template {fingerprintTrait.Fingerprintable} [Children=never] * @template {string} [Text=never] */ export class DeleteOp extends list.ListNode { /** * @param {number} len */ constructor (len) { super() this.delete = len /** * @type {(Children|Text) extends never ? null : (Delta?)} */ this.prevValue = null /** * @type {string|null} */ this._fingerprint = null } /** * @return {'delete'} */ get type () { return 'delete' } get length () { return 0 } get fingerprint () { return this._fingerprint || (this._fingerprint = buffer.toBase64(encoding.encode(encoder => { encoding.writeVarUint(encoder, 2) // deleteOp type: 2 encoding.writeVarUint(encoder, this.delete) }))) } /** * Remove a part of the operation (similar to Array.splice) * * @param {number} _offset * @param {number} len */ _splice (_offset, len) { this.prevValue = /** @type {any} */ (this.prevValue?.slice(_offset, len) || null) this._fingerprint = null this.delete -= len return this } /** * @return {DeltaListOpJSON} */ toJSON () { return { delete: this.delete } } /** * @param {DeleteOp} other */ [equalityTrait.EqualityTraitSymbol] (other) { return this.delete === other.delete } clone (start = 0, end = this.delete) { return new DeleteOp(end - start) } } export class RetainOp extends list.ListNode { /** * @param {number} retain * @param {FormattingAttributes|null} format * @param {Attribution?} attribution */ constructor (retain, format, attribution) { super() /** * @readonly * @type {number} */ this.retain = retain /** * @readonly * @type {FormattingAttributes?} */ this.format = format /** * @readonly * @type {Attribution?} */ this.attribution = attribution /** * @type {string|null} */ this._fingerprint = null } /** * @return {'retain'} */ get type () { return 'retain' } get length () { return this.retain } get fingerprint () { return this._fingerprint || (this._fingerprint = buffer.toBase64(encoding.encode(encoder => { encoding.writeVarUint(encoder, 3) // retainOp type: 3 encoding.writeVarUint(encoder, this.retain) encoding.writeAny(encoder, this.format) }))) } /** * Remove a part of the operation (similar to Array.splice) * * @param {number} _offset * @param {number} len */ _splice (_offset, len) { // @ts-ignore this.retain -= len this._fingerprint = null return this } /** * @return {DeltaListOpJSON} */ toJSON () { const { retain, format, attribution } = this return object.assign({ type: /** @type {'retain'} */ ('retain'), retain }, format ? { format } : {}, attribution != null ? { attribution } : {}) } /** * @param {RetainOp} other */ [equalityTrait.EqualityTraitSymbol] (other) { return this.retain === other.retain && fun.equalityDeep(this.format, other.format) && fun.equalityDeep(this.attribution, other.attribution) } clone (start = 0, end = this.retain) { return new RetainOp(end - start, _cloneAttrs(this.format), _cloneAttrs(this.attribution)) } } /** * Delta that can be applied on a YType Embed * * @template {DeltaAny} [DTypes=DeltaAny] */ export class ModifyOp extends list.ListNode { /** * @param {DTypes} delta * @param {FormattingAttributes|null} format * @param {Attribution?} attribution */ constructor (delta, format, attribution) { super() /** * @readonly * @type {DTypes} */ this.value = delta /** * @readonly * @type {FormattingAttributes?} */ this.format = format /** * @readonly * @type {Attribution?} */ this.attribution = attribution /** * @type {string|null} */ this._fingerprint = null } /** * @return {'modify'} */ get type () { return 'modify' } get length () { return 1 } /** * @type {DeltaBuilderAny} */ get _modValue () { /** * @type {any} */ const d = this.value this._fingerprint = null if (d.isDone) { // @ts-ignore return (this.value = clone(d)) } return d } get fingerprint () { // don't cache fingerprint because we don't know when delta changes return this._fingerprint || (this._fingerprint = buffer.toBase64(encoding.encode(encoder => { encoding.writeVarUint(encoder, 4) // modifyOp type: 4 encoding.writeVarString(encoder, this.value.fingerprint) encoding.writeAny(encoder, this.format) }))) } /** * Remove a part of the operation (similar to Array.splice) * * @param {number} _offset * @param {number} _len */ _splice (_offset, _len) { return this } /** * @return {DeltaListOpJSON} */ toJSON () { const { value, attribution, format } = this return object.assign({ type: /** @type {'modify'} */ ('modify'), value: value.toJSON() }, format ? { format } : {}, attribution != null ? { attribution } : {}) } /** * @param {ModifyOp} other */ [equalityTrait.EqualityTraitSymbol] (other) { return this.value[equalityTrait.EqualityTraitSymbol](other.value) && fun.equalityDeep(this.format, other.format) && fun.equalityDeep(this.attribution, other.attribution) } /** * @return {ModifyOp} */ clone () { return new ModifyOp(/** @type {DTypes} */ (this.value.done()), _cloneAttrs(this.format), _cloneAttrs(this.attribution)) } } /** * @template {fingerprintTrait.Fingerprintable} V * @template {string|number} [K=any] */ export class AttrInsertOp { /** * @param {K} key * @param {V} value * @param {V|undefined} prevValue * @param {Attribution?} attribution */ constructor (key, value, prevValue, attribution) { /** * @readonly * @type {K} */ this.key = key /** * @readonly * @type {V} */ this.value = value /** * @readonly * @type {V|undefined} */ this.prevValue = prevValue /** * @readonly * @type {Attribution?} */ this.attribution = attribution /** * @type {string|null} */ this._fingerprint = null } /** * @return {'insert'} */ get type () { return 'insert' } /** * @type {DeltaBuilderAny} */ get _modValue () { /** * @type {any} */ const v = this.value this._fingerprint = null if ($deltaAny.check(v) && v.isDone) { // @ts-ignore return (this.value = clone(v)) } return v } get fingerprint () { return this._fingerprint || (this._fingerprint = buffer.toBase64(encoding.encode(encoder => { encoding.writeVarUint(encoder, 5) // map insert type: 5 encoding.writeAny(encoder, this.key) if ($deltaAny.check(this.value)) { encoding.writeUint8(encoder, 0) encoding.writeVarString(encoder, this.value.fingerprint) } else { encoding.writeUint8(encoder, 1) encoding.writeAny(encoder, this.value) } }))) } toJSON () { const v = this.value const prevValue = this.prevValue const attribution = this.attribution return object.assign({ type: this.type, value: $deltaAny.check(v) ? v.toJSON() : v }, attribution != null ? { attribution } : {}, prevValue !== undefined ? { prevValue } : {}) } /** * @param {AttrInsertOp} other */ [equalityTrait.EqualityTraitSymbol] (other) { return this.key === other.key && fun.equalityDeep(this.value, other.value) && fun.equalityDeep(this.attribution, other.attribution) } /** * @return {AttrInsertOp} */ clone () { return new AttrInsertOp(this.key, _markMaybeDeltaAsDone(this.value), _markMaybeDeltaAsDone(this.prevValue), _cloneAttrs(this.attribution)) } } /** * @template V * @template {string|number} [K=string] */ export class AttrDeleteOp { /** * @param {K} key * @param {V|undefined} prevValue * @param {Attribution?} attribution */ constructor (key, prevValue, attribution) { /** * @type {K} */ this.key = key /** * @type {V|undefined} */ this.prevValue = prevValue this.attribution = attribution /** * @type {string|null} */ this._fingerprint = null } get value () { return undefined } /** * @type {'delete'} */ get type () { return 'delete' } get fingerprint () { return this._fingerprint || (this._fingerprint = buffer.toBase64(encoding.encode(encoder => { encoding.writeVarUint(encoder, 6) // map delete type: 6 encoding.writeAny(encoder, this.key) }))) } /** * @return {DeltaAttrOpJSON} */ toJSON () { const { type, attribution, prevValue } = this return object.assign({ type }, attribution != null ? { attribution } : {}, prevValue !== undefined ? { prevValue } : {}) } /** * @param {AttrDeleteOp} other */ [equalityTrait.EqualityTraitSymbol] (other) { return this.key === other.key && fun.equalityDeep(this.attribution, other.attribution) } clone () { return new AttrDeleteOp(this.key, _markMaybeDeltaAsDone(this.prevValue), _cloneAttrs(this.attribution)) } } /** * @template {DeltaAny} [Modifier=DeltaAny] * @template {string|number} [K=string] */ export class AttrModifyOp { /** * @param {K} key * @param {Modifier} delta */ constructor (key, delta) { /** * @readonly * @type {K} */ this.key = key /** * @readonly * @type {Modifier} */ this.value = delta /** * @type {string|null} */ this._fingerprint = null } /** * @type {'modify'} */ get type () { return 'modify' } get fingerprint () { return this._fingerprint || (this._fingerprint = buffer.toBase64(encoding.encode(encoder => { encoding.writeVarUint(encoder, 7) // map modify type: 7 encoding.writeAny(encoder, this.key) encoding.writeVarString(encoder, this.value.fingerprint) }))) } /** * @return {DeltaBuilder} */ get _modValue () { this._fingerprint = null if (this.value.isDone) { // @ts-ignore this.value = /** @type {any} */ (clone(this.value)) } // @ts-ignore return this.value } /** * @return {DeltaAttrOpJSON} */ toJSON () { return { type: this.type, value: this.value.toJSON() } } /** * @param {AttrModifyOp} other */ [equalityTrait.EqualityTraitSymbol] (other) { return this.key === other.key && this.value[equalityTrait.EqualityTraitSymbol](other.value) } /** * @return {AttrModifyOp} */ clone () { return new AttrModifyOp(this.key, /** @type {Modifier} */ (this.value.done())) } } /** * @type {s.Schema | DeleteOp>} */ export const $deleteOp = s.$custom(o => o != null && (o.constructor === DeleteOp || o.constructor === AttrDeleteOp)) /** * @type {s.Schema | InsertOp>} */ export const $insertOp = s.$custom(o => o != null && (o.constructor === AttrInsertOp || o.constructor === InsertOp)) /** * @template {fingerprintTrait.Fingerprintable} Content * @param {s.Schema} $content * @return {s.Schema | InsertOp>} */ export const $insertOpWith = $content => s.$custom(o => o != null && ( (o.constructor === AttrInsertOp && $content.check(/** @type {AttrInsertOp} */ (o).value)) || (o.constructor === InsertOp && /** @type {InsertOp} */ (o).insert.every(ins => $content.check(ins))) ) ) /** * @type {s.Schema} */ export const $textOp = s.$constructedBy(TextOp) /** * @type {s.Schema} */ export const $retainOp = s.$constructedBy(RetainOp) /** * @type {s.Schema} */ export const $modifyOp = s.$custom(o => o != null && (o.constructor === AttrModifyOp || o.constructor === ModifyOp)) /** * @template {DeltaAny} Modify * @param {s.Schema} $content * @return {s.Schema | ModifyOp>} */ export const $modifyOpWith = $content => s.$custom(o => o != null && ( (o.constructor === AttrModifyOp && $content.check(/** @type {AttrModifyOp} */ (o).value)) || (o.constructor === ModifyOp && $content.check(/** @type {ModifyOp} */ (o).value)) ) ) export const $anyOp = s.$union($insertOp, $deleteOp, $textOp, $modifyOp) /** * @template {Array|string} C1 * @template {Array|string} C2 * @typedef {Extract> extends never * ? never * : (Array<(Extract> extends Array ? (unknown extends AC1 ? never : AC1) : never)>)} MergeListArrays */ /** * @template {{[Key in string|number]: any}} Attrs * @template {string|number} Key * @template {any} Val * @typedef {{ [K in (Key | keyof Attrs)]: (unknown extends Attrs[K] ? never : Attrs[K]) | (Key extends K ? Val : never) }} AddToAttrs */ /** * @template {{[Key in string|number|symbol]: any}} Attrs * @template {{[Key in string|number|symbol]: any}} NewAttrs * @typedef {{ [K in (keyof NewAttrs | keyof Attrs)]: (unknown extends Attrs[K] ? never : Attrs[K]) | (unknown extends NewAttrs[K] ? never : NewAttrs[K]) }} MergeAttrs */ /** * @template X * @typedef {0 extends (1 & X) ? null : X} _AnyToNull */ /** * @template {s.Schema>|null} Schema * @typedef {_AnyToNull extends null ? Delta : (Schema extends s.Schema ? D : never)} AllowedDeltaFromSchema */ /** * @typedef {Delta} DeltaAny */ /** * @typedef {DeltaBuilder} DeltaBuilderAny */ /** * @template {string} [NodeName=any] * @template {{[k:string|number]:any}} [Attrs={}] * @template {fingerprintTrait.Fingerprintable} [Children=never] * @template {string} [Text=never] * @template {s.Schema>|null} [Schema=any] */ export class Delta { /** * @param {NodeName} [name] * @param {Schema} [$schema] */ constructor (name, $schema) { this.name = name || null this.$schema = $schema || null /** * @type {{ [K in keyof Attrs]?: K extends string|number ? (AttrInsertOp|AttrDeleteOp|(Delta extends Attrs[K] ? AttrModifyOp,K> : never)) : never } * & { [Symbol.iterator]: () => Iterator<{ [K in keyof Attrs]: K extends string|number ? (AttrInsertOp|AttrDeleteOp|(Delta extends Attrs[K] ? AttrModifyOp,K> : never)) : never }[keyof Attrs]> } * } */ this.attrs = /** @type {any} */ ({ * [Symbol.iterator] () { for (const k in this) { yield this[k] } } }) /** * @type {list.List< * RetainOp * | DeleteOp * | (Text extends never ? never : TextOp) * | (Children extends never ? never : InsertOp) * | (Delta extends Children ? ModifyOp>> : never) * >} */ this.children = /** @type {any} */ (list.create()) this.childCnt = 0 /** * @type {any} */ this.origin = null /** * @type {string|null} */ this._fingerprint = null this.isDone = false } /** * @type {string} */ get fingerprint () { return this._fingerprint || (this._fingerprint = buffer.toBase64(encoding.encode(encoder => { encoding.writeUint32(encoder, 0xf2ae5680) // "magic number" that ensures that different types of content don't yield the same fingerprint encoding.writeAny(encoder, this.name) /** * @type {Array} */ const keys = [] for (const attr of this.attrs) { keys.push(attr.key) } keys.sort((a, b) => { const aIsString = s.$string.check(a) const bIsString = s.$string.check(b) // numbers first // in ascending order return (aIsString && bIsString) ? a.localeCompare(b) : (aIsString ? 1 : (bIsString ? -1 : (a - b))) }) encoding.writeVarUint(encoder, keys.length) for (const key of keys) { encoding.writeVarString(encoder, /** @type {any} */ (this.attrs[/** @type {keyof Attrs} */ (key)]).fingerprint) } encoding.writeVarUint(encoder, this.children.len) for (const child of this.children) { encoding.writeVarString(encoder, child.fingerprint) } return buffer.toBase64(rabin.fingerprint(rabin.StandardIrreducible128, encoding.toUint8Array(encoder))) }))) } [fingerprintTrait.FingerprintTraitSymbol] () { return this.fingerprint } isEmpty () { return object.isEmpty(this.attrs) && list.isEmpty(this.children) } /** * @return {DeltaJSON} */ toJSON () { const name = this.name /** * @type {any} */ const attrs = {} /** * @type {any} */ const children = [] for (const attr of this.attrs) { attrs[attr.key] = attr.toJSON() } this.children.forEach(val => { children.push(val.toJSON()) }) return object.assign( { type: /** @type {'delta'} */ ('delta') }, (name != null ? { name } : {}), (object.isEmpty(attrs) ? {} : { attrs }), (children.length > 0 ? { children } : {}) ) } /** * @param {Delta} other * @return {boolean} */ equals (other) { return this[equalityTrait.EqualityTraitSymbol](other) } /** * @param {any} other * @return {boolean} */ [equalityTrait.EqualityTraitSymbol] (other) { // @todo it is only necessary to compare finrerprints OR do a deep equality check (remove // childCnt as well) return this.name === other.name && fun.equalityDeep(this.attrs, other.attrs) && fun.equalityDeep(this.children, other.children) && this.childCnt === other.childCnt } /** * @return {DeltaBuilder} */ clone () { return this.slice(0, this.childCnt) } /** * @param {number} start * @param {number} end * @return {DeltaBuilder} */ slice (start = 0, end = this.childCnt) { const cpy = /** @type {DeltaAny} */ (new DeltaBuilder(/** @type {any} */ (this.name), this.$schema)) cpy.origin = this.origin // copy attrs for (const op of this.attrs) { cpy.attrs[op.key] = /** @type {any} */ (op.clone()) } // copy children const slicedLen = end - start let remainingLen = slicedLen /** * @type {ChildrenOpAny?} */ let currNode = this.children.start let currNodeOffset = 0 while (start > 0 && currNode != null) { if (currNode.length <= start) { start -= currNode.length currNode = currNode.next } else { currNodeOffset = start start = 0 } } if (currNodeOffset > 0 && currNode) { const ncpy = currNode.clone(currNodeOffset, currNodeOffset + math.min(remainingLen, currNode.length - currNodeOffset)) list.pushEnd(cpy.children, ncpy) remainingLen -= ncpy.length currNode = currNode.next } while (currNode != null && currNode.length <= remainingLen) { list.pushEnd(cpy.children, currNode.clone()) remainingLen -= currNode.length currNode = currNode.next } if (currNode != null && remainingLen > 0) { list.pushEnd(cpy.children, currNode.clone(0, remainingLen)) remainingLen -= math.min(currNode.length, remainingLen) } cpy.childCnt = slicedLen - remainingLen // @ts-ignore return cpy } /** * Mark this delta as done and perform some cleanup (e.g. remove appended retains without * formats&attributions). In the future, there might be additional merge operations that can be * performed to result in smaller deltas. Set `markAsDone=false` to only perform the cleanup. * * @return {Delta} */ done (markAsDone = true) { if (!this.isDone) { this.isDone = markAsDone const cs = this.children for (let end = cs.end; end !== null && $retainOp.check(end) && end.format == null && end.attribution == null; end = cs.end) { this.childCnt -= end.length list.popEnd(cs) } } return this } } /** * @template {DeltaAny} D * @param {D} d * @return {D extends DeltaBuilder ? DeltaBuilder : never} */ export const clone = d => /** @type {any} */ (d.slice(0, d.childCnt)) /** * Try merging this op with the previous op * @param {list.List} parent * @param {InsertOp|RetainOp|DeleteOp|TextOp|ModifyOp} op */ const tryMergeWithPrev = (parent, op) => { const prevOp = op.prev if ( prevOp?.constructor !== op.constructor || ( (!$deleteOp.check(op) && !$modifyOp.check(op)) && (!fun.equalityDeep(op.format, /** @type {InsertOp} */ (prevOp).format) || !fun.equalityDeep(op.attribution, /** @type {InsertOp} */ (prevOp).attribution)) ) ) { // constructor mismatch or format/attribution mismatch return } // can be merged if ($insertOp.check(op)) { /** @type {InsertOp} */ (prevOp).insert.push(...op.insert) } else if ($retainOp.check(op)) { // @ts-ignore /** @type {RetainOp} */ (prevOp).retain += op.retain } else if ($deleteOp.check(op)) { /** @type {DeleteOp} */ (prevOp).delete += op.delete } else if ($textOp.check(op)) { /** @type {TextOp} */ (prevOp)._updateInsert(/** @type {TextOp} */ (prevOp).insert + op.insert) } else { error.unexpectedCase() } list.remove(parent, op) } /** * Ensures that the delta can be edited. clears _fingerprint cache. * * @param {any} d */ const modDeltaCheck = d => { if (d.isDone) { /** * You tried to modify a delta after it has been marked as "done". */ throw error.create("Readonly Delta can't be modified") } d._fingerprint = null } /** * @template {string} [NodeName=any] * @template {{[key:string|number]:any}} [Attrs={}] * @template {fingerprintTrait.Fingerprintable} [Children=never] * @template {string} [Text=never] * @template {s.Schema>|null} [Schema=any] * @extends {Delta} */ export class DeltaBuilder extends Delta { /** * @param {NodeName} [name] * @param {Schema} [$schema] */ constructor (name, $schema) { super(name, $schema) /** * @type {FormattingAttributes?} */ this.usedAttributes = null /** * @type {Attribution?} */ this.usedAttribution = null } /** * @param {Attribution?} attribution */ useAttribution (attribution) { modDeltaCheck(this) this.usedAttribution = attribution return this } /** * @param {FormattingAttributes?} attributes * @return {this} */ useAttributes (attributes) { modDeltaCheck(this) this.usedAttributes = attributes return this } /** * @param {string} name * @param {any} value */ updateUsedAttributes (name, value) { modDeltaCheck(this) if (value == null) { this.usedAttributes = object.assign({}, this.usedAttributes) delete this.usedAttributes?.[name] if (object.isEmpty(this.usedAttributes)) { this.usedAttributes = null } } else if (!fun.equalityDeep(this.usedAttributes?.[name], value)) { this.usedAttributes = object.assign({}, this.usedAttributes) this.usedAttributes[name] = value } return this } /** * @template {keyof Attribution} NAME * @param {NAME} name * @param {Attribution[NAME]?} value */ updateUsedAttribution (name, value) { modDeltaCheck(this) if (value == null) { this.usedAttribution = object.assign({}, this.usedAttribution) delete this.usedAttribution?.[name] if (object.isEmpty(this.usedAttribution)) { this.usedAttribution = null } } else if (!fun.equalityDeep(this.usedAttribution?.[name], value)) { this.usedAttribution = object.assign({}, this.usedAttribution) this.usedAttribution[name] = value } return this } /** * @template {AllowedDeltaFromSchema extends Delta ? ((Children extends never ? never : Array) | Text) : never} NewContent * @param {NewContent} insert * @param {FormattingAttributes?} [formatting] * @param {Attribution?} [attribution] * @return {DeltaBuilder< * NodeName, * Attrs, * Exclude[number]|Children, * (Extract|Text) extends never ? never : string, * Schema * >} */ insert (insert, formatting = null, attribution = null) { modDeltaCheck(this) const mergedAttributes = mergeAttrs(this.usedAttributes, formatting) const mergedAttribution = mergeAttrs(this.usedAttribution, attribution) /** * @param {TextOp | InsertOp} lastOp */ const checkMergedEquals = lastOp => (mergedAttributes === lastOp.format || fun.equalityDeep(mergedAttributes, lastOp.format)) && (mergedAttribution === lastOp.attribution || fun.equalityDeep(mergedAttribution, lastOp.attribution)) const end = this.children.end if (s.$string.check(insert)) { if ($textOp.check(end) && checkMergedEquals(end)) { end._updateInsert(end.insert + insert) } else if (insert.length > 0) { list.pushEnd(this.children, new TextOp(insert, object.isEmpty(mergedAttributes) ? null : mergedAttributes, object.isEmpty(mergedAttribution) ? null : mergedAttribution)) } this.childCnt += insert.length } else if (arr.isArray(insert)) { if ($insertOp.check(end) && checkMergedEquals(end)) { // @ts-ignore end.insert.push(...insert) end._fingerprint = null } else if (insert.length > 0) { list.pushEnd(this.children, new InsertOp(insert, object.isEmpty(mergedAttributes) ? null : mergedAttributes, object.isEmpty(mergedAttribution) ? null : mergedAttribution)) } this.childCnt += insert.length } return /** @type {any} */ (this) } /** * @template {AllowedDeltaFromSchema extends Delta ? Extract> : never} NewContent * @param {NewContent} modify * @param {FormattingAttributes?} formatting * @param {Attribution?} attribution * @return {DeltaBuilder< * NodeName, * Attrs, * Exclude[number]|Children, * (Extract|Text) extends string ? string : never, * Schema * >} */ modify (modify, formatting = null, attribution = null) { modDeltaCheck(this) const mergedAttributes = mergeAttrs(this.usedAttributes, formatting) const mergedAttribution = mergeAttrs(this.usedAttribution, attribution) list.pushEnd(this.children, new ModifyOp(modify, object.isEmpty(mergedAttributes) ? null : mergedAttributes, object.isEmpty(mergedAttribution) ? null : mergedAttribution)) this.childCnt += 1 return /** @type {any} */ (this) } /** * @param {number} len * @param {FormattingAttributes?} [format] * @param {Attribution?} [attribution] */ retain (len, format = null, attribution = null) { modDeltaCheck(this) const mergedFormats = mergeAttrs(this.usedAttributes, format) const mergedAttribution = mergeAttrs(this.usedAttribution, attribution) const lastOp = /** @type {RetainOp|InsertOp} */ (this.children.end) if (lastOp instanceof RetainOp && fun.equalityDeep(mergedFormats, lastOp.format) && fun.equalityDeep(mergedAttribution, lastOp.attribution)) { // @ts-ignore lastOp.retain += len } else if (len > 0) { list.pushEnd(this.children, new RetainOp(len, mergedFormats, mergedAttribution)) } this.childCnt += len return this } /** * @param {number} len */ delete (len) { modDeltaCheck(this) const lastOp = /** @type {DeleteOp|InsertOp} */ (this.children.end) if (lastOp instanceof DeleteOp) { lastOp.delete += len } else if (len > 0) { list.pushEnd(this.children, new DeleteOp(len)) } this.childCnt += len return this } /** * @template {AllowedDeltaFromSchema extends Delta ? (keyof Attrs) : never} Key * @template {AllowedDeltaFromSchema extends Delta ? (Attrs[Key]) : never} Val * @param {Key} key * @param {Val} val * @param {Attribution?} attribution * @param {Val|undefined} [prevValue] * @return {DeltaBuilder< * NodeName, * { [K in keyof AddToAttrs]: AddToAttrs[K] }, * Children, * Text, * Schema * >} */ set (key, val, attribution = null, prevValue) { modDeltaCheck(this) this.attrs[key] = /** @type {any} */ (new AttrInsertOp(key, val, prevValue, mergeAttrs(this.usedAttribution, attribution))) return /** @type {any} */ (this) } /** * @template {AllowedDeltaFromSchema extends Delta ? Attrs : never} NewAttrs * @param {NewAttrs} attrs * @param {Attribution?} attribution * @return {DeltaBuilder< * NodeName, * { [K in keyof MergeAttrs]: MergeAttrs[K] }, * Children, * Text, * Schema * >} */ setMany (attrs, attribution = null) { modDeltaCheck(this) for (const k in attrs) { this.set(/** @type {any} */ (k), attrs[k], attribution) } return /** @type {any} */ (this) } /** * @template {AllowedDeltaFromSchema extends Delta ? keyof As : never} Key * @param {Key} key * @param {Attribution?} attribution * @param {any} [prevValue] * @return {DeltaBuilder< * NodeName, * { [K in keyof AddToAttrs]: AddToAttrs[K] }, * Children, * Text, * Schema * >} */ unset (key, attribution = null, prevValue) { modDeltaCheck(this) this.attrs[key] = /** @type {any} */ (new AttrDeleteOp(key, prevValue, mergeAttrs(this.usedAttribution, attribution))) return /** @type {any} */ (this) } /** * @template {AllowedDeltaFromSchema extends Delta ? { [K in keyof As]: Extract> extends never ? never : K }[keyof As] : never} Key * @template {AllowedDeltaFromSchema extends Delta ? Extract> : never} D * @param {Key} key * @param {D} modify * @return {DeltaBuilder< * NodeName, * { [K in keyof AddToAttrs]: AddToAttrs[K] }, * Children, * Text, * Schema * >} */ update (key, modify) { modDeltaCheck(this) this.attrs[key] = /** @type {any} */ (new AttrModifyOp(key, modify)) return /** @type {any} */ (this) } /** * @param {Delta} other */ apply (other) { modDeltaCheck(this) this.$schema?.expect(other) // apply attrs for (const op of other.attrs) { const c = /** @type {AttrInsertOp|AttrDeleteOp|AttrModifyOp} */ (this.attrs[op.key]) if ($modifyOp.check(op)) { if ($deltaAny.check(c?.value)) { c._modValue.apply(op.value) } else { // then this is a simple modify // @ts-ignore this.attrs[op.key] = op.clone() } } else if ($insertOp.check(op)) { // @ts-ignore op.prevValue = c?.value // @ts-ignore this.attrs[op.key] = op.clone() } else if ($deleteOp.check(op)) { op.prevValue = c?.value delete this.attrs[op.key] } } // apply children /** * @type {ChildrenOpAny?} */ let opsI = this.children.start let offset = 0 /** * At the end, we will try to merge this op, and op.next op with their respective previous op. * * Hence, anytime an op is cloned, deleted, or inserted (anytime list.* api is used) we must add * an op to maybeMergeable. * * @type {Array|RetainOp|DeleteOp|TextOp|ModifyOp>} */ const maybeMergeable = [] /** * @template {InsertOp|RetainOp|DeleteOp|TextOp|ModifyOp|null} OP * @param {OP} op * @return {OP} */ const scheduleForMerge = op => { op && maybeMergeable.push(op) return op } other.children.forEach(op => { if ($textOp.check(op) || $insertOp.check(op)) { if (offset === 0) { list.insertBetween(this.children, opsI == null ? this.children.end : opsI.prev, opsI, scheduleForMerge(op.clone())) } else { // @todo inmplement "splitHelper" and "insertHelper" - I'm splitting all the time and // forget to update opsI if (opsI == null) error.unexpectedCase() const cpy = scheduleForMerge(opsI.clone(offset)) opsI._splice(offset, opsI.length - offset) list.insertBetween(this.children, opsI, opsI.next || null, cpy) list.insertBetween(this.children, opsI, cpy || null, scheduleForMerge(op.clone())) opsI = cpy offset = 0 } this.childCnt += op.insert.length } else if ($retainOp.check(op)) { let retainLen = op.length if (offset > 0 && opsI != null && op.format != null && !$deleteOp.check(opsI) && !object.every(op.format, (v, k) => fun.equalityDeep(v, /** @type {InsertOp|RetainOp|ModifyOp} */ (opsI).format?.[k] || null))) { // need to split current op const cpy = scheduleForMerge(opsI.clone(offset)) opsI._splice(offset, opsI.length - offset) list.insertBetween(this.children, opsI, opsI.next || null, cpy) opsI = cpy offset = 0 } while (opsI != null && opsI.length - offset <= retainLen) { op.format != null && updateOpFormat(opsI, op.format) retainLen -= opsI.length - offset opsI = opsI?.next || null offset = 0 } if (opsI != null) { if (op.format != null && retainLen > 0) { // split current op and apply format const cpy = scheduleForMerge(opsI.clone(retainLen)) opsI._splice(retainLen, opsI.length - retainLen) list.insertBetween(this.children, opsI, opsI.next || null, cpy) updateOpFormat(opsI, op.format) opsI = cpy } else { offset += retainLen } } else if (retainLen > 0) { list.pushEnd(this.children, scheduleForMerge(new RetainOp(retainLen, op.format, op.attribution))) this.childCnt += retainLen } } else if ($deleteOp.check(op)) { let remainingLen = op.delete while (remainingLen > 0) { if (opsI == null) { list.pushEnd(this.children, scheduleForMerge(new DeleteOp(remainingLen))) this.childCnt += remainingLen break } else if (opsI instanceof DeleteOp) { const delLen = opsI.length - offset // the same content can't be deleted twice, remove duplicated deletes if (delLen >= remainingLen) { offset = 0 opsI = opsI.next } else { offset += remainingLen } remainingLen -= delLen } else { // insert / embed / retain / modify ⇒ replace // case1: delete o fully // case2: delete some part of beginning // case3: delete some part of end // case4: delete some part of center const delLen = math.min(opsI.length - offset, remainingLen) this.childCnt -= delLen if (opsI.length === delLen) { // case 1 offset = 0 scheduleForMerge(opsI.next) list.remove(this.children, opsI) opsI = opsI.next } else if (offset === 0) { // case 2 offset = 0 opsI._splice(0, delLen) } else if (offset + delLen === opsI.length) { // case 3 opsI._splice(offset, delLen) offset = 0 opsI = opsI.next } else { // case 4 opsI._splice(offset, delLen) } remainingLen -= delLen } } } else if ($modifyOp.check(op)) { if (opsI == null) { list.pushEnd(this.children, op.clone()) this.childCnt += 1 return } if ($modifyOp.check(opsI)) { opsI._modValue.apply(op.value) } else if ($insertOp.check(opsI)) { opsI._modValue(offset).apply(op.value) } else if ($retainOp.check(opsI)) { if (offset > 0) { const cpy = scheduleForMerge(opsI.clone(0, offset)) // skipped len opsI._splice(0, offset) // new remainder list.insertBetween(this.children, opsI.prev, opsI, cpy) // insert skipped len offset = 0 } list.insertBetween(this.children, opsI.prev, opsI, scheduleForMerge(op.clone())) // insert skipped len if (opsI.length === 1) { list.remove(this.children, opsI) } else { opsI._splice(0, 1) scheduleForMerge(opsI) } } else if ($deleteOp.check(opsI)) { // nop } else { error.unexpectedCase() } } else { error.unexpectedCase() } }) maybeMergeable.forEach(op => { // check if this is still integrated if (op.prev?.next === op) { tryMergeWithPrev(this.children, op) op.next && tryMergeWithPrev(this.children, op.next) } }) return this } /** * @param {DeltaAny} other * @param {boolean} priority */ rebase (other, priority) { modDeltaCheck(this) /** * Rebase attributes * * - insert vs delete ⇒ insert takes precedence * - insert vs modify ⇒ insert takes precedence * - insert vs insert ⇒ priority decides * - delete vs modify ⇒ delete takes precedence * - delete vs delete ⇒ current delete op is removed because item has already been deleted * - modify vs modify ⇒ rebase using priority */ for (const op of this.attrs) { if ($insertOp.check(op)) { if ($insertOp.check(other.attrs[op.key]) && !priority) { delete this.attrs[op.key] } } else if ($deleteOp.check(op)) { const otherOp = other.attrs[/** @type {any} */ (op.key)] if ($insertOp.check(otherOp)) { delete this.attrs[otherOp.key] } } else if ($modifyOp.check(op)) { const otherOp = other.attrs[/** @type {any} */ (op.key)] if (otherOp == null) { // nop } else if ($modifyOp.check(otherOp)) { op._modValue.rebase(otherOp.value, priority) } else { delete this.attrs[otherOp.key] } } } /** * Rebase children. * * Precedence: insert with higher priority comes first. Op with less priority is transformed to * be inserted later. * * @todo always check if inser OR text */ /** * @type {ChildrenOpAny?} */ let currChild = this.children.start let currOffset = 0 /** * @type {ChildrenOpAny?} */ let otherChild = other.children.start let otherOffset = 0 while (currChild != null && otherChild != null) { if ($insertOp.check(currChild) || $textOp.check(currChild)) { /** * Transforming *insert*. If other is.. * - insert: transform based on priority * - retain/delete/modify: transform next op against other */ if ($insertOp.check(otherChild) || $modifyOp.check(otherChild) || $textOp.check(otherChild)) { if (!priority) { list.insertBetween(this.children, currChild.prev, currChild, new RetainOp(otherChild.length, null, null)) this.childCnt += otherChild.length // curr is transformed against other, transform curr against next otherOffset = otherChild.length } else { // curr stays as is, transform next op currOffset = currChild.length } } else { // otherChild = delete | retain | modify - curr stays as is, transform next op currOffset = currChild.length } } else if ($modifyOp.check(currChild)) { /** * Transforming *modify*. If other is.. * - insert: adjust position * - modify: rebase curr modify on other modify * - delete: remove modify * - retain: adjust offset */ if ($insertOp.check(otherChild) || $textOp.check(otherChild)) { // @todo: with all list changes (retain insertions, removal), try to merge the surrounding // ops later list.insertBetween(this.children, currChild.prev, currChild, new RetainOp(otherChild.length, null, null)) this.childCnt += otherChild.length // curr is transformed against other, transform curr against next otherOffset = otherChild.length } else { if ($modifyOp.check(otherChild)) { /** @type {any} */ (currChild.value).rebase(otherChild, priority) } else if ($deleteOp.check(otherChild)) { list.remove(this.children, currChild) this.childCnt -= 1 } currOffset += 1 otherOffset += 1 } } else { // DeleteOp | RetainOp const maxCommonLen = math.min(currChild.length - currOffset, otherChild.length - otherOffset) /** * Transforming *retain* OR *delete*. If other is.. * - retain / modify: adjust offsets * - delete: shorten curr op * - insert: split curr op and insert retain */ if ($retainOp.check(otherChild) || $modifyOp.check(otherChild)) { currOffset += maxCommonLen otherOffset += maxCommonLen } else if ($deleteOp.check(otherChild)) { if ($retainOp.check(currChild)) { // @ts-ignore currChild.retain -= maxCommonLen } else if ($deleteOp.check(currChild)) { currChild.delete -= maxCommonLen } this.childCnt -= maxCommonLen } else { // insert/text.check(currOp) if (currOffset > 0) { const leftPart = currChild.clone(currOffset) list.insertBetween(this.children, currChild.prev, currChild, leftPart) currChild._splice(currOffset, currChild.length - currOffset) currOffset = 0 } list.insertBetween(this.children, currChild.prev, currChild, new RetainOp(otherChild.length, null, null)) this.childCnt += otherChild.length otherOffset = otherChild.length } } if (currOffset >= currChild.length) { currChild = currChild.next currOffset = 0 } if (otherOffset >= otherChild.length) { otherChild = otherChild.next otherOffset = 0 } } return this } /** * Same as doing `delta.rebase(other.inverse())`, without creating a temporary delta. * * @param {DeltaAny} other * @param {boolean} priority */ rebaseOnInverse (other, priority) { modDeltaCheck(this) // @todo console.info('method rebaseOnInverse unimplemented') return this } /** * Append child ops from one op to the other. * * delta.create().insert('a').append(delta.create().insert('b')) // => insert "ab" * * @template {DeltaAny} OtherDelta * @param {OtherDelta} other * @return {CastToDelta extends Delta ? DeltaBuilder : never} */ append (other) { const children = this.children const prevLast = children.end // @todo Investigate. Above is a typescript issue. It is necessary to cast OtherDelta to a Delta first before // inferring type, otherwise Children will contain Text. for (const child of other.children) { list.pushEnd(children, child.clone()) } this.childCnt += other.childCnt prevLast?.next && tryMergeWithPrev(children, prevLast.next) // @ts-ignore return this } } /** * @param {ChildrenOpAny} op * @param {{[k:string]:any}} formatUpdate */ const updateOpFormat = (op, formatUpdate) => { if (!$deleteOp.check(op)) { // apply formatting attributes for (const k in formatUpdate) { const v = formatUpdate[k] if (v != null || $retainOp.check(op)) { // never modify formats /** @type {any} */ (op).format = object.assign({}, op.format, { [k]: v }) } else if (op.format != null) { const { [k]: _, ...rest } = op.format ;/** @type {any} */ (op).format = rest } } } } /** * @template {DeltaAny} D * @typedef {D extends DeltaBuilder ? Delta : D} CastToDelta */ /** * @template {string} NodeName * @template {{ [key: string|number]: any }} [Attrs={}] * @template {fingerprintTrait.Fingerprintable|never} [Children=never] * @template {string|never} [Text=never] * @typedef {Delta|RecursiveDelta,Text>} RecursiveDelta */ /** * @template {string} Name * @template {{[k:string|number]:any}} Attrs * @template {fingerprintTrait.Fingerprintable} Children * @template {boolean} HasText * @template {{ [k:string]:any }} Formats * @template {boolean} Recursive * @extends {s.Schema : never), * HasText extends true ? string : never, * any>>} */ export class $Delta extends s.Schema { /** * @param {s.Schema} $name * @param {s.Schema} $attrs * @param {s.Schema} $children * @param {HasText} hasText * @param {s.Schema} $formats * @param {Recursive} recursive */ constructor ($name, $attrs, $children, hasText, $formats, recursive) { super() const $attrsPartial = s.$$object.check($attrs) ? $attrs.partial : $attrs if (recursive) { // @ts-ignore $children = s.$union($children, this) } this.shape = { $name, $attrs: $attrsPartial, $children, hasText, $formats } } /** * @param {any} o * @param {s.ValidationError} [err] * @return {o is Delta< * Name, * Attrs, * Children|(Recursive extends true ? RecursiveDelta : never), * HasText extends true ? string : never, * any>} */ check (o, err = undefined) { const { $name, $attrs, $children, hasText, $formats } = this.shape if (!(o instanceof Delta)) { err?.extend(null, 'Delta', o?.constructor.name, 'Constructor match failed') } else if (o.name != null && !$name.check(o.name, err)) { err?.extend('Delta.name', $name.toString(), o.name, 'Unexpected node name') } else if (list.toArray(o.children).some(c => (!hasText && $textOp.check(c)) || (hasText && $textOp.check(c) && c.format != null && !$formats.check(c.format)) || ($insertOp.check(c) && !c.insert.every(ins => $children.check(ins))))) { err?.extend('Delta.children', '', '', 'Children don\'t match the schema') } else if (object.some(o.attrs, (op, k) => $insertOp.check(op) && !$attrs.check({ [k]: op.value }, err))) { err?.extend('Delta.attrs', '', '', 'Attrs don\'t match the schema') } else { return true } return false } } /** * @template {s.Schema|string|Array} [NodeNameSchema=s.Schema] * @template {s.Schema<{ [key: string|number]: any }>|{ [key:string|number]:any }} [AttrsSchema=s.Schema<{}>] * @template {any} [ChildrenSchema=s.Schema] * @template {boolean} [HasText=false] * @template {boolean} [Recursive=false] * @template {{ [k:string]:any }} [Formats={[k:string]:any}] * @param {object} opts * @param {NodeNameSchema?} [opts.name] * @param {AttrsSchema?} [opts.attrs] What key-value pairs are included. * @param {ChildrenSchema?} [opts.children] The type of content in `insertOp` * @param {HasText} [opts.text] Whether this delta contains text using `textOp` * @param {Formats} [opts.formats] * @param {Recursive} [opts.recursive] * @return {[s.Unwrap>,s.Unwrap>,s.Unwrap>] extends [infer NodeName, infer Attrs, infer Children] ? s.Schema : never), * HasText extends true ? string : never * >> : never} */ export const $delta = ({ name, attrs, children, text, formats, recursive }) => /** @type {any} */ (new $Delta( name == null ? s.$any : s.$(name), /** @type {any} */ (attrs == null ? s.$object({}) : s.$(attrs)), /** @type {any} */ (children == null ? s.$never : s.$(children)), text ?? false, formats == null ? s.$any : s.$(formats), recursive ?? false )) export const $$delta = s.$constructedBy($Delta) /** * @todo remove this * * @template {s.Schema|string|Array} [NodeNameSchema=s.Schema] * @template {s.Schema<{ [key: string|number]: any }>|{ [key:string|number]:any }} [AttrsSchema=s.Schema<{}>] * @template {any} [ChildrenSchema=s.Schema] * @template {boolean} [HasText=false] * @template {boolean} [Recursive=false] * @param {object} opts * @param {NodeNameSchema?} [opts.name] * @param {AttrsSchema?} [opts.attrs] * @param {ChildrenSchema?} [opts.children] * @param {HasText} [opts.text] * @param {Recursive} [opts.recursive] * @return {[s.Unwrap>,s.Unwrap>,s.Unwrap>] extends [infer NodeName, infer Attrs, infer Children] ? s.Schema : never), * HasText extends true ? string : never * >> : never} */ export const _$delta = ({ name, attrs, children, text, recursive }) => { /** * @type {s.Schema>} */ let $arrContent = children == null ? s.$never : s.$array(s.$(children)) const $name = name == null ? s.$any : s.$(name) const $attrsPartial = attrs == null ? s.$object({}) : (s.$$record.check(attrs) ? attrs : /** @type {any} */ (s.$(attrs)).partial) const $d = s.$instanceOf(Delta, /** @param {Delta} d */ d => { if ( !$name.check(d.name) || object.some(d.attrs, (op, k) => $insertOp.check(op) && !$attrsPartial.check({ [k]: op.value }) ) ) return false for (const op of d.children) { if ((!text && $textOp.check(op)) || ($insertOp.check(op) && !$arrContent.check(op.insert))) { return false } } return true }) if (recursive) { $arrContent = children == null ? s.$array($d) : s.$array(s.$(children), $d) } return /** @type {any} */ ($d) } /** * @type {s.Schema} */ export const $deltaAny = /** @type {any} */ (s.$instanceOf(Delta)) /** * @type {s.Schema} */ export const $deltaBuilderAny = /** @type {any} */ (s.$instanceOf(DeltaBuilder)) /** * Helper function to merge attribution and attributes. The latter input "wins". * * @template {{ [key: string]: any }} T * @param {T | null} a * @param {T | null} b */ export const mergeAttrs = (a, b) => object.isEmpty(a) ? (object.isEmpty(b) ? null : b) : (object.isEmpty(b) ? a : object.assign({}, a, b)) /** * @template {DeltaAny|null} D * @param {D} a * @param {D} b * @return {D} */ export const mergeDeltas = (a, b) => { if (a != null && b != null) { const c = clone(a) c.apply(b) return /** @type {any} */ (c) } return a == null ? b : (a || null) } /** * @template {DeltaAny} D * @param {prng.PRNG} gen * @param {s.Schema} $d * @return {D extends Delta ? DeltaBuilder : never} */ export const random = (gen, $d) => { const { $name, $attrs, $children, hasText, $formats: $formats_ } = /** @type {$Delta} */ (/** @type {any} */ ($d)).shape const d = s.$$any.check($name) ? create($deltaAny) : create(s.random(gen, $name), $deltaAny) const $formats = s.$$any.check($formats_) ? s.$null : $formats_ prng.bool(gen) && d.setMany(s.random(gen, $attrs)) for (let i = prng.uint32(gen, 0, 5); i > 0; i--) { if (hasText && prng.bool(gen)) { d.insert(prng.word(gen), s.random(gen, $formats)) } else if (!s.$$never.check($children)) { /** * @type {Array} */ const ins = [] let insN = prng.int32(gen, 0, 5) while (insN--) { ins.push(s.random(gen, $children)) } d.insert(ins, s.random(gen, $formats)) } } return /** @type {any} */ (d) } /** * @overload * @return {DeltaBuilder} */ /** * @template {string} NodeName * @overload * @param {NodeName} nodeName * @return {DeltaBuilder} */ /** * @template {string} NodeName * @template {s.Schema} Schema * @overload * @param {NodeName} nodeName * @param {Schema} schema * @return {Schema extends s.Schema> ? DeltaBuilder : never} */ /** * @template {s.Schema} Schema * @overload * @param {Schema} schema * @return {Schema extends s.Schema> ? DeltaBuilder : never} */ /** * @template {string|null} NodeName * @template {{[k:string|number]:any}|null} Attrs * @template {Array|string} [Children=never] * @overload * @param {NodeName} nodeName * @param {Attrs} attrs * @param {Children} [children] * @return {DeltaBuilder< * NodeName extends null ? any : NodeName, * Attrs extends null ? {} : Attrs, * Extract> extends Array ? (unknown extends Ac ? never : Ac) : never, * Extract, * null * >} */ /** * @param {string|s.Schema} [nodeNameOrSchema] * @param {{[K:string|number]:any}|s.Schema} [attrsOrSchema] * @param {(Array|string)} [children] * @return {DeltaBuilder} */ export const create = (nodeNameOrSchema, attrsOrSchema, children) => { const nodeName = /** @type {any} */ (s.$string.check(nodeNameOrSchema) ? nodeNameOrSchema : null) const schema = /** @type {any} */ (s.$$schema.check(nodeNameOrSchema) ? nodeNameOrSchema : (s.$$schema.check(attrsOrSchema) ? attrsOrSchema : null)) const d = /** @type {DeltaBuilder} */ (new DeltaBuilder(nodeName, schema)) if (s.$objectAny.check(attrsOrSchema)) { d.setMany(attrsOrSchema) } children && d.insert(children) return d } // DELTA TEXT /** * @template {fingerprintTrait.Fingerprintable} [Embeds=never] * @typedef {Delta} TextDelta */ /** * @template {fingerprintTrait.Fingerprintable} [Embeds=never] * @typedef {DeltaBuilder} TextDeltaBuilder */ /** * @template {Array>} [$Embeds=any] * @param {$Embeds} $embeds * @return {s.Schema extends null ? never : ($Embeds extends Array> ? $C : never)>>} */ export const $text = (...$embeds) => /** @type {any} */ ($delta({ children: s.$union(...$embeds), text: true })) export const $textOnly = $text() /** * @template {s.Schema>} [Schema=s.Schema>] * @param {Schema} [$schema] * @return {Schema extends s.Schema> ? DeltaBuilder : never} */ export const text = $schema => /** @type {any} */ (create($schema || $textOnly)) /** * @template {fingerprintTrait.Fingerprintable} Children * @typedef {Delta} ArrayDelta */ /** * @template {fingerprintTrait.Fingerprintable} Children * @typedef {DeltaBuilder} ArrayDeltaBuilder */ /** * @template {any|s.Schema} $Children * @param {$Children} [$children] * @return {s.Schema>>>} */ export const $array = $children => /** @type {any} */ ($delta({ children: $children })) /** * @template {s.Schema>} [$Schema=never] * @param {$Schema} $schema * @return {$Schema extends never ? ArrayDeltaBuilder : DeltaBuilder} */ export const array = $schema => /** @type {any} */ ($schema ? create($schema) : create()) /** * @template {{ [K: string|number]: any }} Attrs * @typedef {Delta} MapDelta */ /** * @template {{ [K: string|number]: any }} Attrs * @typedef {DeltaBuilder} MapDeltaBuilder */ /** * @template {{ [K: string|number]: any }} $Attrs * @param {s.Schema<$Attrs>} $attrs * @return {s.Schema>} */ export const $map = $attrs => /** @type {any} */ ($delta({ attrs: $attrs })) /** * @template {s.Schema>|undefined} [$Schema=undefined] * @param {$Schema} [$schema] * @return {$Schema extends s.Schema> ? DeltaBuilder : MapDeltaBuilder<{}>} */ export const map = $schema => /** @type {any} */ (create(/** @type {any} */ ($schema))) /** * @template {DeltaAny} D * @param {D} d1 * @param {NoInfer} d2 * @return {D extends Delta ? DeltaBuilder : never} */ export const diff = (d1, d2) => { /** * @type {DeltaBuilderAny} */ const d = create() if (d1.fingerprint !== d2.fingerprint) { let left1 = d1.children.start let left2 = d2.children.start let right1 = d1.children.end let right2 = d2.children.end let commonPrefixOffset = 0 // perform a patience sort // 1) remove common prefix and suffix while (left1 != null && left1.fingerprint === left2?.fingerprint) { if (!$deleteOp.check(left1)) { commonPrefixOffset += left1.length } left1 = left1.next left2 = left2.next } while (right1 !== null && right1 !== left1 && right1.fingerprint === right2?.fingerprint) { right1 = right1.prev right2 = right2.prev } /** * @type {Array} */ const ops1 = [] /** * @type {Array} */ const ops2 = [] while (left1 !== null && left1 !== right1?.next) { ops1.push(left1) left1 = left1.next } while (left2 !== null && left2 !== right2?.next) { ops2.push(left2) left2 = left2.next } const fprints1 = ops1.map(op => op.fingerprint) const fprints2 = ops2.map(op => op.fingerprint) const changeset = patience.diff(fprints1, fprints2) d.retain(commonPrefixOffset) for (let i = 0, lastIndex1 = 0, currIndexOffset2 = 0; i < changeset.length; i++) { const change = changeset[i] d.retain(change.index - lastIndex1) // insert minimal diff at curred position in d /** * * @todo it would be better if these would be slices of delta (an actual delta) * * @param {ChildrenOpAny[]} opsIs * @param {ChildrenOpAny[]} opsShould */ const diffAndApply = (opsIs, opsShould) => { const d = create() // @todo unoptimized implementation. Convert content to array and diff that based on // generated fingerprints. We probably could do better and cache more information. // - benchmark // - cache fingerprints in ops /** * @type {Array} */ const isContent = opsIs.flatMap(op => $insertOp.check(op) ? op.insert : ($textOp.check(op) ? op.insert.split('') : error.unexpectedCase())) /** * @type {Array} */ const shouldContent = opsShould.flatMap(op => $insertOp.check(op) ? op.insert : ($textOp.check(op) ? op.insert.split('') : error.unexpectedCase())) const isContentFingerprinted = isContent.map(c => s.$string.check(c) ? c : fingerprintTrait.fingerprint(c)) const shouldContentFingerprinted = shouldContent.map(c => s.$string.check(c) ? c : fingerprintTrait.fingerprint(c)) const hasFormatting = opsIs.some(op => !$deleteOp.check(op) && op.format != null) || opsShould.some(op => !$deleteOp.check(op) && op.format != null) /** * @type {{ index: number, insert: Array, remove: Array }[]} */ const cdiff = patience.diff(isContentFingerprinted, shouldContentFingerprinted) // overwrite fingerprinted content with actual content for (let i = 0, adj = 0; i < cdiff.length; i++) { const cd = cdiff[i] cd.remove = isContent.slice(cd.index, cd.index + cd.remove.length) cd.insert = shouldContent.slice(cd.index + adj, cd.index + adj + cd.insert.length) adj += cd.insert.length - cd.remove.length } for (let i = 0, lastIndex = 0; i < cdiff.length; i++) { const cd = cdiff[i] d.retain(cd.index - lastIndex) lastIndex = cd.index let cdii = 0 let cdri = 0 // try to match as much content as possible, preferring to skip over non-deltas for (; cdii < cd.insert.length && cdri < cd.remove.length;) { const a = cd.insert[cdii] const b = cd.remove[cdri] if ($deltaAny.check(a) && $deltaAny.check(b) && a.name === b.name) { d.modify(diff(b, a)) cdii++ cdri++ } else if ($deltaAny.check(b)) { d.insert(s.$string.check(a) ? a : [a]) cdii++ } else { d.delete(1) cdri++ } } for (; cdii < cd.insert.length; cdii++) { const a = cd.insert[cdii] d.insert(s.$string.check(a) ? a : [a]) } d.delete(cd.remove.length - cdri) } // create the diff for formatting if (hasFormatting) { const formattingDiff = create() // update opsIs with content diff. then we can figure out the formatting diff. const isUpdated = create() // copy opsIs to fresh delta opsIs.forEach(op => { isUpdated.childCnt += op.length list.pushEnd(isUpdated.children, op.clone()) }) isUpdated.apply(d) let shouldI = 0 let shouldOffset = 0 let isOp = isUpdated.children.start let isOffset = 0 while (shouldI < opsShould.length && isOp != null) { const shouldOp = opsShould[shouldI] if (!$deleteOp.check(shouldOp) && !$deleteOp.check(isOp)) { const isFormat = isOp.format const minForward = math.min(shouldOp.length - shouldOffset, isOp.length - isOffset) shouldOffset += minForward isOffset += minForward if (fun.equalityDeep(shouldOp.format, isFormat)) { formattingDiff.retain(minForward) } else { /** * @type {FormattingAttributes} */ const fupdate = {} shouldOp.format != null && object.forEach(shouldOp.format, (v, k) => { if (!fun.equalityDeep(v, isFormat?.[k] || null)) { fupdate[k] = v } }) isFormat && object.forEach(isFormat, (_, k) => { if (shouldOp?.format?.[k] === undefined) { fupdate[k] = null } }) formattingDiff.retain(minForward, fupdate) } // update offset and iterators if (shouldOffset >= shouldOp.length) { shouldI++ shouldOffset = 0 } if (isOffset >= isOp.length) { isOp = isOp.next isOffset = 0 } } } d.apply(formattingDiff) } return d } const subd = diffAndApply(ops1.slice(change.index, change.index + change.remove.length), ops2.slice(change.index + currIndexOffset2, change.index + currIndexOffset2 + change.insert.length)) d.append(subd) lastIndex1 = change.index + change.remove.length currIndexOffset2 += change.insert.length - change.remove.length } for (const attr2 of d2.attrs) { const attr1 = d1.attrs[attr2.key] if (attr1 == null || (attr1.fingerprint !== attr2.fingerprint)) { /* c8 ignore else */ if ($insertOp.check(attr2)) { d.set(attr2.key, attr2.value) } else { /* c8 ignore next 2 */ error.unexpectedCase() } } } for (const attr1 of d1.attrs) { if (d2.attrs[attr1.key] == null) { d.unset(attr1.key) } } } return /** @type {any} */ (d.done(false)) } dmonad-lib0-c7e7806/delta/delta.test.js000066400000000000000000000607661512504553500176670ustar00rootroot00000000000000import * as t from 'lib0/testing' import * as s from 'lib0/schema' import * as delta from './delta.js' import * as error from '../error.js' import * as prng from '../prng.js' /** * Delta is a versatyle format enabling you to efficiently describe changes. It is part of lib0, so * that non-yjs applications can use it without consuming the full Yjs package. It is well suited * for efficiently describing state & changesets. * * Assume we start with the text "hello world". Now we want to delete " world" and add an * exclamation mark. The final content should be "hello!" ("hello world" => "hello!") * * In most editors, you would describe the necessary changes as replace operations using indexes. * However, this might become ambiguous when many changes are involved. * * - delete range 5-11 * - insert "!" at position 11 * * Using the delta format, you can describe the changes similar to what you would do in an text editor. * The "|" describes the current cursor position. * * - d.retain(5) - "|hello world" => "hello| world" - jump over the next five characters * - d.delete(6) - "hello| world" => "hello|" - delete the next 6 characres * - d.insert('!') - "hello!|" - insert "!" at the current position * => compact form: d.retain(5).delete(6).insert('!') * * You can also apply the changes in two distinct steps and then rebase the op so that you can apply * them in two distinct steps. * - delete " world": d1 = delta.create().retain(5).delete(6) * - insert "!": d2 = delta.create().retain(11).insert('!') * - rebase d2 on-top of d1: d2.rebase(d1) == delta.create().retain(5).insert('!') * - merge into a single change: d1.apply(d2) == delta.create().retain(5).delete(6).insert(!) * * @param {t.TestCase} _tc */ export const testDeltaBasicApi = _tc => { // the state of our text document const state = delta.create().insert('hello world') // describe changes: delete " world" & insert "!" const change = delta.create().retain(5).delete(6).insert('!') // apply changes to state state.apply(change) // compare state to expected state t.assert(state.equals(delta.create().insert('hello!'))) } /** * Deltas can describe changes on attributes and children. Textual insertions are children. But we * may also insert json-objects and other deltas as children. * Key-value pairs can be represented as attributes. This "convoluted" changeset enables us to * describe many changes in the same breath: * * delta.create().set('a', 42).retain(5).delete(6).insert('!').unset('b') * * @param {t.TestCase} _tc */ export const testDeltaValues = _tc => { const change = delta.create().set('a', 42).unset('b').retain(5).delete(6).insert('!').insert([{ my: 'custom object' }]) // iterate through attribute changes for (const attrChange of change.attrs) { if (delta.$insertOp.check(attrChange)) { console.log(`set ${attrChange.key} to ${attrChange.value}`) } else if (delta.$deleteOp.check(attrChange)) { console.log(`delete ${attrChange.key}`) } } // iterate through child changes for (const childChange of change.children) { if (delta.$retainOp.check(childChange)) { console.log(`retain ${childChange.retain} child items`) } else if (delta.$deleteOp.check(childChange)) { console.log(`delete ${childChange.delete} child items`) } else if (delta.$insertOp.check(childChange)) { console.log('insert child items:', childChange.insert) } else if (delta.$textOp.check(childChange)) { console.log('insert textual content', childChange.insert) } } } export const testDeltaBasicCases = () => { const $ds = delta.$delta({ name: s.$string, attrs: { k: s.$number, d: delta.$delta({ name: 'sub', text: true }) }, children: s.$number, text: true }) const ds = delta.create('root', $ds) ds.insert('dtrn') ds.update('d', delta.create('sub', null, 'hi')) ds.apply(delta.create('root', { k: 42 }, [42])) ds.apply(delta.create('root', { k: 42 })) // @ts-expect-error t.fails(() => ds.apply(delta.create('root', { k: 'hi' }, 'content'))) const d1 = delta.create().insert('hi') d1.insert([42]).insert('hi').insert([{ there: 42 }]).insert(['']).insert(['dtrn']).insert('stri').insert('dtruniae') d1.set('hi', 'there').set('test', 42).set(42, 43) const _tdelta = delta.create().insert('dtrn').insert([42]).insert(['', { q: 42 }]).set('kv', false).set('x', 42) // eslint-disable-line delta.$delta({ name: s.$any, attrs: s.$object({ kv: s.$boolean, x: s.$number }), children: s.$union(s.$string, s.$number, s.$object({ q: s.$number })), text: true }).expect(_tdelta) console.log(_tdelta) // @ts-expect-error delta.create().insert('hi').apply(delta.create().insert('there').insert([42])) // @ts-expect-error delta.create().set('x', 42).apply(delta.create().set('x', '42')) // @ts-expect-error delta.create().set('x', 42).apply(delta.create().set('y', '42')) delta.create().set('x', 42).apply(delta.create().unset('x')) const t2 = delta.create().insert('hi').insert(['there']).set('k', '42').set('k', 42) t2.apply(delta.create().insert('there').insert(['??']).set('k', 42)) const m = delta.create().set('x', 42).set('y', 'str').insert('hi').insert([42]) m.apply(delta.create().unset('y').insert('hi')) m.set('k', m).update('k', m) } export const testDeltaArrayBasics = () => { t.group('apply edge cases', () => { const d = delta.create().insert('abc') d.apply(delta.create().retain(1).delete(1)) t.compare(d, delta.create().insert('ac')) }) } /** * It should be possible to assign delta "subsets" to "supersets", but not the other way around */ export const testAssignability = () => { t.group('map - prop is a subset of the other', () => { /** * @type {delta.Delta} */ let subset = delta.create(delta.$delta({ attrs: s.$object({ a: s.$number }) })).done() /** * @type {delta.Delta} */ let superset = delta.create(delta.$delta({ attrs: s.$object({ a: s.$union(s.$number, s.$string) }) })).done() superset = subset // @ts-expect-error subset = superset }) t.group('map - map is a subset of the other', () => { /** * @type {delta.Delta} */ const d = delta.create(delta.$delta({ attrs: s.$object({ a: s.$number }) })).done() /** * @type {delta.Delta} */ let m = delta.create(delta.$delta({ attrs: s.$object({ a: s.$number, b: s.$string }) })).done() m = d return m }) t.group('children - are different', () => { /** * @type {delta.Delta} */ let a = delta.create(delta.$delta({ children: s.$number })).done() /** * @type {delta.Delta} */ let b = delta.create(delta.$delta({ children: s.$string })).done() // @ts-expect-error a = b // @ts-expect-error b = a }) t.group('children - is a subset of the other', () => { /** * @type {delta.Delta} */ let a = delta.create(delta.$delta({ children: s.$number })).done() /** * @type {delta.Delta} */ let b = delta.create(delta.$delta({ children: s.$union(s.$string, s.$number) })).done() b = a // @ts-expect-error a = b }) t.group('children - is a subset of the other - with modify', () => { const $child = delta.$delta({ name: s.$literal('string'), attrs: s.$object({ a: s.$number }) }) /** * @type {delta.Delta} */ let a = delta.create(delta.$delta({ children: s.$number })).done() /** * @type {delta.Delta,any,any>} */ let b = delta.create(delta.$delta({ children: s.$union(s.$string, s.$number, $child) })).done() b = a // @ts-expect-error a = b }) t.group('children - is a subset of the other - with different modify', () => { const $child = delta.$delta({ name: s.$literal('string'), attrs: s.$object({ a: s.$string }), text: false }) const $child2 = delta.$delta({ name: s.$literal('number'), attrs: s.$object({ a: s.$number }), text: false }) /** * @type {delta.Delta,never,any>} */ let a = delta.create(delta.$delta({ children: $child })).done() /** * @type {delta.Delta|s.Unwrap<$child2>,never,any>} */ let b = delta.create(delta.$delta({ children: s.$union($child, $child2) })).done() /** * @type {delta.Delta,never,any>} */ let c = delta.create(delta.$delta({ children: $child2 })).done() // d is a superset of a and b let d = delta.create(delta.$delta({ children: delta.$delta({ attrs: s.$object({ a: s.$union(s.$string, s.$number) }) }), text: false })).done() b = a // @ts-expect-error a = b // @ts-expect-error different children c = a d = a d = b return [a, b, c, d] }) t.group('text+array builder - text and array builder support', () => { const $d = delta.$delta({ name: s.$literal('string'), children: s.$number, text: true }) /** * @type {delta.Delta<'string', {}, number, string>} */ let d = delta.create($d) const b1 = delta.create('string', null, 'hi') $d.expect(b1) const b2 = delta.create('string', null, ['there']) // @ts-expect-error d = b2 t.fails(() => { // @ts-expect-error $d.expect(b2) }) const b3 = delta.create('string', null, 'hi').insert([42]) d = b3 $d.expect(b3) const b4 = delta.create('string', null, [42]) d = b4 $d.expect(b4) return [d] }) t.group('Delta children sub and superset', () => { /** * @type {delta.DeltaAny} */ let deltaAny = delta.create().done() let deltaNone = delta.create().done() let deltaNoneWithString = delta.create(delta.$delta({ text: true })).done() let deltaNoneWithNumberContent = delta.create(delta.$delta({ children: s.$number })).done() // @ts-expect-error deltaNone = deltaAny deltaNone = delta.create() deltaAny = delta.create().set('x', 42) // @ts-expect-error deltaNone = deltaNoneWithString // i can assign non-string content to with-string content deltaNoneWithString = deltaNone deltaNoneWithString = delta.create().insert('hi') // @ts-expect-error no numbers allowed deltaNoneWithString = delta.create().insert([42]).done() // @ts-expect-error no children allowed deltaNone = deltaNoneWithNumberContent // i can assign non-children content to with-children content deltaNoneWithNumberContent = deltaNone // @ts-expect-error no strings deltaNoneWithNumberContent = delta.create().insert('hi') deltaNoneWithNumberContent = delta.create().insert([42]).done() // @ts-expect-error because it contains object deltaNoneWithNumberContent = delta.create().insert([42]).insert([{}]).done() }) } export const testText = () => { // only allow certain embeds const $q = delta.$text(s.$object({ m: s.$number })) const q = delta.text($q) q.insert('hi') q.insert([{ m: 42 }]) // @ts-expect-error q.insert([{ q: 42 }]) delta.text() // @ts-expect-error .insert([{ m: 42 }]) } /** * @param {t.TestCase} _tc */ export const testDelta = _tc => { const d = delta.create().insert('hello').insert(' ').useAttributes({ bold: true }).insert('world').useAttribution({ insert: ['tester'] }).insert('!') t.compare(d.toJSON(), { type: 'delta', children: [{ type: 'insert', insert: 'hello ' }, { type: 'insert', insert: 'world', format: { bold: true } }, { type: 'insert', insert: '!', format: { bold: true }, attribution: { insert: ['tester'] } }] }) } /** * @param {t.TestCase} _tc */ export const testDeltaMerging = _tc => { const $d = delta.$delta({ name: s.$string, children: s.$union(s.$number, s.$object({})), text: true }) const d = delta.create($d) .insert('hello') .insert('world') .insert(' ', { italic: true }) .insert([{}]) .insert([1]) .insert([2]) t.compare(d.toJSON(), { type: 'delta', children: [{ type: 'insert', insert: 'helloworld' }, { type: 'insert', insert: ' ', format: { italic: true } }, { type: 'insert', insert: [{}, 1, 2] }] }) } /** * @param {t.TestCase} _tc */ export const testUseAttributes = _tc => { const d = delta.create() .insert('a') .updateUsedAttributes('bold', true) .insert('b') .insert('c', { bold: 4 }) .updateUsedAttributes('bold', null) .insert('d') .useAttributes({ italic: true }) .insert('e') .useAttributes(null) .insert('f') const d2 = delta.create() .insert('a') .insert('b', { bold: true }) .insert('c', { bold: 4 }) .insert('d') .insert('e', { italic: true }) .insert('f') t.compare(d, d2) } /** * @param {t.TestCase} _tc */ export const testUseAttribution = _tc => { const d = delta.create() .insert('a') .updateUsedAttribution('insert', ['me']) .insert('b') .insert('c', null, { insert: ['you'] }) .updateUsedAttribution('insert', null) .insert('d') .useAttribution({ insert: ['me'] }) .insert('e') .useAttribution(null) .insert('f') const d2 = delta.create() .insert('a') .insert('b', null, { insert: ['me'] }) .insert('c', null, { insert: ['you'] }) .insert('d') .insert('e', null, { insert: ['me'] }) .insert('f') t.compare(d, d2) } export const testMapTyping = () => { const $q = delta.$map(s.$object({ x: s.$number })) const mmm = delta.map().set('x', 42) $q.expect(mmm) const mmm2 = delta.map().set('x', 'xx') t.fails(() => { // @ts-expect-error $q.expect(mmm2) }) const q = delta.map($q) q.set('x', 42) // @ts-expect-error q.set('y', 42) } /** * @param {t.TestCase} _tc */ export const testMapDeltaBasics = _tc => { const $d = s.$object({ num: s.$union(s.$number, s.$string), str: s.$string }) const dmap = delta.create(delta.$delta({ attrs: $d })) t.fails(() => { // @ts-expect-error dmap.apply(delta.create().set('str', 42)) }) dmap.set('str', 'hi') for (const c of dmap.attrs) { if (c.key === 'str') { // @ts-expect-error because value can't be a string t.assert(c.value !== 42) } else if (c.key === 'num') { t.assert(c.value !== 42) } else { // no other option, this will always throw if called s.assert(c, s.$never) } } const x = dmap.attrs.str t.assert(delta.$insertOpWith(s.$string).optional.check(x) && delta.$insertOpWith(s.$string).optional.validate(x)) t.assert(!delta.$insertOpWith(s.$number).optional.check(x)) t.assert(dmap.attrs.str !== null) } /** * @param {t.TestCase} _tc */ export const testMapDeltaModify = _tc => { // Yjs users will create nested Yjs types like this (instead of $mapDelta they would use $yarray): const $d = delta.$delta({ attrs: s.$object({ num: s.$union(s.$number, s.$string), str: s.$string, map: delta.$delta({ attrs: s.$object({ x: s.$number }) }) }) }) const $dsmaller = delta.$delta({ attrs: s.$object({ str: s.$string }) }) t.group('test extensibility', () => { // observeDeep needs to transform this to a modifyOp, while preserving tying const d = delta.create().set('num', 42) t.assert($d.check(d)) t.assert($dsmaller.check(d)) t.assert($d.check(delta.create().set('x', 99))) // this should work, since this is a unknown property t.assert(!$d.check(delta.create().set('str', 99))) // this shoul fail, since str is supposed to be a string }) t.group('test delta insert', () => { const d = delta.create($d) const testDeleteThis = delta.create(delta.$delta({ attrs: s.$object({ x: s.$number }) })).set('x', 42) d.set('map', testDeleteThis) for (const change of d.attrs) { if (change.key === 'map' && change.type === 'insert') { delta.$delta({ attrs: s.$object({ x: s.$number }) }).validate(change.value) } else { error.unexpectedCase() } } }) t.group('test modify', () => { const d = delta.create($d) d.update('map', delta.create().unset('x')) for (const change of d.attrs) { if (change.key === 'map' && change.type === 'modify') { delta.$delta({ attrs: s.$object({ x: s.$number }) }).validate(change.value) } else { error.unexpectedCase() } } }) } /** * @param {t.TestCase} _tc */ export const testMapDelta = _tc => { const x = delta.$delta({ attrs: s.$object({ key: s.$string, v: s.$number, over: s.$string }) }) const d = delta.create(x) .unset('over') .set('key', 'value') .useAttribution({ delete: ['me'] }) .unset('v') .useAttribution(null) .set('over', 'andout') t.compare(d.toJSON(), { type: 'delta', attrs: { key: { type: 'insert', value: 'value' }, v: { type: 'delete', attribution: { delete: ['me'] } }, over: { type: 'insert', value: 'andout' } } }) t.compare(d.origin, null) for (const change of d.attrs) { if (change.key === 'v') { t.assert(d.attrs[change.key]?.prevValue !== 94) // should know that value is number if (delta.$insertOp.check(change)) { // @ts-expect-error t.assert(change.value !== '') t.assert(change.value === undefined) } else if (delta.$deleteOp.check(change)) { t.assert(change.value === undefined) } else { t.fail('should be an insert op') } } else if (change.key === 'key') { if (change.type === 'insert') { t.assert(change.prevValue !== 'test') // @ts-expect-error should know that prevValue is not a string t.assert(change.prevValue !== 42) } t.assert(d.attrs[change.key]?.value === 'value') // show know that value is a string t.assert(change.value === 'value') } else if (change.key === 'over') { t.assert(change.value === 'andout') } else { throw new Error() } } } /** * @param {t.TestCase} tc */ export const testRepeatRebaseMergeDeltas = tc => { const $d = delta.$delta({ attrs: s.$object({ a: s.$number, b: delta.$delta({ attrs: s.$object({ x: s.$string }) }) }) }) const gen = tc.prng const createDelta = () => { const d = delta.create($d) prng.oneOf(gen, [ // create insert () => { if (prng.bool(gen)) { // write 'a' d.set('a', prng.int32(gen, 0, 365)) } else if (prng.bool(gen)) { // 25% chance to create an insertion on 'b' d.set('b', delta.create().set('x', prng.utf16String(gen))) } else { // 25% chance to create a modify op on 'b' d.update('b', delta.create().set('x', prng.utf16String(gen))) } }, // create delete () => { if (prng.bool(gen)) { d.unset('a') } else { d.unset('b') } } ])() return d } const da = createDelta() da.origin = 1 const db = createDelta() db.origin = 2 const dc = createDelta() dc.origin = 3 const order1 = [da, db, dc].map(delta.clone) const order2 = [dc, db, da].map(delta.clone) /** * @param {Array>} ops */ const rebase = (ops) => { for (let i = 1; i < ops.length; i++) { for (let j = 0; j < i; j++) { /** @type {delta.DeltaBuilder} */ (ops[i]).rebase(ops[j], ops[i].origin < ops[j].origin) } } } rebase(order1) rebase(order2) /** * @param {Array>} ops */ const apply = ops => { const d = delta.create($d) for (let i = 0; i < ops.length; i++) { d.apply(ops[i]) } return d } const dmerged1 = apply(order1) const dmerged2 = apply(order2) console.log('1', JSON.stringify(dmerged1)) console.log('2', JSON.stringify(dmerged2)) t.compare(dmerged1, dmerged2) } /** * @param {t.TestCase} _tc */ export const testNodeDelta = _tc => { const $d = delta.$delta({ name: s.$string, attrs: s.$object({ a: s.$number }), children: s.$string }) const d = delta.create('test', $d) d.insert(['hi']) // @ts-expect-error d.insert([42]) // @ts-expect-error d.insert('hi') d.set('a', 1) d.unset('a') /** * @type {Array| string | number>} */ const arr = [] d.children.forEach( (op) => { if (delta.$insertOp.check(op)) { arr.push(op.insert) } } ) t.compare(arr, [['hi', 42]]) /** * @type {any} */ const attrs = [] for (const attr of d.attrs) { t.assert(attr.key === 'a') attrs.push(attr.key, attr.value) } t.compare(attrs, ['a', undefined]) } export const testRecursiveNode = () => { const $d = delta.$delta({ name: 'hi', attrs: { q: s.$number }, text: true, recursive: true }) const rd = delta.create($d) // should allow inserting deltas const recC = delta.create('hi', { q: 342 }) rd.insert([recC]) const d = delta.create('hi', { q: 42 }) $d.expect(d) // should detect invalid attrs // @ts-expect-error t.assert(!$d.validate(delta.create('hi', { q: 'fortytwo' }))) // should detect invalid child (of type string) // @ts-expect-error t.assert(!$d.validate(delta.create('hi', { q: 42 }, ['dtrn']))) // should allow adding valid children (of type $d) const p = delta.create('hi', { q: 42 }, [d]) t.assert($d.validate(p)) // should allow adding text t.assert($d.validate(delta.create('hi', { q: 42 }, 'hi'))) } export const testSimplifiedDeltaSchemaDefinition = () => { const $d = delta.$delta({ name: 'div', attrs: { a: s.$number, b: s.$string.optional, unknown: s.$string }, children: [s.$number], text: true }) t.assert($d.check(delta.create('div', { a: 42 }).insert([42]).insert('str'))) t.assert(!$d.check(delta.create('dove', { a: 42 }).insert([42]).insert('str'))) } export const testDiffing = () => { const $d = delta.$delta({ name: 'div', attrs: { key: s.$number, b: s.$string, unknown: s.$string }, children: [s.$number], text: true }) const d1 = delta.create($d).insert([1]).insert('hello').insert([2]).set('key', 42).set('unknown', 'unknown').done() const d2 = delta.create($d).insert('hello').set('key', 1).done() const d = delta.diff(d1, d2) t.compare(d, delta.create().delete(1).retain(1).delete(1).set('key', 1).unset('unknown')) } export const testDiffingCommonPreSuffix = () => { const $d = delta.$delta({ name: 'div', children: [s.$number], text: true }) const d1 = delta.create($d).insert([1, 2]).insert('aa').insert([3, 4]) const d2 = delta.create($d).insert([1, 2]).insert('a').insert([3, 4]) const d = delta.diff(d1, d2) t.compare(d, delta.create().retain(3).delete(1)) } export const testSlice = () => { const d1 = delta.create().insert('abcde').slice(1, 3) t.assert(d1.equals(delta.create().insert('bc'))) } /** * @param {t.TestCase} tc */ export const testRepeatRandomListDiff = tc => { const $d = delta.$delta({ children: s.$number }) const d1 = delta.random(tc.prng, $d) const d2 = delta.random(tc.prng, $d) $d.expect(d1) $d.expect(d2) const d = delta.diff(d1, d2) d1.apply(d) t.compare(d1, d2) } /** * @param {t.TestCase} tc */ export const testRepeatRandomMapDiff = tc => { const $d = delta.$delta({ name: 'list', attrs: { a: s.$string, b: s.$number } }) const d1 = delta.random(tc.prng, $d) const d2 = delta.random(tc.prng, $d) $d.expect(d1) $d.expect(d2) const d = delta.diff(d1, d2) d1.apply(d) t.compare(d1, d2) } /** * @param {t.TestCase} _tc */ export const testDeltaAppend = _tc => { const $d = delta.$delta({ children: s.$number, text: true }) const other = delta.create().insert('b').insert([1, 2]) const _d = delta.create().insert('a') const d = _d.append(other) $d.expect(d) } export const testDeltaDiffWithFormatting = () => { const d1 = delta.create().insert('hello world!') const d2 = delta.create().insert('hello ').insert('world', { bold: true }).insert('!') const diff = delta.diff(d1, d2) t.compare(diff, delta.create().retain(6).retain(5, { bold: true })) } export const testDeltaDiffWithFormatting2 = () => { const d1 = delta.create().insert('hello!') const d2 = delta.create().insert('hello ').insert('world', { bold: true }).insert('!') const diff = delta.diff(d1, d2) t.compare(diff, delta.create().retain(5).insert(' ').insert('world', { bold: true })) } export const testDeltaDiffIssue1 = () => { const stateA = delta.create().insert([delta.create('paragraph').set('ychange', null).insert('ABCDEFGHIJKLMNOPQRSTUVWXYZ')]) const stateB = delta.create().insert([delta.create('paragraph').set('ychange', null).insert('ABCDE123FGHIJKLMNOPQRSTUVWXYZ2sawfa')]) const expectedDiff = delta.create().modify(delta.create().retain(5).insert('123').retain(21).insert('2sawfa')) const diffResult = delta.diff(stateA, stateB) const synced = delta.clone(stateA).apply(diffResult) t.assert(synced.equals(stateB)) t.assert(expectedDiff.equals(diffResult)) } dmonad-lib0-c7e7806/delta/readme.md000066400000000000000000000073701512504553500170310ustar00rootroot00000000000000# Deltas - Enable you to efficiently represent changes on all kinds of data structures. - Support schemas - Support OT-style conflict resolution `delta2.apply(delta1.rebase(delta2, true)) === delta1.apply(delta2.rebase(delta1, false))` - nice typings ## Delta for Map-like structures ```javascript // define schema const $d = delta.$delta(s.$any, { attr1: s.$string, attr2: s.$number }) const d = delta.create($d) // create an update const update = delta.create().set('attr1', 'val1').set('attr2', 42) d.apply(update) // In case of an invalid update const update2 = delta.create().set('attr1', 42) // it is possible to check an update beforehand $d.check(update2) // => false // and you also get type errors d.apply(update2) // type error: expected 'attr1' to be of type string ``` ## Delta for Text-like structures Text-like deltas work similarly to [Quill Deltas]{https://quilljs.com/docs/delta} ```javascript // define schema const $d = delta.$delta(s.$any, null, s.$string) const d = delta.create($d).insert('hello world') // create an update const update = delta.create().retain(11).insert('!') d.apply(update) // In case of an invalid update const update2 = delta.create().insert([{ some: 'object' }]) // it is possible to check an update beforehando $d.check(update2) // => false // and you also get type errors d.apply(update2) // type error: unexpected attribute 'attr1' ``` ## Delta for Array-like structures ```javascript // define schema const $d = delta.$delta(s.$any, null, s.$array(s.object({ some: s.$string }, s.$string))) const d = delta.create($d).insert(['hello world']) // create an update const update = delta.create().retain(1).insert({ some: 'object' }) d.apply(update) // In case of an invalid update const update2 = delta.create().insert([{ unknown: 'prop' }]) // it is possible to check an update beforehando $d.check(update2) // => false // and you also get type errors d.apply(update2) // type error: { unknown: 'prop' } is not assignable to { some: string } ``` ## Delta for Node-like structures (similar to XML,Trees with named nodes) ```javascript // define schema for a 'p'|'h1' node that may contain text or other instances of itself const $d = delta.$delta(s.$literal('div', 'p', 'h1'), { style: s.$string }, s.$string, true)) const d = delta.create('div', $d) // create an update - insert paragraph into the
const update = delta.create().insert([delta.create('p', { style: 'bold: true' }, 'hello world')]) d.apply(update) // modify the paragraph by deleting the text 'world' and appending '!' d.apply(delta.create().modify( delta.create().retain(6).delete(5).insert('!') )) ``` # Transformers We often have two different data structures that we want to sync. There might be slight differences between those data structures. I.e. we might have a Yjs data structure containing the following content: ```javascript /** * { * headline: {{ headline }}, * content: {{ content }} * } */ const $data = s.$delta(null, { headline: s.$string, content: s.$string }) ``` A typical scenario is that we want to sync that to the dom and back. "two-way bindings" - When the Yjs struucture updates, we want to sync the changes to the dom. - When the dom is updated (because the dom is a `contenteditable` editor), we want to sync back the changes to the yjs structure. Now, the dom might look like this: ```javascript /** *
*

{{headline}}

* dturianed *

{{content}}

*
*/ ``` We can achieve automattic back-and-forth transformations with delta transformers: ```javascript const Λdata = Λ.transform($data, $d => Λ.delta('div', {}, [ Λ.delta('h1', { style: 'bold:true' }, [Λ.query('headline')($d)], []), Λ.delta('p', null, [Λ.query('content')($d)]) ]) ) ``` dmonad-lib0-c7e7806/delta/t3.test.js000066400000000000000000000157751512504553500171240ustar00rootroot00000000000000/* eslint-disable */ // @ts-nocheck import * as t from '../testing.js' import * as delta from './delta.js' import * as s from '../schema.js' import * as array from '../array.js' /** * @template {delta.Delta?} DeltaA * @template {delta.Delta?} DeltaB * @typedef {{ a: DeltaA?, b: DeltaB? }} TransformResult */ /** * @template {delta.DeltaBuilder?} DeltaA * @template {delta.DeltaBuilder?} DeltaB * @param {DeltaA} a * @param {DeltaB} b * @return {TransformResult} */ export const transformResult = (a, b) => ({ a, b }) export const transformResultEmpty = transformResult(null, null) let x = transformResult(delta.create('x'), null) x = transformResult(null, null) /** * @template {delta.DeltaAny} DeltaA * @template {delta.DeltaAny} DeltaB * @typedef {(t:{a:DeltaA?,b:DeltaB?})=>({a:DeltaA?,b:DeltaB?})} DeltaTransformer */ /** * @template {delta.Delta} A * @template {(A extends delta.Delta ? delta.Delta<`x-${NodeName}`,Attrs,Children,Text> : never)} B * @param {TransformResult} t * @return {TransformResult} */ const rename = t => { /** * @type {any} */ const tout = /** @type {any} */ (transformResult(null, null)) if (t.a) { const c = /** @type {delta.Delta} */ (t.a.clone()) c.name = 'x-' + c.name // @ts-ignore tout.b = c } if (t.b) { const c = /** @type {delta.Delta} */ (t.b.clone()) c.name = c.name.slice(2) // @ts-ignore tout.a = c } return tout } /** * @param {Set} allowed */ const filter = (allowed) => { /** * contains inserted items that didn't make it into t.b */ const diff = delta.create() /** * @template {delta.Delta?} A * @template {(A extends delta.Delta ? delta.Delta<`x-${NodeName}`,Attrs,Children,Text> : never)} B * @param {{ a: A?, b: B? }} t * @return {{ a: A?, b: B? }} */ return t => { /** * @type {any} */ const tout = /** @type {any} */ (transformResult(null, null)) if (t.a) { const c = delta.create() let index = 0 /** * Split delta into two parts: hidden and visible. hidden contains all "hidden" changes (filtered inserts). * visible everything else. * * return visible.rebaseAgainstInverse(diff) * apply `diff.apply(merge)` * */ for (const child of t.a.children) { if (delta.$insertOp.check(child)) { for (let i = 0; i < child.insert.length; i++) { const ins = child.insert[i] if (delta.$deltaAny.check(ins) && allowed.has(ins.name)) { ins } else { } ins } } if (!delta.$deleteOp.check(child)) { index += child.length } } } if (t.b) { } return tout } } const dd = delta.create('x', { x: 'dtrn' }) const y = rename({ a: delta.create('x', { x: 'dtrn' }), b: null }) /** * @template {delta.DeltaAny} DeltaA * @template {delta.DeltaAny} Delta_ * @template {delta.DeltaAny} DeltaB * @param {(t:TransformResult)=>TransformResult} t1 * @param {(t:TransformResult)=>TransformResult} t2 * @return {(dx:TransformResult)=>TransformResult} */ const pipe = (t1, t2) => (dx) => { return /** @type {any} */ (null) } // next idea: Transform object that changes typings /** * Transforms should.. * - transform from a->b->c->b->a * - extendable mod(Transform):Transform * - i can start with id: mod(Id($d)) */ /** * @template {delta.Delta} DeltaA * @template {delta.Delta} DeltaB * @typedef {{ applyA: (da:DeltaA)=>TransformResult, applyB: (db:DeltaB)=>TransformResult }} Transform */ /** * @template {delta.DeltaBuilder} A * @template {delta.DeltaBuilder} B */ class Transformer { /** * @param {s.Schema} $da */ constructor ($da) { this.$da = $da /** * @type {Array>} */ this._tr = [] } /** * @param {TransformResult} d * @return {TransformResult} */ apply ({ a, b }) { if (a == null && b == null) return transformResult(null, null) /** * @type {Array<{ a: delta.DeltaBuilder?, b: delta.DeltaBuilder? }>} */ const pendingApply = array.unfold(this._tr.length + 2, () => ({ a: null, b: null })) pendingApply[1].a = a pendingApply[pendingApply.length - 2].b = b /** * @param {number} i */ const applyTransformI = i => { const p = pendingApply[i + 1] const t = this._tr[i] const aout = p.a !== null ? t.applyA(p.a) : transformResult(null, null) if (p.b !== null) { if (aout.b !== null) { p.b = p.b.rebase(aout.b, true) } const bout = t.applyB(p.b) aout.a = delta.mergeDeltas(aout.a, bout.a) aout.b = delta.mergeDeltas(aout.b, bout.b) } // write out.a into prev.b, and out.b into next.a pendingApply[i].b = delta.mergeDeltas(pendingApply[i].b, aout.a) pendingApply[i + 2].a = delta.mergeDeltas(pendingApply[i + 2].a, aout.b) return aout } let needsBackwardTransform = a != null let needsForwardTransform = b != null while (needsBackwardTransform || needsForwardTransform) { if (needsForwardTransform) { for (let i = 0; i < this._tr.length; i++) { const r = applyTransformI(i) if (i != null) { needsBackwardTransform = needsBackwardTransform || r.a != null } } needsForwardTransform = false } if (needsBackwardTransform) { for (let i = this._tr.length - 1; i >= 0; i--) { const r = applyTransformI(i) if (i != null) { needsForwardTransform = needsForwardTransform || r.b != null } } needsBackwardTransform = false } } return /** @type {TransformResult} */ (transformResult(pendingApply[0].b, pendingApply[pendingApply.length - 1].a)) } } // // /** // * @template {delta.Delta} D // * @param {s.Schema} $d // * @return {Transformer} // */ // const id = ($d) => /** @type {Transformer} */ (new Transformer($d)) // // const q = id(delta.$delta({ name: 'div' })) // const q2 = id(delta.$delta({ name: 'div', attrs: { a: s.$string } })).pipe(t.delta('h1', { color: t => query('a')(t), name:'mystuff' }, t => [query('b')(t)])) // const q3 = t.delta('h1', { color: t => query('a')(t), name:'mystuff' }, t => [query('b')(t)])(id(delta.$delta({ name: 'div', attrs: { a: s.$string } })))) // // // /** // * @param {Transformer>} t // */ // const dataToH1 = t => t.delta('h1', { color: t => query('a')(t), name:'mystuff' }, t => [query('b')(t)])(t) // const q4 = dataToH1(id(delta.$delta({ name: 'div', attrs: { a: s.$string } }))) // // const dataToH1_2 = t => rename('h1')(renameAttr({ a: 'color' })(static(delta.create('h1', { name: 'mystuff' }, 'some content!'))(t))) dmonad-lib0-c7e7806/deno.json000066400000000000000000000006311512504553500157720ustar00rootroot00000000000000{ "imports": { "isomorphic.js": "./node_modules/isomorphic.js/node.mjs", "lib0/logging": "./logging.node.js", "lib0/performance": "./performance.node.js", "lib0/crypto/aes-gcm": "./crypto/aes-gcm.js", "lib0/crypto/rsa-oaep": "./crypto/rsa-oaep.js", "lib0/crypto/ecdsa": "./crypto/ecdsa.js", "lib0/crypto/jwt": "./crypto/jwt.js", "lib0/webcrypto": "./webcrypto.deno.js" } } dmonad-lib0-c7e7806/deno.lock000066400000000000000000000404571512504553500157630ustar00rootroot00000000000000{ "version": "2", "remote": { "https://deno.land/std@0.177.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462", "https://deno.land/std@0.177.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", "https://deno.land/std@0.177.0/bytes/index_of_needle.ts": "65c939607df609374c4415598fa4dad04a2f14c4d98cd15775216f0aaf597f24", "https://deno.land/std@0.177.0/crypto/_wasm/lib/deno_std_wasm_crypto.generated.mjs": "5dedb7f9aa05f0e18ed017691c58df5f4686e4cbbd70368c6f896e5cca03f2b4", "https://deno.land/std@0.177.0/crypto/_wasm/mod.ts": "e2df88236fc061eac7a89e8cb0b97843f5280b08b2a990e473b7397a3e566003", "https://deno.land/std@0.177.0/crypto/timing_safe_equal.ts": "8d69ab611c67fe51b6127d97fcfb4d8e7d0e1b6b4f3e0cc4ab86744c3691f965", "https://deno.land/std@0.177.0/encoding/base64.ts": "7de04c2f8aeeb41453b09b186480be90f2ff357613b988e99fabb91d2eeceba1", "https://deno.land/std@0.177.0/encoding/base64url.ts": "3f1178f6446834457b16bfde8b559c1cd3481727fe384d3385e4a9995dc2d851", "https://deno.land/std@0.177.0/encoding/hex.ts": "50f8c95b52eae24395d3dfcb5ec1ced37c5fe7610ef6fffdcc8b0fdc38e3b32f", "https://deno.land/std@0.177.0/flags/mod.ts": "d1cdefa18472ef69858a17df5cf7c98445ed27ac10e1460183081303b0ebc270", "https://deno.land/std@0.177.0/node/_core.ts": "9a58c0ef98ee77e9b8fcc405511d1b37a003a705eb6a9b6e95f75434d8009adc", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/asn1.js/base/buffer.js": "c9364c761681134015ec8ba6f33b39c067d6e5dd59860d55face8d5be8522744", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/asn1.js/base/node.js": "8f7f23bfa300990bbd6db7e7395e9688b54a04e3eb2fab5cab9a9a72e26c525f", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/asn1.js/base/reporter.js": "788aec7662991da549e5f7f3edbc3e3d6c6cecabc894b18d1a705b0f204e06c3", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/asn1.js/constants/der.js": "57181db0519bb3864a6cdf4e7eb9bfeb1bf5f80605187fbe80e27083b473e367", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/asn1.js/decoders/der.js": "fdc4de98c9b0b59db169a2b225895741e2ab34b00e14315ac2ff5e389d6db16e", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/asn1.js/decoders/pem.js": "fd7f0072c193c82959fec0374f4fd3adf3f4ac38594fd404d66b3e8724107151", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/asn1.js/encoders/der.js": "137bc4f8fe66b9950c743025e199789e25342f791e2d52353ceb016ad2854b42", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/asn1.js/encoders/pem.js": "e43bc706973c4c27e1e2f96262daba3d38822cb10f5b494f6944c726ee655160", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/asn1.js/mod.js": "1f88293688296be7a6c735bd8ea39425f5b274b94db1d6b7968dddfb54ac9d37", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/bn.js/bn.js": "f3f3c1dae1aa55de9e6472af1d6bec5ccda4b4890ee5c52a90961137fe99564e", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/browserify_aes/aes.js": "698e1ed386b7dff27b2d59fa1c75f506beceec96b78670a15a734e438c08f138", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/browserify_aes/auth_cipher.js": "5c245b5685b066356a7c9529a3a441bf5f57823a6946ce1b0ef2e1af32bb76f4", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/browserify_aes/decrypter.js": "39152b2b3409893b8548feeab7e5997ceb1595f31df0dedaf765708be8f025c0", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/browserify_aes/encrypter.js": "f9cc703d5a7b5255999c1a3600fbf48ff564b65f827744877526803093ceebff", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/browserify_aes/ghash.js": "759d80b760f44cd3a454b4f161fd03a7d6c359901446f0a907a6870cb66d6767", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/browserify_aes/incr32.js": "2bdea27b74b3990ee56807a1a5abe335f118826beabeeb905459c8768094b28f", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/browserify_aes/mod.js": "fe4affebbd210d885b2e5135c668751f9d10bc14aa0cc3905cbfff66f04b4c58", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/browserify_aes/modes/cbc.js": "ff24b4506522a724ba7a03c1403ad8938aba45056f9fd47c7f0b4fcb3a640adf", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/browserify_aes/modes/cfb.js": "643720a1db969b6bcc896c95523630838a8335513d02f340514fd524bb4113cb", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/browserify_aes/modes/cfb1.js": "01c9a46aa3affd84a54ae33652fb0fa0ff7c862be2a459d9cb188cb8e2c4b11e", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/browserify_aes/modes/cfb8.js": "97476cee25103e02a02b196d7fe6f28a9f0f9e47ee344687d7492bc7282a59f8", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/browserify_aes/modes/ctr.js": "1e3835adb753cfe6761e4df8c43d190e31e1ca6a586fd582747c8255c82ed78d", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/browserify_aes/modes/ecb.js": "79677b96d4af50c49f0a4f698e5c7e5a64f1d2926b799e0d2eac2cdd5ec7488c", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/browserify_aes/modes/mod.js": "fe3db429b867a0a8066c64d7b33b840a1f24cad9174156384a763733f68cf518", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/browserify_aes/modes/ofb.js": "3553308f98d078e2006eac39bb6d91818f8bb376b01d962ae98eabf6ee79ad4e", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/browserify_aes/stream_cipher.js": "70f50f37ddec530ae95911ca2f286ebd2ddbd54d914ab0be461ec1dc3c61990f", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/browserify_aes/xor.ts": "7132baacdb39ba82c3bfe325a60e68ca87469c0ed0cdd0508caf6f40bab852b8", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/browserify_rsa.js": "96e0e4fee7c2cf75ef86d958c709bfc239297a080fd17ace5ea5ab699a1b6174", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/cipher_base.js": "9ebc6ccc364cf7b23024821054d2e72a2d8da8d8a2a36cacdc5aa6cc6770ef93", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/evp_bytes_to_key.ts": "7c4c27b6e321b2d7065a6703d90264921e9a805d91d9dfdb21103393228024e2", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/parse_asn1/asn1.js": "7d99b6df508164169a33377346e8840d519fe2defccb362a023c92c5bd503433", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/parse_asn1/certificate.js": "5795348417b3ec7aafa4854ba55f364e0148eadfdd29d1566c90e617237621bb", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/parse_asn1/fix_proc.js": "858dd3e6ce264d75822cadc21bb55114f4e4867a706abde1663548aa2710fc1b", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/parse_asn1/mod.js": "ea164fbd497ce3d710426742d4b72f71da8954c4ebaeb7eadc33316c5b0060f1", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/public_encrypt/mgf.js": "dfac5008a550b3e7e6b851c4fb42e984aa9e7fae64707888f47f2aa0991c004d", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/public_encrypt/mod.js": "0704326ff3ee2bb0764a964995d1aa62b1147b714ad5465e878ba4d57731e3db", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/public_encrypt/private_decrypt.js": "8a1d11edb176d95d1e3bdf1aff5c3248a986bf9734d1a6b07508e29132d2f65c", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/public_encrypt/public_encrypt.js": "f88b0e3c228d84096fdbc03e614e86bef86e56013cb9628b2425e31b3b142b2c", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/public_encrypt/with_public.js": "752da754d253b5743d89c0f2432b6eb6f8815b80efd9ee588683e10a13d34400", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/public_encrypt/xor.js": "087ebef8f6fcb8ca4c7216cc22de728d9a61ec27b9a036b900681ff25d6409af", "https://deno.land/std@0.177.0/node/_crypto/crypto_browserify/randombytes.ts": "23bde8be640e274d7bb88cf10d1da8bba252654252dc6a877fed86a77da5952c", "https://deno.land/std@0.177.0/node/_events.d.ts": "1347437fd6b084d7c9a4e16b9fe7435f00b030970086482edeeb3b179d0775af", "https://deno.land/std@0.177.0/node/_events.mjs": "d4ba4e629abe3db9f1b14659fd5c282b7da8b2b95eaf13238eee4ebb142a2448", "https://deno.land/std@0.177.0/node/_global.d.ts": "2d88342f38b4083b858998e27c706725fb03a74aa14ef8d985dc18438b5188e4", "https://deno.land/std@0.177.0/node/_next_tick.ts": "9a3cf107d59b019a355d3cf32275b4c6157282e4b68ea85b46a799cb1d379305", "https://deno.land/std@0.177.0/node/_process/exiting.ts": "6e336180aaabd1192bf99ffeb0d14b689116a3dec1dfb34a2afbacd6766e98ab", "https://deno.land/std@0.177.0/node/_process/process.ts": "c96bb1f6253824c372f4866ee006dcefda02b7050d46759736e403f862d91051", "https://deno.land/std@0.177.0/node/_process/stdio.mjs": "cf17727eac8da3a665851df700b5aca6a12bacc3ebbf33e63e4b919f80ba44a6", "https://deno.land/std@0.177.0/node/_stream.d.ts": "112e1a0677cd6db932c3ce0e6e5bbdc7a2ac1874572f449044ecc82afcf5ee2e", "https://deno.land/std@0.177.0/node/_stream.mjs": "d6e2c86c1158ac65b4c2ca4fa019d7e84374ff12e21e2175345fe68c0823efe3", "https://deno.land/std@0.177.0/node/_utils.ts": "7fd55872a0cf9275e3c080a60e2fa6d45b8de9e956ebcde9053e72a344185884", "https://deno.land/std@0.177.0/node/buffer.ts": "85617be2063eccaf177dbb84c7580d1e32023724ed14bd9df4e453b152a26167", "https://deno.land/std@0.177.0/node/crypto.ts": "2c94fa0f76e90190fbc34df891dc5c284bddb86c932fae8ac11747de3f75293c", "https://deno.land/std@0.177.0/node/events.ts": "d2de352d509de11a375e2cb397d6b98f5fed4e562fc1d41be33214903a38e6b0", "https://deno.land/std@0.177.0/node/internal/buffer.d.ts": "bdfa991cd88cb02fd08bf8235d2618550e3e511c970b2a8f2e1a6885a2793cac", "https://deno.land/std@0.177.0/node/internal/buffer.mjs": "e92303a3cc6d9aaabcd270a937ad9319825d9ba08cb332650944df4562029b27", "https://deno.land/std@0.177.0/node/internal/crypto/_keys.ts": "8f3c3b5a141aa0331a53c205e9338655f1b3b307a08085fd6ff6dda6f7c4190b", "https://deno.land/std@0.177.0/node/internal/crypto/_randomBytes.ts": "36dd164747f73b830ba86562abb160a8ac5bea34aaeb816a67f3005a00d41177", "https://deno.land/std@0.177.0/node/internal/crypto/_randomFill.ts": "297186f290eba87a1ad7b8aa42a960ff4278a8b6b0c963fa81918c326d5c0b58", "https://deno.land/std@0.177.0/node/internal/crypto/_randomInt.ts": "6cf19da9684b67520e67a2d99f2581a3f841140842c7ce2e014d166457550fe1", "https://deno.land/std@0.177.0/node/internal/crypto/certificate.ts": "b4a6695f82e70a42e85247c74a7691ed4b3a904646451af0287e49efe1a28814", "https://deno.land/std@0.177.0/node/internal/crypto/cipher.ts": "2bae9b4d94c465e4d1c70e5a9e8fd67ce20bcc66fecd2eec6be00d35144ca4eb", "https://deno.land/std@0.177.0/node/internal/crypto/constants.ts": "544d605703053218499b08214f2e25cf4310651d535b7ab995891c4b7a217693", "https://deno.land/std@0.177.0/node/internal/crypto/diffiehellman.ts": "9cfb219c5b2936db773f559b6affe6d25b0e40531010389f05df3f05ce7eebf5", "https://deno.land/std@0.177.0/node/internal/crypto/hash.ts": "d01f5d3ad5477655b432036d2d553c7a0c31a901ac0e1e9e0d8b3975daae7624", "https://deno.land/std@0.177.0/node/internal/crypto/hkdf.ts": "5bd801234e56468fbd47466f46e88bdadc66432d625e3616abe38878d410bb66", "https://deno.land/std@0.177.0/node/internal/crypto/keygen.ts": "530cc1a00acf71a43719bb876a2dc563b6196095d080eba77c92c9f39658a5b9", "https://deno.land/std@0.177.0/node/internal/crypto/keys.ts": "c4dfa5aa3420cf700178b87203593a0989c8a93934bfef2b29adb3399d687958", "https://deno.land/std@0.177.0/node/internal/crypto/pbkdf2.ts": "0a0a3e0d3d45db0638fe75a4199c7ed7ca2164405750a520e786e4adebdb45a4", "https://deno.land/std@0.177.0/node/internal/crypto/random.ts": "85f3147e14cb45c18e016da45d319a5c663309411232a956fdc09c2317acdd9f", "https://deno.land/std@0.177.0/node/internal/crypto/scrypt.ts": "b55a0fcd12b295af4127d05b1c0bc3098b74fc0e3c62321c2a43c20f9ed18209", "https://deno.land/std@0.177.0/node/internal/crypto/sig.ts": "25819a89d49c1ebfe3baa1f9464501ec599a36cf53e9b600ec0399e568b9dccc", "https://deno.land/std@0.177.0/node/internal/crypto/types.ts": "52feb182bcbd59206f3e2f4a3cb8a5775d4452c2a8045c3e613e2178d32c2a86", "https://deno.land/std@0.177.0/node/internal/crypto/util.ts": "db282c0413aeee28bc0665fcfc1c08a65fc96dc12ed4d03282f2da4907fcf0ce", "https://deno.land/std@0.177.0/node/internal/crypto/x509.ts": "0e8a541c4f58ecb83862c373d3f7d2371aa8f5108f55bc837b190c4ab3408764", "https://deno.land/std@0.177.0/node/internal/error_codes.ts": "8495e33f448a484518d76fa3d41d34fc20fe03c14b30130ad8e936b0035d4b8b", "https://deno.land/std@0.177.0/node/internal/errors.ts": "1c699b8a3cb93174f697a348c004b1c6d576b66688eac8a48ebb78e65c720aae", "https://deno.land/std@0.177.0/node/internal/fixed_queue.ts": "62bb119afa5b5ae8fc0c7048b50502347bec82e2588017d0b250c4671d6eff8f", "https://deno.land/std@0.177.0/node/internal/hide_stack_frames.ts": "9dd1bad0a6e62a1042ce3a51eb1b1ecee2f246907bff44835f86e8f021de679a", "https://deno.land/std@0.177.0/node/internal/normalize_encoding.mjs": "fd1d9df61c44d7196432f6e8244621468715131d18cc79cd299fc78ac549f707", "https://deno.land/std@0.177.0/node/internal/options.ts": "888f267c3fe8f18dc7b2f2fbdbe7e4a0fd3302ff3e99f5d6645601e924f3e3fb", "https://deno.land/std@0.177.0/node/internal/primordials.mjs": "a72d86b5aa55d3d50b8e916b6a59b7cc0dc5a31da8937114b4a113ad5aa08c74", "https://deno.land/std@0.177.0/node/internal/streams/destroy.mjs": "b665fc71178919a34ddeac8389d162a81b4bc693ff7dc2557fa41b3a91011967", "https://deno.land/std@0.177.0/node/internal/streams/end-of-stream.mjs": "a4fb1c2e32d58dff440d4e716e2c4daaa403b3095304a028bb428575cfeed716", "https://deno.land/std@0.177.0/node/internal/streams/utils.mjs": "f2fe2e6bdc506da24c758970890cc2a21642045b129dee618bd3827c60dd9e33", "https://deno.land/std@0.177.0/node/internal/streams/writable.mjs": "775928726d0483ace8e45a35f30db2019a22dd7b9a81b67b158420e21cc692c5", "https://deno.land/std@0.177.0/node/internal/util.mjs": "f7fe2e1ca5e66f550ad0856b9f5ee4d666f0c071fe212ea7fc7f37cfa81f97a5", "https://deno.land/std@0.177.0/node/internal/util/inspect.mjs": "11d7c9cab514b8e485acc3978c74b837263ff9c08ae4537fa18ad56bae633259", "https://deno.land/std@0.177.0/node/internal/util/types.ts": "0e587b44ec5e017cf228589fc5ce9983b75beece6c39409c34170cfad49d6417", "https://deno.land/std@0.177.0/node/internal/validators.mjs": "e02f2b02dd072a5d623970292588d541204dc82207b4c58985d933a5f4b382e6", "https://deno.land/std@0.177.0/node/internal_binding/_libuv_winerror.ts": "30c9569603d4b97a1f1a034d88a3f74800d5ea1f12fcc3d225c9899d4e1a518b", "https://deno.land/std@0.177.0/node/internal_binding/_node.ts": "cb2389b0eab121df99853eb6a5e3a684e4537e065fb8bf2cca0cbf219ce4e32e", "https://deno.land/std@0.177.0/node/internal_binding/_timingSafeEqual.ts": "7d9732464d3c669ff07713868ce5d25bc974a06112edbfb5f017fc3c70c0853e", "https://deno.land/std@0.177.0/node/internal_binding/_utils.ts": "7c58a2fbb031a204dee9583ba211cf9c67922112fe77e7f0b3226112469e9fe1", "https://deno.land/std@0.177.0/node/internal_binding/_winerror.ts": "3e8cfdfe22e89f13d2b28529bab35155e6b1730c0221ec5a6fc7077dc037be13", "https://deno.land/std@0.177.0/node/internal_binding/buffer.ts": "31729e0537921d6c730ad0afea44a7e8a0a1044d070ade8368226cb6f7390c8b", "https://deno.land/std@0.177.0/node/internal_binding/constants.ts": "21ff9d1ee71d0a2086541083a7711842fc6ae25e264dbf45c73815aadce06f4c", "https://deno.land/std@0.177.0/node/internal_binding/crypto.ts": "29e8f94f283a2e7d4229d3551369c6a40c2af9737fad948cb9be56bef6c468cd", "https://deno.land/std@0.177.0/node/internal_binding/node_options.ts": "0b5cb0bf4379a39278d7b7bb6bb2c2751baf428fe437abe5ed3e8441fae1f18b", "https://deno.land/std@0.177.0/node/internal_binding/string_decoder.ts": "54c3c1cbd5a9254881be58bf22637965dc69535483014dab60487e299cb95445", "https://deno.land/std@0.177.0/node/internal_binding/types.ts": "2187595a58d2cf0134f4db6cc2a12bf777f452f52b15b6c3aed73fa072aa5fc3", "https://deno.land/std@0.177.0/node/internal_binding/util.ts": "808ff3b92740284184ab824adfc420e75398c88c8bccf5111f0c24ac18c48f10", "https://deno.land/std@0.177.0/node/internal_binding/uv.ts": "eb0048e30af4db407fb3f95563e30d70efd6187051c033713b0a5b768593a3a3", "https://deno.land/std@0.177.0/node/perf_hooks.ts": "c20e1f02f463e065e8f6c1d4fa97b0d3832dc0db5b5d8ea37f99a962ecf11f35", "https://deno.land/std@0.177.0/node/stream.ts": "09e348302af40dcc7dc58aa5e40fdff868d11d8d6b0cfb85cbb9c75b9fe450c7", "https://deno.land/std@0.177.0/node/string_decoder.ts": "1a17e3572037c512cc5fc4b29076613e90f225474362d18da908cb7e5ccb7e88" } } dmonad-lib0-c7e7806/diff.js000066400000000000000000000112211512504553500154150ustar00rootroot00000000000000/** * Efficient diffs. * * @module diff */ import { equalityStrict } from './function.js' /** * A SimpleDiff describes a change on a String. * * ```js * console.log(a) // the old value * console.log(b) // the updated value * // Apply changes of diff (pseudocode) * a.remove(diff.index, diff.remove) // Remove `diff.remove` characters * a.insert(diff.index, diff.insert) // Insert `diff.insert` * a === b // values match * ``` * * @template {string|Array} T * @typedef {Object} SimpleDiff * @property {Number} index The index where changes were applied * @property {Number} remove The number of characters to delete starting * at `index`. * @property {T} insert The new text to insert at `index` after applying */ const highSurrogateRegex = /[\uD800-\uDBFF]/ const lowSurrogateRegex = /[\uDC00-\uDFFF]/ /** * Create a diff between two strings. This diff implementation is highly * efficient, but not very sophisticated. * * @function * * @param {string} a The old version of the string * @param {string} b The updated version of the string * @return {SimpleDiff} The diff description. */ export const simpleDiffString = (a, b) => { let left = 0 // number of same characters counting from left let right = 0 // number of same characters counting from right while (left < a.length && left < b.length && a[left] === b[left]) { left++ } // If the last same character is a high surrogate, we need to rollback to the previous character if (left > 0 && highSurrogateRegex.test(a[left - 1])) left-- while (right + left < a.length && right + left < b.length && a[a.length - right - 1] === b[b.length - right - 1]) { right++ } // If the last same character is a low surrogate, we need to rollback to the previous character if (right > 0 && lowSurrogateRegex.test(a[a.length - right])) right-- return { index: left, remove: a.length - left - right, insert: b.slice(left, b.length - right) } } /** * @todo Remove in favor of simpleDiffString * @deprecated */ export const simpleDiff = simpleDiffString /** * 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. * * @function * @template T * * @param {Array} a The old version of the array * @param {Array} b The updated version of the array * @param {function(T, T):boolean} [compare] * @return {SimpleDiff>} The diff description. */ export const simpleDiffArray = (a, b, compare = equalityStrict) => { let left = 0 // number of same characters counting from left let right = 0 // number of same characters counting from right while (left < a.length && left < b.length && compare(a[left], b[left])) { left++ } while (right + left < a.length && right + left < b.length && compare(a[a.length - right - 1], b[b.length - right - 1])) { right++ } return { index: left, remove: a.length - left - right, insert: b.slice(left, b.length - right) } } /** * Diff text and try to diff at the current cursor position. * * @param {string} a * @param {string} b * @param {number} cursor This should refer to the current left cursor-range position */ export const simpleDiffStringWithCursor = (a, b, cursor) => { let left = 0 // number of same characters counting from left let right = 0 // number of same characters counting from right // Iterate left to the right until we find a changed character // First iteration considers the current cursor position while ( left < a.length && left < b.length && a[left] === b[left] && left < cursor ) { left++ } // If the last same character is a high surrogate, we need to rollback to the previous character if (left > 0 && highSurrogateRegex.test(a[left - 1])) left-- // Iterate right to the left until we find a changed character while ( right + left < a.length && right + left < b.length && a[a.length - right - 1] === b[b.length - right - 1] ) { right++ } // If the last same character is a low surrogate, we need to rollback to the previous character if (right > 0 && lowSurrogateRegex.test(a[a.length - right])) right-- // Try to iterate left further to the right without caring about the current cursor position while ( right + left < a.length && right + left < b.length && a[left] === b[left] ) { left++ } if (left > 0 && highSurrogateRegex.test(a[left - 1])) left-- return { index: left, remove: a.length - left - right, insert: b.slice(left, b.length - right) } } dmonad-lib0-c7e7806/diff.test.js000066400000000000000000000120271512504553500164000ustar00rootroot00000000000000import { simpleDiffString, simpleDiffArray, simpleDiffStringWithCursor } from './diff.js' import * as prng from './prng.js' import * as f from './function.js' import * as t from './testing.js' import * as str from './string.js' /** * @param {string} a * @param {string} b * @param {{index: number,remove:number,insert:string}} expected */ function runDiffTest (a, b, expected) { const result = simpleDiffString(a, b) t.compare(result, expected) t.compare(result, simpleDiffStringWithCursor(a, b, a.length)) // check that the withCursor approach returns the same result const recomposed = str.splice(a, result.index, result.remove, result.insert) t.compareStrings(recomposed, b) const arrResult = simpleDiffArray(Array.from(a), Array.from(b)) const arrRecomposed = Array.from(a) arrRecomposed.splice(arrResult.index, arrResult.remove, ...arrResult.insert) t.compareStrings(arrRecomposed.join(''), b) } /** * @param {t.TestCase} tc */ export const testDiffing = tc => { runDiffTest('abc', 'axc', { index: 1, remove: 1, insert: 'x' }) runDiffTest('bc', 'xc', { index: 0, remove: 1, insert: 'x' }) runDiffTest('ab', 'ax', { index: 1, remove: 1, insert: 'x' }) runDiffTest('b', 'x', { index: 0, remove: 1, insert: 'x' }) runDiffTest('', 'abc', { index: 0, remove: 0, insert: 'abc' }) runDiffTest('abc', 'xyz', { index: 0, remove: 3, insert: 'xyz' }) runDiffTest('axz', 'au', { index: 1, remove: 2, insert: 'u' }) runDiffTest('ax', 'axy', { index: 2, remove: 0, insert: 'y' }) // These strings share high-surrogate characters runDiffTest('\u{d83d}\u{dc77}'/* '👷' */, '\u{d83d}\u{dea7}\u{d83d}\u{dc77}'/* '🚧👷' */, { index: 0, remove: 0, insert: '🚧' }) runDiffTest('\u{d83d}\u{dea7}\u{d83d}\u{dc77}'/* '🚧👷' */, '\u{d83d}\u{dc77}'/* '👷' */, { index: 0, remove: 2, insert: '' }) // These strings share low-surrogate characters runDiffTest('\u{d83d}\u{dfe6}\u{d83d}\u{dfe6}'/* '🟦🟦' */, '\u{d83c}\u{dfe6}\u{d83d}\u{dfe6}'/* '🏦🟦' */, { index: 0, remove: 2, insert: '🏦' }) // check 4-character unicode symbols runDiffTest('🇦🇨', '🇦🇩', { index: 2, remove: 2, insert: '🇩' }) runDiffTest('a🇧🇩', '🇦🇩', { index: 0, remove: 3, insert: '🇦' }) } /** * @param {t.TestCase} tc */ export const testRepeatDiffing = tc => { const a = prng.word(tc.prng) const b = prng.word(tc.prng) const change = simpleDiffString(a, b) const recomposed = str.splice(a, change.index, change.remove, change.insert) t.compareStrings(recomposed, b) } /** * @param {t.TestCase} tc */ export const testSimpleDiffWithCursor = tc => { const initial = 'Hello WorldHello World' const expected = 'Hello World' { const change = simpleDiffStringWithCursor(initial, 'Hello World', 0) // should delete the first hello world t.compare(change, { insert: '', remove: 11, index: 0 }) const recomposed = str.splice(initial, change.index, change.remove, change.insert) t.compareStrings(expected, recomposed) } { const change = simpleDiffStringWithCursor(initial, 'Hello World', 11) // should delete the second hello world t.compare(change, { insert: '', remove: 11, index: 11 }) const recomposedSecond = str.splice(initial, change.index, change.remove, change.insert) t.compareStrings(recomposedSecond, expected) } { const change = simpleDiffStringWithCursor(initial, 'Hello World', 5) // should delete in the midst of Hello World t.compare(change, { insert: '', remove: 11, index: 5 }) const recomposed = str.splice(initial, change.index, change.remove, change.insert) t.compareStrings(expected, recomposed) } { const initial = 'Hello my World' const change = simpleDiffStringWithCursor(initial, 'Hello World', 0) // Should delete after the current cursor position t.compare(change, { insert: '', remove: 3, index: 5 }) const recomposed = str.splice(initial, change.index, change.remove, change.insert) t.compareStrings(expected, recomposed) } { const initial = '🚧🚧🚧' const change = simpleDiffStringWithCursor(initial, '🚧🚧', 2) // Should delete after the midst of 🚧 t.compare(change, { insert: '', remove: 2, index: 2 }) const recomposed = str.splice(initial, change.index, change.remove, change.insert) t.compareStrings('🚧🚧', recomposed) } { const initial = '🚧👷🚧👷' const change = simpleDiffStringWithCursor(initial, '🚧🚧', 2) // Should delete after the first 🚧 and insert 🚧 t.compare(change, { insert: '🚧', remove: 6, index: 2 }) const recomposed = str.splice(initial, change.index, change.remove, change.insert) t.compareStrings('🚧🚧', recomposed) } } /** * @param {t.TestCase} tc */ export const testArrayDiffing = tc => { const a = [[1, 2], { x: 'x' }] const b = [[1, 2], { x: 'x' }] t.compare(simpleDiffArray(a, b, f.equalityFlat), { index: 2, remove: 0, insert: [] }) t.compare(simpleDiffArray(a, b, f.equalityStrict), { index: 0, remove: 2, insert: b }) t.compare(simpleDiffArray([{ x: 'y' }, []], a, f.equalityFlat), { index: 0, remove: 2, insert: b }) } dmonad-lib0-c7e7806/diff/000077500000000000000000000000001512504553500150625ustar00rootroot00000000000000dmonad-lib0-c7e7806/diff/patience.js000066400000000000000000000152001512504553500172060ustar00rootroot00000000000000/** * A very simple diff algorithm. Slightly adapted to support splitting at different stages (e.g. * first diff lines, then diff words) * * https://bramcohen.livejournal.com/73318.html * * @experiemantal This API will likely change. */ import * as map from '../map.js' import * as math from '../math.js' import * as array from '../array.js' /** * Implementation of patience diff. Expects that content is pre-split (e.g. by newline). * * @param {Array} as * @param {Array} bs * @return {Array<{ index: number, remove: Array, insert: Array}>} changeset @todo should use delta instead */ export const diff = (as, bs) => { const { middleAs, middleBs, commonPrefix } = removeCommonPrefixAndSuffix(as, bs) return lcs(middleAs, middleBs, commonPrefix) } /** * @param {string} a * @param {string} b * @param {RegExp|string} _regexp */ export const diffSplitBy = (a, b, _regexp) => { const isStringSeparator = typeof _regexp === 'string' const separator = isStringSeparator ? _regexp : '' const regexp = isStringSeparator ? new RegExp(_regexp, 'g') : _regexp const as = splitByRegexp(a, regexp, !isStringSeparator) const bs = splitByRegexp(b, regexp, !isStringSeparator) const changes = diff(as, bs) let prevSplitIndex = 0 let prevStringIndex = 0 return changes.map(change => { for (; prevSplitIndex < change.index; prevSplitIndex++) { prevStringIndex += as[prevSplitIndex].length } return { index: prevStringIndex, remove: change.remove.join(separator), insert: change.insert.join(separator) } }) } /** * Sensible default for diffing strings using patience (it's fast though). * * Perform different types of patience diff on the content. Diff first by newline, then paragraphs, then by word * (split by space, brackets, punctuation) * * @param {string} a * @param {string} b */ export const diffAuto = (a, b) => diffSplitBy(a, b, '\n').map(d => diffSplitBy(d.remove, d.insert, /\. |[a-zA-Z0-9]+|[. ()[\],;{}]/g).map(dd => ({ insert: dd.insert, remove: dd.remove, index: dd.index + d.index })) ).flat() /** * @param {Array} as * @param {Array} bs */ const removeCommonPrefixAndSuffix = (as, bs) => { const commonLen = math.min(as.length, bs.length) let commonPrefix = 0 let commonSuffix = 0 // match start for (; commonPrefix < commonLen && as[commonPrefix] === bs[commonPrefix]; commonPrefix++) { /* nop */ } // match end for (; commonSuffix < commonLen - commonPrefix && as[as.length - 1 - commonSuffix] === bs[bs.length - 1 - commonSuffix]; commonSuffix++) { /* nop */ } const middleAs = as.slice(commonPrefix, as.length - commonSuffix) const middleBs = bs.slice(commonPrefix, bs.length - commonSuffix) return { middleAs, middleBs, commonPrefix, commonSuffix } } /** * Splits string by regex and returns all strings as an array. The matched parts are also returned. * * @param {string} str * @param {RegExp} regexp * @param {boolean} includeSeparator */ const splitByRegexp = (str, regexp, includeSeparator) => { const matches = [...str.matchAll(regexp)] let prevIndex = 0 /** * @type {Array} */ const res = [] matches.forEach(m => { prevIndex < (m.index || 0) && res.push(str.slice(prevIndex, m.index)) includeSeparator && res.push(m[0]) // is always non-empty prevIndex = /** @type {number} */ (m.index) + m[0].length }) const end = str.slice(prevIndex) end.length > 0 && res.push(end) return res } /** * An item may have multiple occurances (not when matching unique entries). It also may have a * reference to the stack of other items (from as to bs). */ class Item { constructor () { /** * @type {Array} */ this.indexes = [] /** * The matching item from the other side * @type {Item?} */ this.match = null /** * For patience sort. Reference (index of the stack) to the previous pile. * * @type {Item?} */ this.ref = null } } /** * @param {Array} xs */ const partition = xs => { /** * @type {Map} */ const refs = map.create() xs.forEach((x, index) => { map.setIfUndefined(refs, x, () => new Item()).indexes.push(index) }) return refs } /** * Find the longest common subsequence of items using patience sort. * * @param {Array} as * @param {Array} bs * @param {number} indexAdjust */ const lcs = (as, bs, indexAdjust) => { if (as.length === 0 && bs.length === 0) return [] const aParts = partition(as) const bParts = partition(bs) /** * @type {Array>} I.e. Array> */ const piles = [] aParts.forEach((aItem, aKey) => { // skip if no match or if either item is not unique if (aItem.indexes.length > 1 || (aItem.match = bParts.get(aKey) || null) == null || aItem.match.indexes.length > 1) return for (let i = 0; i < piles.length; i++) { const pile = piles[i] if (aItem.match.indexes[0] < /** @type {Item} */ (pile[pile.length - 1].match).indexes[0]) { pile.push(aItem) if (i > 0) aItem.ref = array.last(piles[i - 1]) return } } piles.length > 0 && (aItem.ref = array.last(piles[piles.length - 1])) piles.push([aItem]) }) /** * References to all matched items * * @type {Array} */ const matches = [] /** * @type {Item?} */ let currPileItem = piles[piles.length - 1]?.[0] while (currPileItem != null) { matches.push(currPileItem) currPileItem = currPileItem.ref } matches.reverse() // add pseude match (assume the string terminal always matches) const pseudoA = new Item() const pseudoB = new Item() pseudoA.match = pseudoB pseudoA.indexes.push(as.length) pseudoB.indexes.push(bs.length) matches.push(pseudoA) /** * @type {Array<{ index: number, remove: Array, insert: Array}>} */ const changeset = [] let diffAStart = 0 let diffBStart = 0 for (let i = 0; i < matches.length; i++) { const m = matches[i] const delLength = m.indexes[0] - diffAStart const insLength = /** @type {Item} */ (m.match).indexes[0] - diffBStart if (delLength !== 0 || insLength !== 0) { const stripped = removeCommonPrefixAndSuffix(as.slice(diffAStart, diffAStart + delLength), bs.slice(diffBStart, diffBStart + insLength)) if (stripped.middleAs.length !== 0 || stripped.middleBs.length !== 0) { changeset.push({ index: diffAStart + indexAdjust + stripped.commonPrefix, remove: stripped.middleAs, insert: stripped.middleBs }) } } diffAStart = m.indexes[0] + 1 diffBStart = /** @type {Item} */ (m.match).indexes[0] + 1 } return changeset } dmonad-lib0-c7e7806/diff/patience.test.js000066400000000000000000000073531512504553500201760ustar00rootroot00000000000000import * as prng from '../prng.js' import * as t from '../testing.js' import * as patience from './patience.js' /** * @param {string} a * @param {string} b * @param {Array<{ insert: string, remove: string, index: number }>} expect */ const testDiffAuto = (a, b, expect) => { const res = patience.diffAuto(a, b) t.info(`Diffing "${a}" with "${b}"`) console.log(res) t.compare(res, expect) } /** * @param {t.TestCase} _tc */ export const testDiffing = _tc => { testDiffAuto( 'z d a b c', 'y d b a c', [{ insert: 'y', remove: 'z', index: 0 }, { insert: 'b ', remove: '', index: 4 }, { insert: '', remove: ' b', index: 5 }] ) testDiffAuto( 'a b c', 'b a c', [{ insert: 'b ', remove: '', index: 0 }, { insert: '', remove: ' b', index: 1 }] ) testDiffAuto( 'x ', ' ', [{ insert: '', remove: 'x ', index: 0 }] ) // no change testDiffAuto( 'testa', 'testa', [] ) // single char change testDiffAuto( 'testa', 'testb', [{ insert: 'testb', remove: 'testa', index: 0 }] ) // single word change testDiffAuto( 'The rabbit jumped over the fence.\n', 'The dog jumped over the fence.\n', [{ insert: 'dog', remove: 'rabbit', index: 4 }] ) // similar sentences. testDiffAuto( 'the dog. the cat.', 'the cat. the rabbit.', [{ insert: 'cat', remove: 'dog', index: 4 }, { insert: 'rabbit', remove: 'cat', index: 13 }] ) testDiffAuto( 'cat food', 'my cat food', [{ insert: 'my ', remove: '', index: 0 }] ) testDiffAuto( 'the cat stuff', 'my cat food', [{ insert: 'my', remove: 'the', index: 0 }, { insert: 'food', remove: 'stuff', index: 8 }] ) } /** * @param {t.TestCase} tc */ export const testRepeatRandomWordReplace = tc => { const NWords = 600 const NReplacements = Math.floor(NWords / 20) const NInserts = Math.floor(NWords / 20) + 1 const NDeletes = Math.floor(NWords / 20) + 1 const MaxWordLen = 6 t.group(`Diff on changed list of words (#words=${NWords},#replacements=${NReplacements},#inserts=${NInserts},#deletes=${NDeletes}})`, () => { const words = [] for (let i = 0; i < NWords; i++) { words.push(prng.word(tc.prng, 0, MaxWordLen)) } const newWords = words.slice() for (let i = 0; i < NReplacements; i++) { const pos = prng.int32(tc.prng, 0, words.length - 1) newWords[pos] = prng.word(tc.prng, 0, MaxWordLen) } for (let i = 0; i < NInserts; i++) { const pos = prng.int32(tc.prng, 0, words.length - 1) newWords.splice(pos, 0, prng.word(tc.prng, 0, MaxWordLen)) } for (let i = 0; i < NDeletes; i++) { const pos = prng.int32(tc.prng, 0, words.length - 1) newWords.splice(pos, 1) } const before = words.join(' ') const after = newWords.join(' ') /** * @type {Array<{ insert: string, remove: string, index: number }>} */ let d = [] t.measureTime(`time to calculate diff (a.length=${before.length},b.length=${after.length})`, () => { d = patience.diffAuto(before, after) }) let updating = before console.log({ words, newWords, diff: d }) // verify by applying for (let i = d.length - 1; i >= 0; i--) { const change = d[i] const spliced = updating.split('') spliced.splice(change.index, change.remove.length, change.insert) updating = spliced.join('') } t.compare(updating, after) t.assert(d.length <= NReplacements + 1 + NInserts + NDeletes) // Sanity check: A maximum of one fault }) } dmonad-lib0-c7e7806/dom.js000066400000000000000000000151601512504553500152720ustar00rootroot00000000000000/* eslint-env browser */ /** * Utility module to work with the DOM. * * @module dom */ import * as pair from './pair.js' import * as map from './map.js' import * as $ from './schema.js' /* c8 ignore start */ /** * @type {Document} */ export const doc = /** @type {Document} */ (typeof document !== 'undefined' ? document : {}) /** * @param {string} name * @return {HTMLElement} */ export const createElement = name => doc.createElement(name) /** * @return {DocumentFragment} */ export const createDocumentFragment = () => doc.createDocumentFragment() /** * @type {$.Schema} */ export const $fragment = $.$custom(el => el.nodeType === DOCUMENT_FRAGMENT_NODE) /** * @param {string} text * @return {Text} */ export const createTextNode = text => doc.createTextNode(text) export const domParser = /** @type {DOMParser} */ (typeof DOMParser !== 'undefined' ? new DOMParser() : null) /** * @param {HTMLElement} el * @param {string} name * @param {Object} opts */ export const emitCustomEvent = (el, name, opts) => el.dispatchEvent(new CustomEvent(name, opts)) /** * @param {Element} el * @param {Array>} attrs Array of key-value pairs * @return {Element} */ export const setAttributes = (el, attrs) => { pair.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 {Element} el * @param {Map} attrs Array of key-value pairs * @return {Element} */ export const setAttributesMap = (el, attrs) => { attrs.forEach((value, key) => { el.setAttribute(key, value) }) return el } /** * @param {Array|HTMLCollection} children * @return {DocumentFragment} */ export 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} nodes * @return {Element} */ export const append = (parent, nodes) => { appendChild(parent, fragment(nodes)) return parent } /** * @param {HTMLElement} el */ export const remove = el => el.remove() /** * @param {EventTarget} el * @param {string} name * @param {EventListener} f */ export const addEventListener = (el, name, f) => el.addEventListener(name, f) /** * @param {EventTarget} el * @param {string} name * @param {EventListener} f */ export const removeEventListener = (el, name, f) => el.removeEventListener(name, f) /** * @param {Node} node * @param {Array>} listeners * @return {Node} */ export const addEventListeners = (node, listeners) => { pair.forEach(listeners, (name, f) => addEventListener(node, name, f)) return node } /** * @param {Node} node * @param {Array>} listeners * @return {Node} */ export const removeEventListeners = (node, listeners) => { pair.forEach(listeners, (name, f) => removeEventListener(node, name, f)) return node } /** * @param {string} name * @param {Array|pair.Pair>} attrs Array of key-value pairs * @param {Array} children * @return {Element} */ export const element = (name, attrs = [], children = []) => append(setAttributes(createElement(name), attrs), children) /** * @type {$.Schema} */ export const $element = $.$custom(el => el.nodeType === ELEMENT_NODE) /** * @param {number} width * @param {number} height */ export const canvas = (width, height) => { const c = /** @type {HTMLCanvasElement} */ (createElement('canvas')) c.height = height c.width = width return c } /** * @param {string} t * @return {Text} */ export const text = createTextNode /** * @type {$.Schema} */ export const $text = $.$custom(el => el.nodeType === TEXT_NODE) /** * @param {pair.Pair} pair */ export const pairToStyleString = pair => `${pair.left}:${pair.right};` /** * @param {Array>} pairs * @return {string} */ export const pairsToStyleString = pairs => pairs.map(pairToStyleString).join('') /** * @param {Map} m * @return {string} */ export const mapToStyleString = m => map.map(m, (value, key) => `${key}:${value};`).join('') /** * @todo should always query on a dom element * * @param {HTMLElement|ShadowRoot} el * @param {string} query * @return {HTMLElement | null} */ export const querySelector = (el, query) => el.querySelector(query) /** * @param {HTMLElement|ShadowRoot} el * @param {string} query * @return {NodeListOf} */ export const querySelectorAll = (el, query) => el.querySelectorAll(query) /** * @param {string} id * @return {HTMLElement} */ export const getElementById = id => /** @type {HTMLElement} */ (doc.getElementById(id)) /** * @param {string} html * @return {HTMLElement} */ const _parse = html => domParser.parseFromString(`${html}`, 'text/html').body /** * @param {string} html * @return {DocumentFragment} */ export const parseFragment = html => fragment(/** @type {any} */ (_parse(html).childNodes)) /** * @param {string} html * @return {HTMLElement} */ export const parseElement = html => /** @type HTMLElement */ (_parse(html).firstElementChild) /** * @param {HTMLElement} oldEl * @param {HTMLElement|DocumentFragment} newEl */ export const replaceWith = (oldEl, newEl) => oldEl.replaceWith(newEl) /** * @param {HTMLElement} parent * @param {HTMLElement} el * @param {Node|null} ref * @return {HTMLElement} */ export const insertBefore = (parent, el, ref) => parent.insertBefore(el, ref) /** * @param {Node} parent * @param {Node} child * @return {Node} */ export const appendChild = (parent, child) => parent.appendChild(child) export const ELEMENT_NODE = doc.ELEMENT_NODE export const TEXT_NODE = doc.TEXT_NODE export const CDATA_SECTION_NODE = doc.CDATA_SECTION_NODE export const COMMENT_NODE = doc.COMMENT_NODE export const DOCUMENT_NODE = doc.DOCUMENT_NODE export const DOCUMENT_TYPE_NODE = doc.DOCUMENT_TYPE_NODE export const DOCUMENT_FRAGMENT_NODE = doc.DOCUMENT_FRAGMENT_NODE /** * @type {$.Schema} */ export const $node = $.$custom(el => el.nodeType === DOCUMENT_NODE) /** * @param {any} node * @param {number} type */ export const checkNodeType = (node, type) => node.nodeType === type /** * @param {Node} parent * @param {HTMLElement} child */ export const isParentOf = (parent, child) => { let p = child.parentNode while (p && p !== parent) { p = p.parentNode } return p === parent } /* c8 ignore stop */ dmonad-lib0-c7e7806/encoding.js000066400000000000000000000637071512504553500163130ustar00rootroot00000000000000/** * 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 */ import * as math from './math.js' import * as number from './number.js' import * as binary from './binary.js' import * as string from './string.js' import * as array from './array.js' /** * A BinaryEncoder handles the encoding to an Uint8Array. */ export class Encoder { constructor () { this.cpos = 0 this.cbuf = new Uint8Array(100) /** * @type {Array} */ this.bufs = [] } } /** * @function * @return {Encoder} */ export const createEncoder = () => new Encoder() /** * @param {function(Encoder):void} f */ export const encode = (f) => { const encoder = createEncoder() f(encoder) return toUint8Array(encoder) } /** * The current length of the encoded data. * * @function * @param {Encoder} encoder * @return {number} */ export const length = encoder => { let len = encoder.cpos for (let i = 0; i < encoder.bufs.length; i++) { len += encoder.bufs[i].length } return len } /** * Check whether encoder is empty. * * @function * @param {Encoder} encoder * @return {boolean} */ export const hasContent = encoder => encoder.cpos > 0 || encoder.bufs.length > 0 /** * Transform to Uint8Array. * * @function * @param {Encoder} encoder * @return {Uint8Array} The created ArrayBuffer. */ export 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(new Uint8Array(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 */ export const verifyLen = (encoder, len) => { const bufferLen = encoder.cbuf.length if (bufferLen - encoder.cpos < len) { encoder.bufs.push(new Uint8Array(encoder.cbuf.buffer, 0, encoder.cpos)) encoder.cbuf = new Uint8Array(math.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. */ export 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 at a specific position. * Position must already be written (i.e. encoder.length > pos) * * @function * @param {Encoder} encoder * @param {number} pos Position to which to write data * @param {number} num Unsigned 8-bit integer */ export const set = (encoder, pos, num) => { let buffer = null // iterate all buffers and adjust position for (let i = 0; i < encoder.bufs.length && buffer === null; i++) { const b = encoder.bufs[i] if (pos < b.length) { buffer = b // found buffer } else { pos -= b.length } } if (buffer === null) { // use current buffer buffer = encoder.cbuf } buffer[pos] = num } /** * Write one byte as an unsigned integer. * * @function * @param {Encoder} encoder * @param {number} num The number that is to be encoded. */ export const writeUint8 = write /** * Write one byte as an unsigned Integer at a specific location. * * @function * @param {Encoder} encoder * @param {number} pos The location where the data will be written. * @param {number} num The number that is to be encoded. */ export const setUint8 = set /** * Write two bytes as an unsigned integer. * * @function * @param {Encoder} encoder * @param {number} num The number that is to be encoded. */ export const writeUint16 = (encoder, num) => { write(encoder, num & binary.BITS8) write(encoder, (num >>> 8) & binary.BITS8) } /** * Write two bytes as an unsigned integer at a specific location. * * @function * @param {Encoder} encoder * @param {number} pos The location where the data will be written. * @param {number} num The number that is to be encoded. */ export const setUint16 = (encoder, pos, num) => { set(encoder, pos, num & binary.BITS8) set(encoder, pos + 1, (num >>> 8) & binary.BITS8) } /** * Write two bytes as an unsigned integer * * @function * @param {Encoder} encoder * @param {number} num The number that is to be encoded. */ export const writeUint32 = (encoder, num) => { for (let i = 0; i < 4; i++) { write(encoder, num & binary.BITS8) num >>>= 8 } } /** * Write two bytes as an unsigned integer in big endian order. * (most significant byte first) * * @function * @param {Encoder} encoder * @param {number} num The number that is to be encoded. */ export const writeUint32BigEndian = (encoder, num) => { for (let i = 3; i >= 0; i--) { write(encoder, (num >>> (8 * i)) & binary.BITS8) } } /** * Write two bytes as an unsigned integer at a specific location. * * @function * @param {Encoder} encoder * @param {number} pos The location where the data will be written. * @param {number} num The number that is to be encoded. */ export const setUint32 = (encoder, pos, num) => { for (let i = 0; i < 4; i++) { set(encoder, pos + i, num & binary.BITS8) num >>>= 8 } } /** * 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. */ export const writeVarUint = (encoder, num) => { while (num > binary.BITS7) { write(encoder, binary.BIT8 | (binary.BITS7 & num)) num = math.floor(num / 128) // shift >>> 7 } write(encoder, binary.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. */ export const writeVarInt = (encoder, num) => { const isNegative = math.isNegativeZero(num) if (isNegative) { num = -num } // |- whether to continue reading |- whether is negative |- number write(encoder, (num > binary.BITS6 ? binary.BIT8 : 0) | (isNegative ? binary.BIT7 : 0) | (binary.BITS6 & num)) num = math.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 > binary.BITS7 ? binary.BIT8 : 0) | (binary.BITS7 & num)) num = math.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. */ export const _writeVarStringNative = (encoder, str) => { if (str.length < _maxStrBSize) { // We can encode the string into the existing buffer /* c8 ignore next */ const written = string.utf8TextEncoder.encodeInto(str, _strBuffer).written || 0 writeVarUint(encoder, written) for (let i = 0; i < written; i++) { write(encoder, _strBuffer[i]) } } else { writeVarUint8Array(encoder, string.encodeUtf8(str)) } } /** * Write a variable length string. * * @function * @param {Encoder} encoder * @param {String} str The string that is to be encoded. */ export 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 */ export const writeVarString = (string.utf8TextEncoder && /** @type {any} */ (string.utf8TextEncoder).encodeInto) ? _writeVarStringNative : _writeVarStringPolyfill /** * Write a string terminated by a special byte sequence. This is not very performant and is * generally discouraged. However, the resulting byte arrays are lexiographically ordered which * makes this a nice feature for databases. * * The string will be encoded using utf8 and then terminated and escaped using writeTerminatingUint8Array. * * @function * @param {Encoder} encoder * @param {String} str The string that is to be encoded. */ export const writeTerminatedString = (encoder, str) => writeTerminatedUint8Array(encoder, string.encodeUtf8(str)) /** * Write a terminating Uint8Array. Note that this is not performant and is generally * discouraged. There are few situations when this is needed. * * We use 0x0 as a terminating character. 0x1 serves as an escape character for 0x0 and 0x1. * * Example: [0,1,2] is encoded to [1,0,1,1,2,0]. 0x0, and 0x1 needed to be escaped using 0x1. Then * the result is terminated using the 0x0 character. * * This is basically how many systems implement null terminated strings. However, we use an escape * character 0x1 to avoid issues and potenial attacks on our database (if this is used as a key * encoder for NoSql databases). * * @function * @param {Encoder} encoder * @param {Uint8Array} buf The string that is to be encoded. */ export const writeTerminatedUint8Array = (encoder, buf) => { for (let i = 0; i < buf.length; i++) { const b = buf[i] if (b === 0 || b === 1) { write(encoder, 1) } write(encoder, buf[i]) } write(encoder, 0) } /** * Write the content of another Encoder. * * @TODO: can be improved! * - Note: Should consider that when appending a lot of small Encoders, we should rather clone than referencing the old structure. * Encoders start with a rather big initial buffer. * * @function * @param {Encoder} encoder The enUint8Arr * @param {Encoder} append The BinaryEncoder to be written. */ export const writeBinaryEncoder = (encoder, append) => writeUint8Array(encoder, toUint8Array(append)) /** * Append fixed-length Uint8Array to the encoder. * * @function * @param {Encoder} encoder * @param {Uint8Array} uint8Array */ export const writeUint8Array = (encoder, uint8Array) => { const bufferLen = encoder.cbuf.length const cpos = encoder.cpos const leftCopyLen = math.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(math.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 */ export 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} */ export 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 */ export const writeFloat32 = (encoder, num) => writeOnDataView(encoder, 4).setFloat32(0, num, false) /** * @param {Encoder} encoder * @param {number} num */ export const writeFloat64 = (encoder, num) => writeOnDataView(encoder, 8).setFloat64(0, num, false) /** * @param {Encoder} encoder * @param {bigint} num */ export const writeBigInt64 = (encoder, num) => /** @type {any} */ (writeOnDataView(encoder, 8)).setBigInt64(0, num, false) /** * @param {Encoder} encoder * @param {bigint} num */ export const writeBigUint64 = (encoder, num) => /** @type {any} */ (writeOnDataView(encoder, 8)).setBigUint64(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 } /** * @typedef {Array} AnyEncodableArray */ /** * @typedef {undefined|null|number|bigint|boolean|string|{[k:string]:AnyEncodable}|AnyEncodableArray|Uint8Array} AnyEncodable */ /** * 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 | 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 * * @param {Encoder} encoder * @param {AnyEncodable} data */ export const writeAny = (encoder, data) => { switch (typeof data) { case 'string': // TYPE 119: STRING write(encoder, 119) writeVarString(encoder, data) break case 'number': if (number.isInteger(data) && math.abs(data) <= binary.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 (array.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 */ export 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 } } } /** * Basic diff decoder using variable length encoding. * * Encodes the values [3, 1100, 1101, 1050, 0] to [3, 1097, 1, -51, -1050] using writeVarInt. */ export class IntDiffEncoder extends Encoder { /** * @param {number} start */ constructor (start) { super() /** * Current state * @type {number} */ this.s = start } /** * @param {number} v */ write (v) { writeVarInt(this, v - this.s) this.s = v } } /** * 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]) */ export class RleIntDiffEncoder extends Encoder { /** * @param {number} start */ constructor (start) { super() /** * Current state * @type {number} */ this.s = start this.count = 0 } /** * @param {number} v */ write (v) { if (this.s === v && this.count > 0) { 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 writeVarInt(this, v - this.s) this.s = v } } } /** * @param {UintOptRleEncoder} encoder */ const flushUintOptRleEncoder = encoder => { if (encoder.count > 0) { // flush counter, unless this is the first value (count = 0) // case 1: just a single value. set sign to positive // case 2: write several values. set sign to negative to indicate that there is a length coming writeVarInt(encoder.encoder, encoder.count === 1 ? encoder.s : -encoder.s) if (encoder.count > 1) { writeVarUint(encoder.encoder, encoder.count - 2) // since count is always > 1, we can decrement by one. non-standard encoding ftw } } } /** * 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) */ export class UintOptRleEncoder { constructor () { this.encoder = new Encoder() /** * @type {number} */ this.s = 0 this.count = 0 } /** * @param {number} v */ write (v) { if (this.s === v) { this.count++ } else { flushUintOptRleEncoder(this) this.count = 1 this.s = v } } /** * Flush the encoded state and transform this to a Uint8Array. * * Note that this should only be called once. */ toUint8Array () { flushUintOptRleEncoder(this) return toUint8Array(this.encoder) } } /** * 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]. */ export class IncUintOptRleEncoder { constructor () { this.encoder = new Encoder() /** * @type {number} */ this.s = 0 this.count = 0 } /** * @param {number} v */ write (v) { if (this.s + this.count === v) { this.count++ } else { flushUintOptRleEncoder(this) this.count = 1 this.s = v } } /** * Flush the encoded state and transform this to a Uint8Array. * * Note that this should only be called once. */ toUint8Array () { flushUintOptRleEncoder(this) return toUint8Array(this.encoder) } } /** * @param {IntDiffOptRleEncoder} encoder */ const flushIntDiffOptRleEncoder = encoder => { if (encoder.count > 0) { // 31 bit making up the diff | wether to write the counter // const encodedDiff = encoder.diff << 1 | (encoder.count === 1 ? 0 : 1) const encodedDiff = encoder.diff * 2 + (encoder.count === 1 ? 0 : 1) // flush counter, unless this is the first value (count = 0) // case 1: just a single value. set first bit to positive // case 2: write several values. set first bit to negative to indicate that there is a length coming writeVarInt(encoder.encoder, encodedDiff) if (encoder.count > 1) { writeVarUint(encoder.encoder, encoder.count - 2) // since count is always > 1, we can decrement by one. non-standard encoding ftw } } } /** * 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: * * 1 bit that denotes whether the next value is a count (LSB) * * 1 bit that denotes whether this value is negative (MSB - 1) * * 1 bit that denotes whether to continue reading the variable length integer (MSB) * * Therefore, only five bits remain to encode diff ranges. * * Use this Encoder only when appropriate. In most cases, this is probably a bad idea. */ export class IntDiffOptRleEncoder { constructor () { this.encoder = new Encoder() /** * @type {number} */ this.s = 0 this.count = 0 this.diff = 0 } /** * @param {number} v */ write (v) { if (this.diff === v - this.s) { this.s = v this.count++ } else { flushIntDiffOptRleEncoder(this) this.count = 1 this.diff = v - this.s this.s = v } } /** * Flush the encoded state and transform this to a Uint8Array. * * Note that this should only be called once. */ toUint8Array () { flushIntDiffOptRleEncoder(this) return toUint8Array(this.encoder) } } /** * 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. */ export class StringEncoder { constructor () { /** * @type {Array} */ this.sarr = [] this.s = '' this.lensE = new UintOptRleEncoder() } /** * @param {string} string */ write (string) { this.s += string if (this.s.length > 19) { this.sarr.push(this.s) this.s = '' } this.lensE.write(string.length) } toUint8Array () { const encoder = new Encoder() this.sarr.push(this.s) this.s = '' writeVarString(encoder, this.sarr.join('')) writeUint8Array(encoder, this.lensE.toUint8Array()) return toUint8Array(encoder) } } dmonad-lib0-c7e7806/encoding.test.js000066400000000000000000000724031512504553500172620ustar00rootroot00000000000000/* global BigInt */ import * as encoding from './encoding.js' import * as decoding from './decoding.js' import * as prng from './prng.js' import * as t from './testing.js' import * as string from './string.js' import * as binary from './binary.js' import * as buffer from './buffer.js' import * as number from './number.js' import * as math from './math.js' /** * @type {Array} */ let genAnyLookupTable = [ gen => BigInt(prng.int53(gen, number.MIN_SAFE_INTEGER, number.MAX_SAFE_INTEGER)), // TYPE 122 _gen => undefined, // TYPE 127 _gen => null, // TYPE 126 gen => prng.int53(gen, number.MIN_SAFE_INTEGER, number.MAX_SAFE_INTEGER), // TYPE 125 gen => prng.real53(gen), // TYPE 124 and 123 _gen => true, // TYPE 121 _gen => false, // TYPE 120 gen => prng.utf16String(gen), // TYPE 119 (gen, depth, toJsonCompatible) => ({ val: genAny(gen, depth + 1, toJsonCompatible) }), // TYPE 118 (gen, depth, toJsonCompatible) => Array.from({ length: prng.uint32(gen, 0, 20 - depth) }).map(() => genAny(gen, depth + 1, toJsonCompatible)), // TYPE 117 gen => prng.uint8Array(gen, prng.uint32(gen, 0, 50)) // TYPE 116 ] const genAnyLookupTableJsonCompatible = genAnyLookupTable.slice(1) if (typeof BigInt === 'undefined') { genAnyLookupTable = genAnyLookupTable.slice(1) } /** * @param {prng.PRNG} gen * @param {number} _depth The current call-depth */ const genAny = (gen, _depth = 0, toJsonCompatible = false) => prng.oneOf(gen, toJsonCompatible ? genAnyLookupTableJsonCompatible : genAnyLookupTable)(gen, _depth, toJsonCompatible) /** * Check if binary encoding is compatible with golang binary encoding - binary.PutVarUint. * * Result: is compatible up to 32 bit: [0, 4294967295] / [0, 0xffffffff]. (max 32 bit unsigned integer) */ export const testGolangBinaryEncodingCompatibility = () => { const tests = [ { in: 0, out: [0] }, { in: 1, out: [1] }, { in: 128, out: [128, 1] }, { in: 200, out: [200, 1] }, { in: 32, out: [32] }, { in: 500, out: [244, 3] }, { in: 256, out: [128, 2] }, { in: 700, out: [188, 5] }, { in: 1024, out: [128, 8] }, { in: 1025, out: [129, 8] }, { in: 4048, out: [208, 31] }, { in: 5050, out: [186, 39] }, { in: 1000000, out: [192, 132, 61] }, { in: 34951959, out: [151, 166, 213, 16] }, { in: 2147483646, out: [254, 255, 255, 255, 7] }, { in: 2147483647, out: [255, 255, 255, 255, 7] }, { in: 2147483648, out: [128, 128, 128, 128, 8] }, { in: 2147483700, out: [180, 128, 128, 128, 8] }, { in: 4294967294, out: [254, 255, 255, 255, 15] }, { in: 4294967295, out: [255, 255, 255, 255, 15] } ] tests.forEach(test => { const encoder = encoding.createEncoder() encoding.writeVarUint(encoder, test.in) const buffer = encoding.toUint8Array(encoder) t.assert(buffer.byteLength === test.out.length) t.assert(buffer.length > 0) t.assert(encoding.hasContent(encoder)) for (let j = 0; j < buffer.length; j++) { t.assert(buffer[j] === test.out[j]) } }) } /** * @template T * @param {string} testname * @param {function(encoding.Encoder, T):void} write * @param {function(decoding.Decoder):T} read * @param {T} val * @param {boolean} doLog */ function test (testname, write, read, val, doLog = true) { const encoder = encoding.createEncoder() write(encoder, val) const buffer = encoding.toUint8Array(encoder) t.assert((buffer.length > 0) === encoding.hasContent(encoder)) const reader = decoding.createDecoder(buffer) const result = read(reader) const utf8ByteLength = string.utf8ByteLength(val + '') const binaryByteLength = encoding.length(encoder) if (doLog) { t.describe(testname, ` utf8 encode: ${utf8ByteLength} bytes / binary encode: ${binaryByteLength} bytes`) } t.compare(val, result) return { utf8ByteLength, binaryByteLength } } /** * @param {string} s */ const testVarString = s => { const decoder = decoding.createDecoder(encoding.encode(encoder => { encoding.writeVarString(encoder, s) })) const peeked = decoding.peekVarString(decoder) const result = decoding.readVarString(decoder) t.compareStrings(s, result) t.compareStrings(s, peeked) } export const testVerifyLen = () => { const encoder = encoding.createEncoder() const vLen = encoder.cbuf.length + 1 const bufsLen = encoder.bufs.length encoding.verifyLen(encoder, vLen) t.assert(encoder.cbuf.length >= vLen) t.assert(encoder.bufs.length >= bufsLen) t.assert(encoding.hasContent(encoder)) } export const testStringEncodingPerformanceNativeVsPolyfill = () => { const largeRepetitions = 20 let bigstr = '' for (let i = 0; i < 10000; i++) { bigstr += i } const customTime = t.measureTime('large dataset: custom encoding', () => { const encoder = encoding.createEncoder() for (let i = 0; i < largeRepetitions; i++) { encoding._writeVarStringPolyfill(encoder, 'i') encoding._writeVarStringPolyfill(encoder, bigstr) } }) const nativeTime = t.measureTime('large dataset: native encoding', () => { const encoder = encoding.createEncoder() for (let i = 0; i < largeRepetitions; i++) { encoding._writeVarStringNative(encoder, 'i') encoding._writeVarStringNative(encoder, bigstr) } }) t.assert(nativeTime < customTime, 'We expect native encoding to be more performant for large data sets') const smallRepetitions = 100000 const customTimeSmall = t.measureTime('small dataset: custom encoding', () => { const encoder = encoding.createEncoder() for (let i = 0; i < smallRepetitions; i++) { encoding._writeVarStringPolyfill(encoder, 'i') encoding._writeVarStringPolyfill(encoder, 'bb') encoding._writeVarStringPolyfill(encoder, 'ccc') } }) const nativeTimeSmall = t.measureTime('small dataset: native encoding', () => { const encoder = encoding.createEncoder() for (let i = 0; i < smallRepetitions; i++) { encoding._writeVarStringNative(encoder, 'i') encoding._writeVarStringNative(encoder, 'bb') encoding._writeVarStringNative(encoder, 'ccc') } }) console.log({ nativeTimeSmall, customTimeSmall }) // @todo we should check that we use custom encoding for small datasets t.assert(nativeTimeSmall < customTimeSmall * 5, 'We expect native encoding to be not much worse than custom encoding for small data sets') } export const testDecodingPerformanceNativeVsPolyfill = () => { const iterationsSmall = 10000 const iterationsLarge = 1000 let bigstr = '' for (let i = 0; i < 10000; i++) { bigstr += i } const encoder = encoding.createEncoder() const encoderLarge = encoding.createEncoder() for (let i = 0; i < iterationsSmall; i++) { encoding.writeVarString(encoder, 'i') encoding.writeVarString(encoder, 'bb') encoding.writeVarString(encoder, 'ccc') } for (let i = 0; i < iterationsLarge; i++) { encoding.writeVarString(encoderLarge, bigstr) } const buf = encoding.toUint8Array(encoder) const bufLarge = encoding.toUint8Array(encoderLarge) const nativeTimeSmall = t.measureTime('small dataset: native encoding', () => { const decoder = decoding.createDecoder(buf) while (decoding.hasContent(decoder)) { decoding._readVarStringNative(decoder) } }) const polyfillTimeSmall = t.measureTime('small dataset: polyfill encoding', () => { const decoder = decoding.createDecoder(buf) while (decoding.hasContent(decoder)) { decoding.readVarString(decoder) } }) const nativeTimeLarge = t.measureTime('large dataset: native encoding', () => { const decoder = decoding.createDecoder(bufLarge) while (decoding.hasContent(decoder)) { decoding._readVarStringNative(decoder) } }) const polyfillTimeLarge = t.measureTime('large dataset: polyfill encoding', () => { const decoder = decoding.createDecoder(bufLarge) while (decoding.hasContent(decoder)) { decoding._readVarStringPolyfill(decoder) } }) // @todo We should switch to native decoding! console.log({ nativeTimeSmall, polyfillTimeSmall }) t.assert(nativeTimeSmall < polyfillTimeSmall * 2.0, 'Small dataset: We expect native decoding to be not much worse than') t.assert(nativeTimeLarge < polyfillTimeLarge * 1.5, 'Large dataset: We expect native decoding to be much better than polyfill decoding') } export const testStringDecodingPerformance = () => { // test if it is faster to read N single characters, or if it is faster to read N characters in one flush. // to make the comparison meaningful, we read read N characters in an Array const N = 2000000 const durationSingleElements = t.measureTime('read / write single elements', () => { const encoder = encoding.createEncoder() t.measureTime('read / write single elements - write', () => { for (let i = 0; i < N; i++) { encoding.writeVarString(encoder, 'i') } }) const decoder = decoding.createDecoder(encoding.toUint8Array(encoder)) t.measureTime('read / write single elements - read', () => { const arr = [] for (let i = 0; i < N; i++) { arr.push(decoding.readVarString(decoder)) } }) }) const durationConcatElements = t.measureTime('read / write concatenated string', () => { let stringbuf = new Uint8Array() const encoder = encoding.createEncoder() const encoderLengths = encoding.createEncoder() t.measureTime('read / write concatenated string - write', () => { let s = '' for (let i = 0; i < N; i++) { s += 'i' encoding.writeVarUint(encoderLengths, 1) // we write a single char. if (i % 20 === 0) { encoding.writeVarString(encoder, s) s = '' } } encoding.writeVarString(encoder, s) stringbuf = encoding.toUint8Array(encoder) }) const decoder = decoding.createDecoder(stringbuf) const decoderLengths = decoding.createDecoder(encoding.toUint8Array(encoderLengths)) t.measureTime('read / write concatenated string - read', () => { const arr = [] const concatS = decoding.readVarString(decoder) for (let i = 0; i < N; i++) { const len = decoding.readVarUint(decoderLengths) arr.push(concatS.slice(i, len)) // push using slice } }) }) t.assert(durationConcatElements < 2 * durationSingleElements, 'We expect that the second approach is faster. If this fails, our expectantion is not met in your javascript environment. Please report this issue.') } /** * @param {t.TestCase} _tc */ export const testAnyEncodeUnknowns = _tc => { const encoder = encoding.createEncoder() // @ts-ignore encoding.writeAny(encoder, Symbol('a')) encoding.writeAny(encoder, undefined) // @ts-ignore encoding.writeAny(encoder, () => {}) const decoder = decoding.createDecoder(encoding.toUint8Array(encoder)) t.assert(decoding.readAny(decoder) === undefined) t.assert(decoding.readAny(decoder) === undefined) t.assert(decoding.readAny(decoder) === undefined) } /** * @param {t.TestCase} _tc */ export const testAnyEncodeDate = _tc => { test('Encode current date', encoding.writeAny, decoding.readAny, new Date().getTime()) } /** * @param {t.TestCase} _tc */ export const testEncodeMax32bitUint = _tc => { test('max 32bit uint', encoding.writeVarUint, decoding.readVarUint, binary.BITS32) } /** * @param {t.TestCase} _tc */ export const testVarUintEncoding = _tc => { test('varUint 1 byte', encoding.writeVarUint, decoding.readVarUint, 42) test('varUint 2 bytes', encoding.writeVarUint, decoding.readVarUint, 1 << 9 | 3) test('varUint 3 bytes', encoding.writeVarUint, decoding.readVarUint, 1 << 17 | 1 << 9 | 3) test('varUint 4 bytes', encoding.writeVarUint, decoding.readVarUint, 1 << 25 | 1 << 17 | 1 << 9 | 3) test('varUint of 2839012934', encoding.writeVarUint, decoding.readVarUint, 2839012934) test('varUint of 2^53', encoding.writeVarUint, decoding.readVarUint, number.MAX_SAFE_INTEGER) } /** * @param {t.TestCase} _tc */ export const testVarIntEncoding = _tc => { test('varInt 1 byte', encoding.writeVarInt, decoding.readVarInt, -42) test('varInt 2 bytes', encoding.writeVarInt, decoding.readVarInt, -(1 << 9 | 3)) test('varInt 3 bytes', encoding.writeVarInt, decoding.readVarInt, -(1 << 17 | 1 << 9 | 3)) test('varInt 4 bytes', encoding.writeVarInt, decoding.readVarInt, -(1 << 25 | 1 << 17 | 1 << 9 | 3)) test('varInt of -691529286', encoding.writeVarInt, decoding.readVarInt, -(691529286)) test('varInt of 2^53', encoding.writeVarInt, decoding.readVarInt, number.MAX_SAFE_INTEGER) test('varInt of -2^53', encoding.writeVarInt, decoding.readVarInt, number.MIN_SAFE_INTEGER) } /** * @param {t.TestCase} tc */ export const testRepeatVarUintEncoding = tc => { const n = prng.uint32(tc.prng, 0, (1 << 28) - 1) test(`varUint of ${n}`, encoding.writeVarUint, decoding.readVarUint, n, false) } /** * @param {t.TestCase} tc */ export const testRepeatVarUintEncoding53bit = tc => { const n = prng.uint53(tc.prng, 0, number.MAX_SAFE_INTEGER) test(`varUint of ${n}`, encoding.writeVarUint, decoding.readVarUint, n, false) } /** * @param {t.TestCase} tc */ export const testRepeatVarIntEncoding = tc => { const n = prng.int32(tc.prng, number.LOWEST_INT32, binary.BITS32) test(`varInt of ${n}`, encoding.writeVarInt, decoding.readVarInt, n, false) } /** * @param {t.TestCase} tc */ export const testRepeatVarIntEncoding53bit = tc => { const n = prng.int32(tc.prng, number.MIN_SAFE_INTEGER, number.MAX_SAFE_INTEGER) test(`varInt of ${n}`, encoding.writeVarInt, decoding.readVarInt, n, false) } /** * @param {t.TestCase} tc */ export const testRepeanntAnyEncoding = tc => { const n = genAny(tc.prng) test('any encoding', encoding.writeAny, decoding.readAny, n, false) } /** * @param {t.TestCase} tc */ export const testRepeatPeekVarUintEncoding = tc => { const n = prng.int32(tc.prng, 0, (1 << 28) - 1) test(`varUint of ${n}`, encoding.writeVarUint, decoding.peekVarUint, n, false) } /** * @param {t.TestCase} tc */ export const testRepeatPeekVarIntEncoding = tc => { const n = prng.int53(tc.prng, number.MIN_SAFE_INTEGER, number.MAX_SAFE_INTEGER) test(`varInt of ${n}`, encoding.writeVarInt, decoding.peekVarInt, n, false) } /** * @param {t.TestCase} tc */ export const testAnyVsJsonEncoding = tc => { const n = Array.from({ length: 5000 }).map(() => genAny(tc.prng, 5, true)) t.measureTime('lib0 any encoding', () => { const encoder = encoding.createEncoder() encoding.writeAny(encoder, n) const buffer = encoding.toUint8Array(encoder) t.info('buffer length is ' + buffer.length) decoding.readAny(decoding.createDecoder(buffer)) }) t.measureTime('JSON.stringify encoding', () => { const encoder = encoding.createEncoder() encoding.writeVarString(encoder, JSON.stringify(n)) const buffer = encoding.toUint8Array(encoder) t.info('buffer length is ' + buffer.length) JSON.parse(decoding.readVarString(decoding.createDecoder(buffer))) }) } /** * @param {t.TestCase} _tc */ export const testStringEncoding = _tc => { testVarString('hello') testVarString('test!') testVarString('☺☺☺') testVarString('') testVarString('1234') testVarString('쾟') testVarString('龟') // surrogate length 3 testVarString('😝') // surrogate length 4 } /** * @param {t.TestCase} tc */ export const testRepeatStringEncoding = tc => testVarString(prng.utf16String(tc.prng)) /** * @param {t.TestCase} _tc */ export const testSetMethods = _tc => { const encoder = encoding.createEncoder() encoding.writeUint8(encoder, 1) encoding.writeUint16(encoder, 33) encoding.writeUint32(encoder, 29329) encoding.setUint8(encoder, 0, 8) encoding.setUint16(encoder, 1, 16) encoding.setUint32(encoder, 3, 32) const buf = encoding.toUint8Array(encoder) const decoder = decoding.createDecoder(buf) t.assert(decoding.peekUint8(decoder) === 8) decoding.readUint8(decoder) t.assert(decoding.peekUint16(decoder) === 16) decoding.readUint16(decoder) t.assert(decoding.peekUint32(decoder) === 32) decoding.readUint32(decoder) } const defLen = 1000 const loops = 10000 /** * @param {any} a * @param {any} b * @return {boolean} */ const strictComparison = (a, b) => a === b /** * @typedef {Object} EncodingPair * @property {function(decoding.Decoder):any} EncodingPair.read * @property {function(encoding.Encoder,any):void} EncodingPair.write * @property {function(prng.PRNG):any} EncodingPair.gen * @property {function(any,any):boolean} EncodingPair.compare * @property {string} name */ /** * @template T * @type {Array} */ const encodingPairs = [ { name: 'uint8Array', read: decoder => decoding.readUint8Array(decoder, defLen), write: encoding.writeUint8Array, gen: gen => prng.uint8Array(gen, defLen), compare: t.compare }, { name: 'varUint8Array', read: decoding.readVarUint8Array, write: encoding.writeVarUint8Array, gen: gen => prng.uint8Array(gen, prng.uint32(gen, 0, defLen)), compare: t.compare }, { name: 'uint8', read: decoding.readUint8, write: encoding.writeUint8, gen: gen => prng.uint32(gen, 0, binary.BITS8), compare: strictComparison }, { name: 'uint16', read: decoding.readUint16, write: encoding.writeUint16, gen: gen => prng.uint32(gen, 0, binary.BITS16), compare: strictComparison }, { name: 'uint32', read: decoding.readUint32, write: encoding.writeUint32, gen: gen => prng.uint32(gen, 0, binary.BITS32), compare: strictComparison }, { name: 'uint32bigEndian', read: decoding.readUint32BigEndian, write: encoding.writeUint32BigEndian, gen: gen => prng.uint32(gen, 0, binary.BITS32), compare: strictComparison }, { name: 'varString', read: decoding.readVarString, write: encoding.writeVarString, gen: gen => prng.utf16String(gen, prng.uint32(gen, 0, defLen)), compare: strictComparison }, { name: 'varUint', read: decoding.readVarUint, write: encoding.writeVarUint, gen: gen => prng.uint53(gen, 0, number.MAX_SAFE_INTEGER), compare: strictComparison }, { name: 'varInt', read: decoding.readVarInt, write: encoding.writeVarInt, gen: gen => prng.int53(gen, number.MIN_SAFE_INTEGER, number.MAX_SAFE_INTEGER), compare: strictComparison }, { name: 'Any', read: decoding.readAny, write: encoding.writeAny, gen: genAny, compare: t.compare } ] /** * @param {t.TestCase} tc */ export const testRepeatRandomWrites = tc => { t.describe(`Writing ${loops} random values`, `defLen=${defLen}`) const gen = tc.prng /** * @type {any} */ const ops = [] const encoder = encoding.createEncoder() for (let i = 0; i < 10000; i++) { const pair = prng.oneOf(gen, encodingPairs) const val = pair.gen(gen) pair.write(encoder, val) ops.push({ compare: pair.compare, read: pair.read, val, name: pair.name }) } const tailData = prng.uint8Array(gen, prng.int32(gen, 0, defLen)) encoding.writeUint8Array(encoder, tailData) const buf = encoding.toUint8Array(encoder) const decoder = decoding.createDecoder(buf) t.assert(encoding.length(encoder) === buf.byteLength) for (let i = 0; i < ops.length; i++) { const o = ops[i] const val = o.read(decoder) t.assert(o.compare(val, o.val), o.name) } t.compare(tailData, decoding.readTailAsUint8Array(decoder)) } /** * @param {t.TestCase} _tc */ export const testWriteUint8ArrayOverflow = _tc => { const encoder = encoding.createEncoder() const initialLen = encoder.cbuf.byteLength const buf = buffer.createUint8ArrayFromLen(initialLen * 4) for (let i = 0; i < buf.length; i++) { buf[i] = i } encoding.writeUint8Array(encoder, buf) encoding.write(encoder, 42) const res = encoding.toUint8Array(encoder) t.assert(res.length === initialLen * 4 + 1) for (let i = 0; i < buf.length - 1; i++) { t.assert(res[i] === (i % 256)) } t.assert(res[initialLen * 4] === 42) } /** * @param {t.TestCase} _tc */ export const testSetOnOverflow = _tc => { const encoder = encoding.createEncoder() const initialLen = encoder.cbuf.byteLength encoder.cpos = initialLen - 2 encoding.writeUint32(encoder, binary.BITS32) const buf = encoding.toUint8Array(encoder) t.assert(encoding.length(encoder) === initialLen + 2) const decoder = decoding.createDecoder(buf) const space = buffer.createUint8ArrayFromArrayBuffer(decoding.readUint8Array(decoder, initialLen - 2).buffer) for (let i = 0; i < initialLen - 2; i++) { t.assert(space[i] === 0) } t.assert(decoding.hasContent(decoder)) t.assert(binary.BITS32 === decoding.readUint32(decoder)) t.assert(!decoding.hasContent(decoder)) encoding.setUint8(encoder, 5, binary.BITS8) encoding.setUint8(encoder, initialLen + 1, 7) const buf2 = encoding.toUint8Array(encoder) t.assert(buf2[5] === binary.BITS8) t.assert(buf[5] === 0, 'old buffer is not affected') t.assert(buf2[initialLen + 1] === 7) } /** * @param {t.TestCase} _tc */ export const testCloneDecoder = _tc => { const encoder = encoding.createEncoder() encoding.writeUint8(encoder, 12132) encoding.writeVarUint(encoder, 329840128734) encoding.writeVarString(encoder, 'dtrnuiaednudiaendturinaedt nduiaen dturinaed ') const buf = encoding.toUint8Array(encoder) const decoder = decoding.createDecoder(buf) decoding.skip8(decoder) const decoder2 = decoding.clone(decoder) const payload1 = decoding.readTailAsUint8Array(decoder) const payload2 = decoding.readTailAsUint8Array(decoder2) t.compare(payload1, payload2) } /** * @param {t.TestCase} _tc */ export const testWriteBinaryEncoder = _tc => { const encoder = encoding.createEncoder() encoding.writeUint16(encoder, 4) const encoder2 = encoding.createEncoder() encoding.writeVarUint(encoder2, 143095) encoding.writeBinaryEncoder(encoder2, encoder) const buf = encoding.toUint8Array(encoder2) const decoder = decoding.createDecoder(buf) t.assert(decoding.readVarUint(decoder) === 143095) t.assert(decoding.readUint16(decoder) === 4) } /** * @param {t.TestCase} tc */ export const testOverflowStringDecoding = tc => { const gen = tc.prng const encoder = encoding.createEncoder() let longStr = '' while (longStr.length < 11000) { longStr += prng.utf16String(gen, 100000) } encoding.writeVarString(encoder, longStr) const buf = encoding.toUint8Array(encoder) const decoder = decoding.createDecoder(buf) t.assert(longStr === decoding.readVarString(decoder)) } /** * @param {t.TestCase} _tc */ export const testRleEncoder = _tc => { const N = 100 const encoder = new encoding.RleEncoder(encoding.writeVarUint) for (let i = 0; i < N; i++) { encoder.write(i) for (let j = 0; j < i; j++) { // write additional i times encoder.write(i) } } const decoder = new decoding.RleDecoder(encoding.toUint8Array(encoder), decoding.readVarUint) for (let i = 0; i < N; i++) { t.assert(i === decoder.read()) for (let j = 0; j < i; j++) { // read additional i times t.assert(i === decoder.read()) } } } /** * @param {t.TestCase} _tc */ export const testRleIntDiffEncoder = _tc => { const N = 100 const encoder = new encoding.RleIntDiffEncoder(0) for (let i = -N; i < N; i++) { encoder.write(i) for (let j = 0; j < i; j++) { // write additional i times encoder.write(i) } } const decoder = new decoding.RleIntDiffDecoder(encoding.toUint8Array(encoder), 0) for (let i = -N; i < N; i++) { t.assert(i === decoder.read()) for (let j = 0; j < i; j++) { // read additional i times t.assert(i === decoder.read()) } } } /** * @param {t.TestCase} _tc */ export const testUintOptRleEncoder = _tc => { const N = 100 const encoder = new encoding.UintOptRleEncoder() for (let i = 0; i < N; i++) { encoder.write(i) for (let j = 0; j < i; j++) { // write additional i times encoder.write(i) } } const decoder = new decoding.UintOptRleDecoder(encoder.toUint8Array()) for (let i = 0; i < N; i++) { t.assert(i === decoder.read()) for (let j = 0; j < i; j++) { // read additional i times t.assert(i === decoder.read()) } } } /** * @param {t.TestCase} _tc */ export const testIncUintOptRleEncoder = _tc => { const N = 100 const encoder = new encoding.IncUintOptRleEncoder() for (let i = 0; i < N; i++) { encoder.write(i) for (let j = 0; j < i; j++) { // write additional i times encoder.write(i) } } const decoder = new decoding.IncUintOptRleDecoder(encoder.toUint8Array()) for (let i = 0; i < N; i++) { t.assert(i === decoder.read()) for (let j = 0; j < i; j++) { // read additional i times t.assert(i === decoder.read()) } } } /** * @param {t.TestCase} _tc */ export const testIntDiffRleEncoder = _tc => { const N = 100 const encoder = new encoding.IntDiffOptRleEncoder() for (let i = -N; i < N; i++) { encoder.write(i) for (let j = 0; j < i; j++) { // write additional i times encoder.write(i) } } const decoder = new decoding.IntDiffOptRleDecoder(encoder.toUint8Array()) for (let i = -N; i < N; i++) { t.assert(i === decoder.read()) for (let j = 0; j < i; j++) { // read additional i times t.assert(i === decoder.read()) } } } /** * @param {t.TestCase} tc */ export const testIntEncoders = tc => { const arrLen = 10000 const gen = tc.prng /** * @type {Array} */ const vals = [] for (let i = 0; i < arrLen; i++) { if (prng.bool(gen)) { vals.push(prng.int53(gen, math.floor(number.MIN_SAFE_INTEGER / 2), math.floor(number.MAX_SAFE_INTEGER / 2))) } else { vals.push(prng.int32(gen, -10, 10)) } } /** * @type {Array<{ encoder: any, read: function(any):any }>} */ const intEncoders = [ { encoder: new encoding.IntDiffOptRleEncoder(), read: encoder => new decoding.IntDiffOptRleDecoder(encoder.toUint8Array()) }, { encoder: new encoding.IntDiffEncoder(0), read: encoder => new decoding.IntDiffDecoder(encoding.toUint8Array(encoder), 0) }, { encoder: new encoding.IntDiffEncoder(42), read: encoder => new decoding.IntDiffDecoder(encoding.toUint8Array(encoder), 42) }, { encoder: new encoding.RleIntDiffEncoder(0), read: encoder => new decoding.RleIntDiffDecoder(encoding.toUint8Array(encoder), 0) } ] intEncoders.forEach(({ encoder, read }) => { vals.forEach(v => encoder.write(v)) /** * @type {Array} */ const readVals = [] const dec = read(encoder) for (let i = 0; i < arrLen; i++) { readVals.push(dec.read()) } t.compare(vals, readVals) }) } /** * @param {t.TestCase} _tc */ export const testIntDiffEncoder = _tc => { const N = 100 const encoder = new encoding.IntDiffEncoder(0) for (let i = -N; i < N; i++) { encoder.write(i) } const decoder = new decoding.IntDiffDecoder(encoding.toUint8Array(encoder), 0) for (let i = -N; i < N; i++) { t.assert(i === decoder.read()) } } /** * @param {t.TestCase} tc */ export const testStringDecoder = tc => { const gen = tc.prng const N = 1000 const words = [] for (let i = 0; i < N; i++) { words.push(prng.utf16String(gen)) if (i % 100 === 0) { const char = prng.char(gen).slice(0, 1) words.push(char) words.push(char) } if (i % 107 === 0) { words.push(prng.word(gen, 3000, 8000)) } } const encoder = new encoding.StringEncoder() for (let i = 0; i < words.length; i++) { encoder.write(words[i]) } const decoder = new decoding.StringDecoder(encoder.toUint8Array()) for (let i = 0; i < words.length; i++) { t.assert(decoder.read() === words[i]) } } /** * @param {t.TestCase} tc */ export const testLargeNumberEncoding = tc => { const encoder = encoding.createEncoder() const num1 = -2.2062063918362897e+50 const num2 = BigInt(prng.int53(tc.prng, number.MIN_SAFE_INTEGER, number.MAX_SAFE_INTEGER)) const num3 = BigInt(prng.uint53(tc.prng, 0, number.MAX_SAFE_INTEGER)) const num4 = prng.real53(tc.prng) const num5 = 0.5 encoding.writeAny(encoder, num1) encoding.writeBigInt64(encoder, num2) encoding.writeBigUint64(encoder, num3) encoding.writeFloat64(encoder, num4) encoding.writeFloat32(encoder, num5) const decoder = decoding.createDecoder(encoding.toUint8Array(encoder)) const readNum1 = decoding.readAny(decoder) t.assert(readNum1 === num1) const readNum2 = decoding.readBigInt64(decoder) t.assert(readNum2 === num2) const readNum3 = decoding.readBigUint64(decoder) t.assert(readNum3 === num3) const readNum4 = decoding.readFloat64(decoder) t.assert(readNum4 === num4) const readNum5 = decoding.readFloat32(decoder) t.assert(readNum5 === num5) } /** * @param {t.TestCase} _tc */ export const testInvalidVarIntEncoding = _tc => { const encoded = new Uint8Array(1) encoded[0] = 255 const decoder = decoding.createDecoder(encoded) t.fails(() => { decoding.readVarInt(decoder) }) decoder.pos = 0 t.fails(() => { decoding.readVarUint(decoder) }) } /** * @param {t.TestCase} _tc */ export const testTerminatedEncodering = _tc => { const str1 = 'basic test' const str2 = 'hello\0world' // can handle escaped sequences in string const buf1 = new Uint8Array([0, 1, 2, 255, 4, 5]) const buf2 = new Uint8Array([255, 255, 0, 0, 0, 1, 0, 0]) const encoder = encoding.createEncoder() encoding.writeTerminatedString(encoder, str1) encoding.writeTerminatedString(encoder, str2) encoding.writeTerminatedUint8Array(encoder, buf1) encoding.writeTerminatedUint8Array(encoder, buf2) const decoder = decoding.createDecoder(encoding.toUint8Array(encoder)) const readStr1 = decoding.readTerminatedString(decoder) const readStr2 = decoding.readTerminatedString(decoder) const readBuf1 = decoding.readTerminatedUint8Array(decoder) const readBuf2 = decoding.readTerminatedUint8Array(decoder) t.assert(readStr1 === str1) t.assert(readStr2 === str2) t.compare(readBuf1, buf1) t.compare(readBuf2, buf2) } dmonad-lib0-c7e7806/environment.js000066400000000000000000000101641512504553500170560ustar00rootroot00000000000000/** * Isomorphic module to work access the environment (query params, env variables). * * @module environment */ import * as map from './map.js' import * as string from './string.js' import * as conditions from './conditions.js' import * as storage from './storage.js' import * as f from './function.js' /* c8 ignore next 2 */ // @ts-ignore export const isNode = typeof process !== 'undefined' && process.release && /node|io\.js/.test(process.release.name) && Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]' /* c8 ignore next */ export const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined' && !isNode /* c8 ignore next 3 */ export const isMac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false /** * @type {Map} */ let params const args = [] /* c8 ignore start */ const computeParams = () => { if (params === undefined) { if (isNode) { params = map.create() 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 } else { args.push(parg) } } } 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 = map.create(); // eslint-disable-next-line no-undef (location.search || '?').slice(1).split('&').forEach((kv) => { if (kv.length !== 0) { const [key, value] = kv.split('=') params.set(`--${string.fromCamelCase(key, '-')}`, value) params.set(`-${string.fromCamelCase(key, '-')}`, value) } }) } else { params = map.create() } } return params } /* c8 ignore stop */ /** * @param {string} name * @return {boolean} */ /* c8 ignore next */ export const hasParam = (name) => computeParams().has(name) /** * @param {string} name * @param {string} defaultVal * @return {string} */ /* c8 ignore next 2 */ export const getParam = (name, defaultVal) => computeParams().get(name) || defaultVal /** * @param {string} name * @return {string|null} */ /* c8 ignore next 4 */ export const getVariable = (name) => isNode ? conditions.undefinedToNull(process.env[name.toUpperCase().replaceAll('-', '_')]) : conditions.undefinedToNull(storage.varStorage.getItem(name)) /** * @param {string} name * @return {string|null} */ /* c8 ignore next 2 */ export const getConf = (name) => computeParams().get('--' + name) || getVariable(name) /** * @param {string} name * @return {string} */ /* c8 ignore next 5 */ export const ensureConf = (name) => { const c = getConf(name) if (c == null) throw new Error(`Expected configuration "${name.toUpperCase().replaceAll('-', '_')}"`) return c } /** * @param {string} name * @return {boolean} */ /* c8 ignore next 2 */ export const hasConf = (name) => hasParam('--' + name) || getVariable(name) !== null /* c8 ignore next */ export const production = hasConf('production') /* c8 ignore next 2 */ const forceColor = isNode && f.isOneOf(process.env.FORCE_COLOR, ['true', '1', '2']) /* c8 ignore start */ /** * Color is enabled by default if the terminal supports it. * * Explicitly enable color using `--color` parameter * Disable color using `--no-color` parameter or using `NO_COLOR=1` environment variable. * `FORCE_COLOR=1` enables color and takes precedence over all. */ export const supportsColor = forceColor || ( !hasParam('--no-colors') && // @todo deprecate --no-colors !hasConf('no-color') && (!isNode || process.stdout.isTTY) && ( !isNode || hasParam('--color') || getVariable('COLORTERM') !== null || (getVariable('TERM') || '').includes('color') ) ) /* c8 ignore stop */ dmonad-lib0-c7e7806/error.js000066400000000000000000000011451512504553500156420ustar00rootroot00000000000000/** * Error helpers. * * @module error */ /** * @param {string} s * @return {Error} */ /* c8 ignore next */ export const create = s => new Error(s) /** * @throws {Error} * @return {never} */ /* c8 ignore next 3 */ export const methodUnimplemented = () => { throw create('Method unimplemented') } /** * @throws {Error} * @return {never} */ /* c8 ignore next 3 */ export const unexpectedCase = () => { throw create('Unexpected case') } /** * @param {boolean} property * @return {asserts property is true} */ export const assert = property => { if (!property) throw create('Assert failed') } dmonad-lib0-c7e7806/eventloop.js000066400000000000000000000061231512504553500165250ustar00rootroot00000000000000/* global requestIdleCallback, requestAnimationFrame, cancelIdleCallback, cancelAnimationFrame */ import * as time from './time.js' /** * Utility module to work with EcmaScript's event loop. * * @module eventloop */ /** * @type {Array} */ let queue = [] const _runQueue = () => { for (let i = 0; i < queue.length; i++) { queue[i]() } queue = [] } /** * @param {function():void} f */ export const enqueue = f => { queue.push(f) if (queue.length === 1) { setTimeout(_runQueue, 0) } } /** * @typedef {Object} TimeoutObject * @property {function} TimeoutObject.destroy */ /** * @param {function(number):void} clearFunction */ const createTimeoutClass = clearFunction => class TT { /** * @param {number} timeoutId */ constructor (timeoutId) { this._ = timeoutId } destroy () { clearFunction(this._) } } const Timeout = createTimeoutClass(clearTimeout) /** * @param {number} timeout * @param {function} callback * @return {TimeoutObject} */ export const timeout = (timeout, callback) => new Timeout(setTimeout(callback, timeout)) const Interval = createTimeoutClass(clearInterval) /** * @param {number} timeout * @param {function} callback * @return {TimeoutObject} */ export const interval = (timeout, callback) => new Interval(setInterval(callback, timeout)) /* c8 ignore next */ export const Animation = createTimeoutClass(arg => typeof requestAnimationFrame !== 'undefined' && cancelAnimationFrame(arg)) /** * @param {function(number):void} cb * @return {TimeoutObject} */ /* c8 ignore next */ export const animationFrame = cb => typeof requestAnimationFrame === 'undefined' ? timeout(0, cb) : new Animation(requestAnimationFrame(cb)) /* c8 ignore next */ // @ts-ignore const Idle = createTimeoutClass(arg => typeof cancelIdleCallback !== 'undefined' && cancelIdleCallback(arg)) /** * Note: this is experimental and is probably only useful in browsers. * * @param {function} cb * @return {TimeoutObject} */ /* c8 ignore next 2 */ // @ts-ignore export const idleCallback = cb => typeof requestIdleCallback !== 'undefined' ? new Idle(requestIdleCallback(cb)) : timeout(1000, cb) /** * @param {number} timeout Timeout of the debounce action * @param {number} triggerAfter Optional. Trigger callback after a certain amount of time * without waiting for debounce. */ export const createDebouncer = (timeout, triggerAfter = -1) => { let timer = -1 /** * @type {number?} */ let lastCall = null /** * @param {((...args: any)=>void)?} cb function to trigger after debounce. If null, it will reset the * debounce. */ return cb => { clearTimeout(timer) if (cb) { if (triggerAfter >= 0) { const now = time.getUnixTime() if (lastCall === null) lastCall = now if (now - lastCall > triggerAfter) { lastCall = null timer = /** @type {any} */ (setTimeout(cb, 0)) return } } timer = /** @type {any} */ (setTimeout(() => { lastCall = null; cb() }, timeout)) } else { lastCall = null } } } dmonad-lib0-c7e7806/eventloop.test.js000066400000000000000000000053761512504553500175140ustar00rootroot00000000000000import * as eventloop from './eventloop.js' import * as t from './testing.js' import * as promise from './promise.js' /** * @param {t.TestCase} _tc */ export const testEventloopOrder = _tc => { let currI = 0 for (let i = 0; i < 10; i++) { const bi = i eventloop.enqueue(() => { t.assert(currI++ === bi) }) } eventloop.enqueue(() => { t.assert(currI === 10) }) t.assert(currI === 0) return promise.all([ promise.createEmpty(resolve => eventloop.enqueue(resolve)), promise.until(0, () => currI === 10) ]) } /** * @param {t.TestCase} _tc */ export const testTimeout = async _tc => { let set = false const timeout = eventloop.timeout(0, () => { set = true }) timeout.destroy() await promise.create(resolve => { eventloop.timeout(10, resolve) }) t.assert(set === false) } /** * @param {t.TestCase} _tc */ export const testInterval = async _tc => { let set = false const timeout = eventloop.interval(1, () => { set = true }) timeout.destroy() let i = 0 eventloop.interval(1, () => { i++ }) await promise.until(0, () => i > 2) t.assert(set === false) t.assert(i > 1) } /** * @param {t.TestCase} _tc */ export const testAnimationFrame = async _tc => { let x = false eventloop.animationFrame(() => { x = true }) await promise.until(0, () => x) t.assert(x) } /** * @param {t.TestCase} _tc */ export const testIdleCallback = async _tc => { await promise.create(resolve => { eventloop.idleCallback(resolve) }) } /** * @param {t.TestCase} _tc */ export const testDebouncer = async _tc => { const debounce = eventloop.createDebouncer(10) let calls = 0 debounce((_x) => { calls++ }) debounce((_y, _z) => { calls++ }) t.assert(calls === 0) await promise.wait(20) t.assert(calls === 1) } /** * @param {t.TestCase} _tc */ export const testDebouncerTriggerAfter = async _tc => { const debounce = eventloop.createDebouncer(100, 100) let calls = 0 debounce(() => { calls++ }) await promise.wait(40) debounce(() => { calls++ }) await promise.wait(30) debounce(() => { calls++ }) await promise.wait(50) debounce(() => { calls++ }) t.assert(calls === 0) await promise.wait(0) t.assert(calls === 1) await promise.wait(30) t.assert(calls === 1) } /** * @param {t.TestCase} _tc */ export const testDebouncerClear = async _tc => { const debounce = eventloop.createDebouncer(10) let calls = 0 debounce(() => { calls++ }) await promise.wait(5) debounce(() => { calls++ }) await promise.wait(5) debounce(() => { calls++ }) await promise.wait(5) debounce(() => { calls++ }) await promise.wait(5) debounce(null) t.assert(calls === 0) await promise.wait(30) t.assert(calls === 0) } dmonad-lib0-c7e7806/function.js000066400000000000000000000074751512504553500163520ustar00rootroot00000000000000/** * Common functions and function call helpers. * * @module function */ import * as array from './array.js' import * as object from './object.js' import * as equalityTrait from './trait/equality.js' /** * Calls all functions in `fs` with args. Only throws after all functions were called. * * @param {Array} fs * @param {Array} args */ export 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) } } } export const nop = () => {} /** * @template T * @param {function():T} f * @return {T} */ export const apply = f => f() /** * @template A * * @param {A} a * @return {A} */ export const id = a => a /** * @template T * * @param {T} a * @param {T} b * @return {boolean} */ export const equalityStrict = (a, b) => a === b /** * @template T * * @param {Array|object} a * @param {Array|object} b * @return {boolean} */ export const equalityFlat = (a, b) => a === b || (a != null && b != null && a.constructor === b.constructor && ((array.isArray(a) && array.equalFlat(a, /** @type {Array} */ (b))) || (typeof a === 'object' && object.equalFlat(a, b)))) /* c8 ignore start */ /** * @param {any} a * @param {any} b * @return {boolean} */ export const equalityDeep = (a, b) => { if (a === b) { return true } if (a == null || b == null || (a.constructor !== b.constructor && (a.constructor || Object) !== (b.constructor || Object))) { return false } if (a[equalityTrait.EqualityTraitSymbol] != null) { return a[equalityTrait.EqualityTraitSymbol](b) } 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 undefined: case Object: if (object.size(a) !== object.size(b)) { return false } for (const key in a) { if (!object.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} options */ // @ts-ignore export const isOneOf = (value, options) => options.includes(value) /* c8 ignore stop */ export const isArray = array.isArray /** * @param {any} s * @return {s is String} */ export const isString = (s) => s && s.constructor === String /** * @param {any} n * @return {n is Number} */ export const isNumber = n => n != null && n.constructor === Number /** * @template {abstract new (...args: any) => any} TYPE * @param {any} n * @param {TYPE} T * @return {n is InstanceType} */ export const is = (n, T) => n && n.constructor === T /** * @template {abstract new (...args: any) => any} TYPE * @param {TYPE} T */ export const isTemplate = (T) => /** * @param {any} n * @return {n is InstanceType} **/ n => n && n.constructor === T dmonad-lib0-c7e7806/function.test.js000066400000000000000000000066451512504553500173260ustar00rootroot00000000000000import * as f from './function.js' import * as t from './testing.js' /** * @param {t.TestCase} _tc */ export const testBasics = _tc => { let calls = 0 f.apply(() => calls++) t.assert(calls === 1) t.assert(f.isOneOf(1, [3, 2, 1])) t.assert(!f.isOneOf(0, [3, 2, 1])) // test is* const arr = [1, 'two', [1], { one: 1 }] arr.forEach(val => { if (f.isArray(val)) { /** * @type {Array} */ const yy = val t.assert(yy) } if (f.isString(val)) { /** * @type {string} */ const yy = val t.assert(yy) } if (f.isNumber(val)) { /** * @type {number} */ const yy = val t.assert(yy) } if (f.is(val, String)) { /** * @type {string} */ const yy = val t.assert(yy) } if (f.isTemplate(Number)(val)) { /** * @type {number} */ const yy = val t.assert(yy) } }) } /** * @param {t.TestCase} _tc */ export const testCallAll = _tc => { const err = new Error() let calls = 0 try { f.callAll([ () => { calls++ }, () => { throw err }, f.nop, () => { calls++ } ], []) } catch (e) { t.assert(calls === 2) return } t.fail('Expected callAll to throw error') } /** * @param {t.TestCase} _tc */ export const testDeepEquality = _tc => { t.assert(f.equalityDeep(1, 1)) t.assert(!f.equalityDeep(1, 2)) t.assert(!f.equalityDeep(1, '1')) t.assert(!f.equalityDeep(1, null)) const obj = { b: 5 } const map1 = new Map() const map2 = new Map() const map3 = new Map() const map4 = new Map() map1.set('a', obj) map2.set('a', { b: 5 }) map3.set('b', obj) map4.set('a', obj) map4.set('b', obj) t.assert(f.equalityDeep({ a: 4 }, { a: 4 })) t.assert(f.equalityDeep({ a: 4, obj: { b: 5 } }, { a: 4, obj })) t.assert(!f.equalityDeep({ a: 4 }, { a: 4, obj })) t.assert(f.equalityDeep({ a: [], obj }, { a: [], obj })) t.assert(!f.equalityDeep({ a: [], obj }, { a: [], obj: undefined })) t.assert(f.equalityDeep({}, {})) t.assert(!f.equalityDeep({}, { a: 4 })) t.assert(f.equalityDeep([{ a: 4 }, 1], [{ a: 4 }, 1])) t.assert(!f.equalityDeep([{ a: 4 }, 1], [{ a: 4 }, 2])) t.assert(!f.equalityDeep([{ a: 4 }, 1], [{ a: 4 }, 1, 3])) t.assert(f.equalityDeep([], [])) t.assert(!f.equalityDeep([1], [])) t.assert(f.equalityDeep(map1, map2)) t.assert(!f.equalityDeep(map1, map3)) t.assert(!f.equalityDeep(map1, map4)) const set1 = new Set([1]) const set2 = new Set([true]) const set3 = new Set([1, true]) const set4 = new Set([true]) t.assert(f.equalityDeep(set2, set4)) t.assert(!f.equalityDeep(set1, set2)) t.assert(!f.equalityDeep(set1, set3)) t.assert(!f.equalityDeep(set1, set4)) t.assert(!f.equalityDeep(set2, set3)) t.assert(f.equalityDeep(set2, set4)) const buf1 = Uint8Array.from([1, 2]) const buf2 = Uint8Array.from([1, 3]) const buf3 = Uint8Array.from([1, 2, 3]) const buf4 = Uint8Array.from([1, 2]) t.assert(!f.equalityDeep(buf1, buf2)) t.assert(!f.equalityDeep(buf2, buf3)) t.assert(!f.equalityDeep(buf3, buf4)) t.assert(f.equalityDeep(buf4, buf1)) t.assert(!f.equalityDeep(buf1.buffer, buf2.buffer)) t.assert(!f.equalityDeep(buf2.buffer, buf3.buffer)) t.assert(!f.equalityDeep(buf3.buffer, buf4.buffer)) t.assert(f.equalityDeep(buf4.buffer, buf1.buffer)) t.assert(!f.equalityDeep(buf1, buf4.buffer)) } dmonad-lib0-c7e7806/hash/000077500000000000000000000000001512504553500150755ustar00rootroot00000000000000dmonad-lib0-c7e7806/hash/rabin-gf2-polynomial.js000066400000000000000000000212701512504553500213650ustar00rootroot00000000000000/** * The idea of the Rabin fingerprint algorithm is to represent the binary as a polynomial in a * finite field (Galois Field G(2)). The polynomial will then be taken "modulo" by an irreducible * polynomial of the desired size. * * This implementation is inefficient and is solely used to verify the actually performant * implementation in `./rabin.js`. * * @module rabin-gf2-polynomial */ import * as math from '../math.js' import * as webcrypto from 'lib0/webcrypto' import * as array from '../array.js' import * as buffer from '../buffer.js' /** * @param {number} degree */ const _degreeToMinByteLength = degree => math.floor(degree / 8) + 1 /** * This is a GF2 Polynomial abstraction that is not meant for production! * * It is easy to understand and it's correctness is as obvious as possible. It can be used to verify * efficient implementations of algorithms on GF2. */ export class GF2Polynomial { constructor () { /** * @type {Set} */ this.degrees = new Set() } } /** * From Uint8Array (MSB). * * @param {Uint8Array} bytes */ export const createFromBytes = bytes => { const p = new GF2Polynomial() for (let bsi = bytes.length - 1, currDegree = 0; bsi >= 0; bsi--) { const currByte = bytes[bsi] for (let i = 0; i < 8; i++) { if (((currByte >>> i) & 1) === 1) { p.degrees.add(currDegree) } currDegree++ } } return p } /** * Transform to Uint8Array (MSB). * * @param {GF2Polynomial} p * @param {number} byteLength */ export const toUint8Array = (p, byteLength = _degreeToMinByteLength(getHighestDegree(p))) => { const buf = buffer.createUint8ArrayFromLen(byteLength) /** * @param {number} i */ const setBit = i => { const bi = math.floor(i / 8) buf[buf.length - 1 - bi] |= (1 << (i % 8)) } p.degrees.forEach(setBit) return buf } /** * Create from unsigned integer (max 32bit uint) - read most-significant-byte first. * * @param {number} uint */ export const createFromUint = uint => { const buf = new Uint8Array(4) for (let i = 0; i < 4; i++) { buf[i] = uint >>> 8 * (3 - i) } return createFromBytes(buf) } /** * Create a random polynomial of a specified degree. * * @param {number} degree */ export const createRandom = degree => { const bs = new Uint8Array(_degreeToMinByteLength(degree)) webcrypto.getRandomValues(bs) // Get first byte and explicitly set the bit of "degree" to 1 (the result must have the specified // degree). const firstByte = bs[0] | 1 << (degree % 8) // Find out how many bits of the first byte need to be filled with zeros because they are >degree. const zeros = 7 - (degree % 8) bs[0] = ((firstByte << zeros) & 0xff) >>> zeros return createFromBytes(bs) } /** * @param {GF2Polynomial} p * @return number */ export const getHighestDegree = p => array.fold(array.from(p.degrees), 0, math.max) /** * Add (+) p2 int the p1 polynomial. * * Addition is defined as xor in F2. Substraction is equivalent to addition in F2. * * @param {GF2Polynomial} p1 * @param {GF2Polynomial} p2 */ export const addInto = (p1, p2) => { p2.degrees.forEach(degree => { if (p1.degrees.has(degree)) { p1.degrees.delete(degree) } else { p1.degrees.add(degree) } }) } /** * Or (|) p2 into the p1 polynomial. * * Addition is defined as xor in F2. Substraction is equivalent to addition in F2. * * @param {GF2Polynomial} p1 * @param {GF2Polynomial} p2 */ export const orInto = (p1, p2) => { p2.degrees.forEach(degree => { p1.degrees.add(degree) }) } /** * Add (+) p2 to the p1 polynomial. * * Addition is defined as xor in F2. Substraction is equivalent to addition in F2. * * @param {GF2Polynomial} p1 * @param {GF2Polynomial} p2 */ export const add = (p1, p2) => { const result = new GF2Polynomial() p2.degrees.forEach(degree => { if (!p1.degrees.has(degree)) { result.degrees.add(degree) } }) p1.degrees.forEach(degree => { if (!p2.degrees.has(degree)) { result.degrees.add(degree) } }) return result } /** * Add (+) p2 to the p1 polynomial. * * Addition is defined as xor in F2. Substraction is equivalent to addition in F2. * * @param {GF2Polynomial} p */ export const clone = (p) => { const result = new GF2Polynomial() p.degrees.forEach(d => result.degrees.add(d)) return result } /** * Add (+) p2 to the p1 polynomial. * * Addition is defined as xor in F2. Substraction is equivalent to addition in F2. * * @param {GF2Polynomial} p * @param {number} degree */ export const addDegreeInto = (p, degree) => { if (p.degrees.has(degree)) { p.degrees.delete(degree) } else { p.degrees.add(degree) } } /** * Multiply (•) p1 with p2 and store the result in p1. * * @param {GF2Polynomial} p1 * @param {GF2Polynomial} p2 */ export const multiply = (p1, p2) => { const result = new GF2Polynomial() p1.degrees.forEach(degree1 => { p2.degrees.forEach(degree2 => { addDegreeInto(result, degree1 + degree2) }) }) return result } /** * Multiply (•) p1 with p2 and store the result in p1. * * @param {GF2Polynomial} p * @param {number} shift */ export const shiftLeft = (p, shift) => { const result = new GF2Polynomial() p.degrees.forEach(degree => { const r = degree + shift r >= 0 && result.degrees.add(r) }) return result } /** * Computes p1 % p2. I.e. the remainder of p1/p2. * * @param {GF2Polynomial} p1 * @param {GF2Polynomial} p2 */ export const mod = (p1, p2) => { const maxDeg1 = getHighestDegree(p1) const maxDeg2 = getHighestDegree(p2) const result = clone(p1) for (let i = maxDeg1 - maxDeg2; i >= 0; i--) { if (result.degrees.has(maxDeg2 + i)) { const shifted = shiftLeft(p2, i) addInto(result, shifted) } } return result } /** * Computes (p^e mod m). * * http://en.wikipedia.org/wiki/Modular_exponentiation * * @param {GF2Polynomial} p * @param {number} e * @param {GF2Polynomial} m */ export const modPow = (p, e, m) => { let result = ONE while (true) { if ((e & 1) === 1) { result = mod(multiply(result, p), m) } e >>>= 1 if (e === 0) { return result } p = mod(multiply(p, p), m) } } /** * Find the greatest common divisor using Euclid's Algorithm. * * @param {GF2Polynomial} p1 * @param {GF2Polynomial} p2 */ export const gcd = (p1, p2) => { while (p2.degrees.size > 0) { const modded = mod(p1, p2) p1 = p2 p2 = modded } return p1 } /** * true iff p1 equals p2 * * @param {GF2Polynomial} p1 * @param {GF2Polynomial} p2 */ export const equals = (p1, p2) => { if (p1.degrees.size !== p2.degrees.size) return false for (const d of p1.degrees) { if (!p2.degrees.has(d)) return false } return true } const X = createFromBytes(new Uint8Array([2])) const ONE = createFromBytes(new Uint8Array([1])) /** * Computes ( x^(2^p) - x ) mod f * * (shamelessly copied from * https://github.com/opendedup/rabinfingerprint/blob/master/src/org/rabinfingerprint/polynomial/Polynomial.java) * * @param {GF2Polynomial} f * @param {number} p */ const reduceExponent = (f, p) => { // compute (x^q^p mod f) const q2p = math.pow(2, p) const x2q2p = modPow(X, q2p, f) // subtract (x mod f) return mod(add(x2q2p, X), f) } /** * BenOr Reducibility Test * * Tests and Constructions of Irreducible Polynomials over Finite Fields * (1997) Shuhong Gao, Daniel Panario * * http://citeseer.ist.psu.edu/cache/papers/cs/27167/http:zSzzSzwww.math.clemson.eduzSzfacultyzSzGaozSzpaperszSzGP97a.pdf/gao97tests.pdf * * @param {GF2Polynomial} p */ export const isIrreducibleBenOr = p => { const degree = getHighestDegree(p) for (let i = 1; i < degree / 2; i++) { const b = reduceExponent(p, i) const g = gcd(p, b) if (!equals(g, ONE)) { return false } } return true } /** * @param {number} degree */ export const createIrreducible = degree => { while (true) { const p = createRandom(degree) if (isIrreducibleBenOr(p)) return p } } /** * Create a fingerprint of buf using the irreducible polynomial m. * * @param {Uint8Array} buf * @param {GF2Polynomial} m */ export const fingerprint = (buf, m) => toUint8Array(mod(createFromBytes(buf), m), _degreeToMinByteLength(getHighestDegree(m) - 1)) export class RabinPolynomialEncoder { /** * @param {GF2Polynomial} m The irreducible polynomial */ constructor (m) { this.fingerprint = new GF2Polynomial() this.m = m } /** * @param {number} b */ write (b) { const bp = createFromBytes(new Uint8Array([b])) const fingerprint = shiftLeft(this.fingerprint, 8) orInto(fingerprint, bp) this.fingerprint = mod(fingerprint, this.m) } getFingerprint () { return toUint8Array(this.fingerprint, _degreeToMinByteLength(getHighestDegree(this.m) - 1)) } } dmonad-lib0-c7e7806/hash/rabin-uncached.js000066400000000000000000000033541512504553500203030ustar00rootroot00000000000000/** * It is not recommended to use this package. This is the uncached implementation of the rabin * fingerprint algorithm. However, it can be used to verify the `rabin.js` implementation. * * @module rabin-uncached */ import * as math from '../math.js' import * as buffer from '../buffer.js' export class RabinUncachedEncoder { /** * @param {Uint8Array} m assert(m[0] === 1) */ constructor (m) { this.m = m this.blen = m.byteLength this.bs = new Uint8Array(this.blen) /** * This describes the position of the most significant byte (starts with 0 and increases with * shift) */ this.bpos = 0 } /** * Add/Xor/Substract bytes. * * Discards bytes that are out of range. * @todo put this in function or inline * * @param {Uint8Array} cs */ add (cs) { const copyLen = math.min(this.blen, cs.byteLength) // copy from right to left until max is reached for (let i = 0; i < copyLen; i++) { this.bs[(this.bpos + this.blen - i - 1) % this.blen] ^= cs[cs.byteLength - i - 1] } } /** * @param {number} byte */ write (byte) { // [0,m1,m2,b] // x <- bpos // Shift one byte to the left, add b this.bs[this.bpos] = byte this.bpos = (this.bpos + 1) % this.blen // mod for (let i = 7; i >= 0; i--) { if (((this.bs[this.bpos] >>> i) & 1) === 1) { this.add(buffer.shiftNBitsLeft(this.m, i)) } } // if (this.bs[this.bpos] !== 0) { error.unexpectedCase() } // assert(this.bs[this.bpos] === 0) } getFingerprint () { const result = new Uint8Array(this.blen - 1) for (let i = 0; i < result.byteLength; i++) { result[i] = this.bs[(this.bpos + i + 1) % this.blen] } return result } } dmonad-lib0-c7e7806/hash/rabin.js000066400000000000000000000057551512504553500165420ustar00rootroot00000000000000/** * @module rabin * * Very efficient & versatile fingerprint/hashing algorithm. However, it is not cryptographically * secure. Well suited for fingerprinting. */ import * as buffer from '../buffer.js' import * as map from '../map.js' export const StandardIrreducible8 = new Uint8Array([1, 221]) export const StandardIrreducible16 = new Uint8Array([1, 244, 157]) export const StandardIrreducible32 = new Uint8Array([1, 149, 183, 205, 191]) export const StandardIrreducible64 = new Uint8Array([1, 133, 250, 114, 193, 250, 28, 193, 231]) export const StandardIrreducible128 = new Uint8Array([1, 94, 109, 166, 228, 6, 222, 102, 239, 27, 128, 184, 13, 50, 112, 169, 199]) /** * Maps from a modulo to the precomputed values. * * @type {Map} */ const _precomputedFingerprintCache = new Map() /** * @param {Uint8Array} m */ const ensureCache = m => map.setIfUndefined(_precomputedFingerprintCache, buffer.toBase64(m), () => { const byteLen = m.byteLength const cache = new Uint8Array(256 * byteLen) // Use dynamic computing to compute the cached results. // Starting values: cache(0) = 0; cache(1) = m cache.set(m, byteLen) for (let bit = 1; bit < 8; bit++) { const mBitShifted = buffer.shiftNBitsLeft(m, bit) const bitShifted = 1 << bit for (let j = 0; j < bitShifted; j++) { // apply the shifted result (reducing the degree of the polynomial) const msb = bitShifted | j const rest = msb ^ mBitShifted[0] for (let i = 0; i < byteLen; i++) { // rest is already precomputed in the cache cache[msb * byteLen + i] = cache[rest * byteLen + i] ^ mBitShifted[i] } // if (cache[(bitShifted | j) * byteLen] !== (bitShifted | j)) { error.unexpectedCase() } } } return cache }) export class RabinEncoder { /** * @param {Uint8Array} m assert(m[0] === 1) */ constructor (m) { this.m = m this.blen = m.byteLength this.bs = new Uint8Array(this.blen) this.cache = ensureCache(m) /** * This describes the position of the most significant byte (starts with 0 and increases with * shift) */ this.bpos = 0 } /** * @param {number} byte */ write (byte) { // assert(this.bs[0] === 0) // Shift one byte to the left, add b this.bs[this.bpos] = byte this.bpos = (this.bpos + 1) % this.blen const msb = this.bs[this.bpos] for (let i = 0; i < this.blen; i++) { this.bs[(this.bpos + i) % this.blen] ^= this.cache[msb * this.blen + i] } // assert(this.bs[this.bpos] === 0) } getFingerprint () { const result = new Uint8Array(this.blen - 1) for (let i = 0; i < result.byteLength; i++) { result[i] = this.bs[(this.bpos + i + 1) % this.blen] } return result } } /** * @param {Uint8Array} irreducible * @param {Uint8Array} data */ export const fingerprint = (irreducible, data) => { const encoder = new RabinEncoder(irreducible) for (let i = 0; i < data.length; i++) { encoder.write(data[i]) } return encoder.getFingerprint() } dmonad-lib0-c7e7806/hash/rabin.test.js000066400000000000000000000152451512504553500175130ustar00rootroot00000000000000import * as t from '../testing.js' import * as gf2 from './rabin-gf2-polynomial.js' import { RabinUncachedEncoder } from './rabin-uncached.js' import * as rabin from './rabin.js' import * as math from '../math.js' import * as array from '../array.js' import * as prng from '../prng.js' import * as buffer from '../buffer.js' import * as map from '../map.js' /** * @param {t.TestCase} _tc */ export const testPolynomialBasics = _tc => { const bs = new Uint8Array([1, 11]) const p = gf2.createFromBytes(bs) t.assert(p.degrees.has(3)) t.assert(p.degrees.has(1)) t.assert(p.degrees.has(0)) t.assert(p.degrees.has(8)) } /** * @param {t.TestCase} _tc */ export const testIrreducibleInput = _tc => { const pa = gf2.createFromUint(0x53) const pb = gf2.createFromUint(0xCA) const pm = gf2.createFromUint(0x11B) const px = gf2.multiply(pa, pb) t.compare(new Uint8Array([0x53]), gf2.toUint8Array(pa)) t.compare(new Uint8Array([0xCA]), gf2.toUint8Array(pb)) t.assert(gf2.equals(gf2.createFromUint(0x3F7E), px)) t.compare(new Uint8Array([0x3F, 0x7E]), gf2.toUint8Array(px)) const pabm = gf2.mod(px, pm) t.compare(new Uint8Array([0x1]), gf2.toUint8Array(pabm)) } /** * @param {t.TestCase} _tc */ export const testIrreducibleSpread = _tc => { const degree = 32 const N = 1000 const avgSpread = getSpreadAverage(degree, N) const diffSpread = math.abs(avgSpread - degree) t.info(`Average spread for degree ${degree} at ${N} repetitions: ${avgSpread}`) t.assert(diffSpread < 5, 'Spread of irreducible polynomials is within expected range') } /** * @param {number} degree * @param {number} tests */ const getSpreadAverage = (degree, tests) => { const spreads = [] for (let i = 0, test = 0, lastI = 0; test < tests; i++) { const f = gf2.createRandom(degree) t.assert(gf2.getHighestDegree(f) === degree) if (gf2.isIrreducibleBenOr(f)) { const spread = i - lastI spreads.push(spread) lastI = i test++ } } return array.fold(spreads, 0, math.add) / tests } /** * @param {t.TestCase} _tc */ export const testGenerateIrreducibles = _tc => { /** * @param {number} byteLen */ const testIrreducibleGen = byteLen => { const K = byteLen * 8 const irr = gf2.createIrreducible(K) t.assert(gf2.getHighestDegree(irr) === K, 'degree equals K') const irrBs = gf2.toUint8Array(irr) console.log(`K = ${K}`, irrBs) t.assert(irrBs[0] === 1) t.assert(irrBs.byteLength === byteLen + 1) } testIrreducibleGen(1) testIrreducibleGen(2) testIrreducibleGen(4) testIrreducibleGen(8) testIrreducibleGen(16) gf2.isIrreducibleBenOr(gf2.createFromBytes(rabin.StandardIrreducible8)) gf2.isIrreducibleBenOr(gf2.createFromBytes(rabin.StandardIrreducible16)) gf2.isIrreducibleBenOr(gf2.createFromBytes(rabin.StandardIrreducible32)) gf2.isIrreducibleBenOr(gf2.createFromBytes(rabin.StandardIrreducible64)) gf2.isIrreducibleBenOr(gf2.createFromBytes(rabin.StandardIrreducible128)) } /** * @param {t.TestCase} tc * @param {number} K */ const _testFingerprintCompatiblityK = (tc, K) => { /** * @type {Array} */ const dataObjects = [] const N = 300 const MSIZE = 130 t.info(`N=${N} K=${K} MSIZE=${MSIZE}`) /** * @type {gf2.GF2Polynomial} */ let irreducible /** * @type {Uint8Array} */ let irreducibleBuffer t.measureTime(`find irreducible of ${K}`, () => { irreducible = gf2.createIrreducible(K) irreducibleBuffer = gf2.toUint8Array(irreducible) }) for (let i = 0; i < N; i++) { dataObjects.push(prng.uint8Array(tc.prng, MSIZE)) } /** * @type {Array} */ let fingerprints1 = [] t.measureTime('polynomial direct', () => { fingerprints1 = dataObjects.map((o, _index) => gf2.fingerprint(o, irreducible)) }) const testSet = new Set(fingerprints1.map(buffer.toBase64)) t.assert(K < 32 || testSet.size === N) /** * @type {Array} */ let fingerprints2 = [] t.measureTime('polynomial incremental', () => { fingerprints2 = dataObjects.map((o, _index) => { const encoder = new gf2.RabinPolynomialEncoder(irreducible) for (let i = 0; i < o.byteLength; i++) { encoder.write(o[i]) } return encoder.getFingerprint() }) }) t.compare(fingerprints1, fingerprints2) /** * @type {Array} */ let fingerprints3 = [] t.measureTime('polynomial incremental (efficent))', () => { fingerprints3 = dataObjects.map((o, _index) => { const encoder = new RabinUncachedEncoder(irreducibleBuffer) for (let i = 0; i < o.byteLength; i++) { encoder.write(o[i]) } return encoder.getFingerprint() }) }) t.compare(fingerprints1, fingerprints3) // ensuring that the cache is already populated // @ts-ignore // eslint-disable-next-line new rabin.RabinEncoder(irreducibleBuffer) /** * @type {Array} */ let fingerprints4 = [] t.measureTime('polynomial incremental (efficent & cached)) using encoder', () => { fingerprints4 = dataObjects.map((o, _index) => { const encoder = new rabin.RabinEncoder(irreducibleBuffer) for (let i = 0; i < o.byteLength; i++) { encoder.write(o[i]) } return encoder.getFingerprint() }) }) t.compare(fingerprints1, fingerprints4) /** * @type {Array} */ let fingerprints5 = [] t.measureTime('polynomial incremental (efficent & cached))', () => { fingerprints5 = dataObjects.map((o, _index) => { return rabin.fingerprint(irreducibleBuffer, o) }) }) t.compare(fingerprints1, fingerprints5) } /** * @param {t.TestCase} tc */ export const testFingerprintCompatiblity = tc => { _testFingerprintCompatiblityK(tc, 8) _testFingerprintCompatiblityK(tc, 16) _testFingerprintCompatiblityK(tc, 32) _testFingerprintCompatiblityK(tc, 64) _testFingerprintCompatiblityK(tc, 128) } /** * @param {t.TestCase} tc */ export const testConflicts = tc => { /** * @type {Array} */ const data = [] const N = 100 const Irr = rabin.StandardIrreducible8 t.measureTime(`generate ${N} items`, () => { for (let i = 0; i < N; i++) { data.push(prng.uint8Array(tc.prng, prng.uint32(tc.prng, 5, 50))) } }) /** * @type {Map>} */ const results = new Map() t.measureTime(`fingerprint ${N} items`, () => { data.forEach(d => { const f = buffer.toBase64(rabin.fingerprint(Irr, d)) map.setIfUndefined(results, f, () => new Set()).add(buffer.toBase64(d)) }) }) const conflicts = array.fold(map.map(results, (ds) => ds.size - 1), 0, math.add) const usedFields = results.size const unusedFieds = math.pow(2, (Irr.length - 1) * 8) - results.size console.log({ conflicts, usedFields, unusedFieds }) } dmonad-lib0-c7e7806/hash/sha256.js000066400000000000000000000122721512504553500164470ustar00rootroot00000000000000/** * @module sha256 * Spec: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf * Resources: * - https://web.archive.org/web/20150315061807/http://csrc.nist.gov/groups/STM/cavp/documents/shs/sha256-384-512.pdf */ import * as binary from '../binary.js' /** * @param {number} w - a 32bit uint * @param {number} shift */ const rotr = (w, shift) => (w >>> shift) | (w << (32 - shift)) /** * Helper for SHA-224 & SHA-256. See 4.1.2. * @param {number} x */ const sum0to256 = x => rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22) /** * Helper for SHA-224 & SHA-256. See 4.1.2. * @param {number} x */ const sum1to256 = x => rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25) /** * Helper for SHA-224 & SHA-256. See 4.1.2. * @param {number} x */ const sigma0to256 = x => rotr(x, 7) ^ rotr(x, 18) ^ x >>> 3 /** * Helper for SHA-224 & SHA-256. See 4.1.2. * @param {number} x */ const sigma1to256 = x => rotr(x, 17) ^ rotr(x, 19) ^ x >>> 10 // @todo don't init these variables globally /** * See 4.2.2: Constant for sha256 & sha224 * These words represent the first thirty-two bits of the fractional parts of * the cube roots of the first sixty-four prime numbers. In hex, these constant words are (from left to * right) */ const K = new Uint32Array([ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 ]) /** * See 5.3.3. Initial hash value. * * These words were obtained by taking the first thirty-two bits of the fractional parts of the * square roots of the first eight prime numbers. * * @todo shouldn't be a global variable */ const HINIT = new Uint32Array([ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 ]) // time to beat: (large value < 4.35s) class Hasher { constructor () { const buf = new ArrayBuffer(64 + 64 * 4) // Init working variables using a single arraybuffer this._H = new Uint32Array(buf, 0, 8) this._H.set(HINIT) // "Message schedule" - a working variable this._W = new Uint32Array(buf, 64, 64) } _updateHash () { const H = this._H const W = this._W for (let t = 16; t < 64; t++) { W[t] = sigma1to256(W[t - 2]) + W[t - 7] + sigma0to256(W[t - 15]) + W[t - 16] } let a = H[0] let b = H[1] let c = H[2] let d = H[3] let e = H[4] let f = H[5] let g = H[6] let h = H[7] for (let tt = 0, T1, T2; tt < 64; tt++) { T1 = (h + sum1to256(e) + ((e & f) ^ (~e & g)) + K[tt] + W[tt]) >>> 0 T2 = (sum0to256(a) + ((a & b) ^ (a & c) ^ (b & c))) >>> 0 h = g g = f f = e e = (d + T1) >>> 0 d = c c = b b = a a = (T1 + T2) >>> 0 } H[0] += a H[1] += b H[2] += c H[3] += d H[4] += e H[5] += f H[6] += g H[7] += h } /** * Returns a 32-byte hash. * * @param {Uint8Array} data */ digest (data) { let i = 0 for (; i + 56 <= data.length;) { // write data in big endianess let j = 0 for (; j < 16 && i + 3 < data.length; j++) { this._W[j] = data[i++] << 24 | data[i++] << 16 | data[i++] << 8 | data[i++] } if (i % 64 !== 0) { // there is still room to write partial content and the ending bit. this._W.fill(0, j, 16) while (i < data.length) { this._W[j] |= data[i] << ((3 - (i % 4)) * 8) i++ } this._W[j] |= binary.BIT8 << ((3 - (i % 4)) * 8) } this._updateHash() } // same check as earlier - the ending bit has been written const isPaddedWith1 = i % 64 !== 0 this._W.fill(0, 0, 16) let j = 0 for (; i < data.length; j++) { for (let ci = 3; ci >= 0 && i < data.length; ci--) { this._W[j] |= data[i++] << (ci * 8) } } // Write padding of the message. See 5.1.2. if (!isPaddedWith1) { this._W[j - (i % 4 === 0 ? 0 : 1)] |= binary.BIT8 << ((3 - (i % 4)) * 8) } // write length of message (size in bits) as 64 bit uint // @todo test that this works correctly this._W[14] = data.byteLength / binary.BIT30 // same as data.byteLength >>> 30 - but works on floats this._W[15] = data.byteLength * 8 this._updateHash() // correct H endianness to use big endiannes and return a Uint8Array const dv = new Uint8Array(32) for (let i = 0; i < this._H.length; i++) { for (let ci = 0; ci < 4; ci++) { dv[i * 4 + ci] = this._H[i] >>> (3 - ci) * 8 } } return dv } } /** * Returns a 32-byte hash. * * @param {Uint8Array} data */ export const digest = data => new Hasher().digest(data) dmonad-lib0-c7e7806/hash/sha256.node.js000066400000000000000000000003051512504553500173650ustar00rootroot00000000000000import { createHash } from 'node:crypto' /** * @param {Uint8Array} data */ export const digest = data => { const hasher = createHash('sha256') hasher.update(data) return hasher.digest() } dmonad-lib0-c7e7806/hash/sha256.test.js000066400000000000000000000133541512504553500174270ustar00rootroot00000000000000import * as t from '../testing.js' import * as sha256 from './sha256.js' import * as buffer from '../buffer.js' import * as string from '../string.js' import * as prng from '../prng.js' import * as webcrypto from 'lib0/webcrypto' import * as promise from '../promise.js' import * as env from '../environment.js' import * as array from '../array.js' import * as f from '../function.js' /** * @param {t.TestCase} _tc */ export const testSelfReferencingHash = _tc => { const hash = sha256.digest(string.encodeUtf8('The SHA256 for this sentence begins with: one, eight, two, a, seven, c and nine.')) t.assert(buffer.toHexString(hash).startsWith('182a7c9')) } /** * @param {t.TestCase} _tc */ export const testSha256Basics = async _tc => { /** * @param {string | Uint8Array} data input data (buffer or hex encoded) * @param {string} result Expected result (hex encoded) */ const test = async (data, result) => { data = typeof data === 'string' ? buffer.fromHexString(data) : data const res = sha256.digest(data) const resHex = buffer.toHexString(res) t.assert(resHex === result) const resWebcrypto = new Uint8Array(await webcrypto.subtle.digest('SHA-256', data)) const resWebcryptoHex = buffer.toHexString(resWebcrypto) t.assert(resWebcryptoHex === result) } // const newInput = 'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopno' // const xx = new Uint8Array(await webcrypto.subtle.digest('SHA-256', string.encodeUtf8(newInput))) // console.log(buffer.toHexString(xx), ' dtrndtndtn', newInput.length) await test('', 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855') await test(string.encodeUtf8('ab'), 'fb8e20fc2e4c3f248c60c39bd652f3c1347298bb977b8b4d5903b85055620603') await test(string.encodeUtf8('abc'), 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad') await test(string.encodeUtf8('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopno'), '71806f0af18dbbe905f8fcaa576b8e687859163cf68b38bc26f32e5120522cc1') await test(string.encodeUtf8('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'), '248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1') await test(string.encodeUtf8('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopqqqq0001'), 'e54ad86cad2d9d7c03506d6a67ed03fab5fbb8d34012143e9c0eb88ace56ca59') await test(string.encodeUtf8('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopqqqq00012'), '7a1e062405a534817dcb89fa2416a69e4fbe75fabece33d528b82d1b71d3e418') await test(string.encodeUtf8('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopqqqq000123'), '51691fe639226a9df2c0e4637918990291c73cdbb58665a7c729bb4e8d67784c') } /** * Test if implementation is correct when length (in bits) exceeds uint32. * * @param {t.TestCase} _tc */ export const testLargeValue = async _tc => { t.skip(!t.extensive) const BS = 100 * 1000 * 1000 const data = prng.uint8Array(prng.create(42), BS) let resNode = buffer.fromBase64('81m7UtkH2s9J3E33Bw5kRMuC5zvktgZ64SfzenbV5Lw=') let resLib0 let resWebcrypto if (env.isNode) { const sha256Node = await import('./sha256.node.js') t.measureTime(`[node] Hash message of size ${BS}`, () => { const res = new Uint8Array(sha256Node.digest(data)) if (!f.equalityDeep(res, resNode)) { console.warn(`Precomputed result should be the same! New result: ${buffer.toBase64(res)}`) } resNode = res t.compare(res, resNode, 'Precomputed result should be the same') }) } t.measureTime(`[lib0] Hash message of size ${BS}`, () => { resLib0 = sha256.digest(data) }) await t.measureTimeAsync(`[webcrypto] Hash message of size ${BS}`, async () => { resWebcrypto = new Uint8Array(await webcrypto.subtle.digest('SHA-256', data)) }) t.compare(resLib0, resNode) t.compare(resLib0, resWebcrypto) } /** * @param {t.TestCase} tc */ export const testRepeatSha256Hashing = async tc => { const LEN = prng.bool(tc.prng) ? prng.uint32(tc.prng, 0, 512) : prng.uint32(tc.prng, 0, 3003030) const data = prng.uint8Array(tc.prng, LEN) const hashedCustom = sha256.digest(data) const hashedWebcrypto = new Uint8Array(await webcrypto.subtle.digest('SHA-256', data)) t.compare(hashedCustom, hashedWebcrypto) } /** * @param {t.TestCase} _tc */ export const testBenchmarkSha256 = async _tc => { /** * @param {number} N * @param {number} BS */ const bench = (N, BS) => t.groupAsync(`Hash ${N} random values of size ${BS}`, async () => { const gen = prng.create(42) const datas = array.unfold(N, () => prng.uint8Array(gen, BS)) t.measureTime('lib0 (fallback))', () => { for (let i = 0; i < N; i++) { const x = sha256.digest(datas[i]) if (x === null) throw new Error() } }) if (env.isNode) { const nodeSha = await import('./sha256.node.js') t.measureTime('lib0 (node))', () => { for (let i = 0; i < N; i++) { const x = nodeSha.digest(datas[i]) if (x === null) throw new Error() } }) } await t.measureTimeAsync('webcrypto sequentially', async () => { for (let i = 0; i < N; i++) { const x = await webcrypto.subtle.digest('SHA-256', datas[i]) if (x === null) throw new Error() } }) await t.measureTimeAsync('webcrypto concurrent', async () => { /** * @type {Array>} */ const ps = [] for (let i = 0; i < N; i++) { ps.push(webcrypto.subtle.digest('SHA-256', datas[i])) } const x = await promise.all(ps) if (x === null) throw new Error() }) }) await bench(10 * 1000, 10) await bench(10 * 1000, 50) t.skip(!t.extensive) await bench(10 * 1000, 100) await bench(10 * 1000, 500) await bench(10 * 1000, 1000) await bench(10 * 1000, 4098) await bench(10, 5 * 1000 * 1000) } dmonad-lib0-c7e7806/index.js000066400000000000000000000035771512504553500156330ustar00rootroot00000000000000/** * Experimental method to import lib0. * * Not recommended if the module bundler doesn't support dead code elimination. * * @module lib0 */ import * as array from './array.js' import * as binary from './binary.js' import * as broadcastchannel from './broadcastchannel.js' import * as buffer from './buffer.js' import * as conditions from './conditions.js' import * as decoding from './decoding.js' import * as diff from './diff.js' import * as dom from './dom.js' import * as encoding from './encoding.js' import * as environment from './environment.js' import * as error from './error.js' import * as eventloop from './eventloop.js' // @todo rename file to func import * as func from './function.js' import * as indexeddb from './indexeddb.js' import * as iterator from './iterator.js' import * as json from './json.js' import * as logging from 'lib0/logging' import * as map from './map.js' import * as math from './math.js' import * as mutex from './mutex.js' import * as number from './number.js' import * as object from './object.js' import * as pair from './pair.js' import * as prng from './prng.js' import * as promise from './promise.js' // import * as random from './random.js' import * as set from './set.js' import * as sort from './sort.js' import * as statistics from './statistics.js' import * as string from './string.js' import * as symbol from './symbol.js' // import * as testing from './testing.js' import * as time from './time.js' import * as tree from './tree.js' import * as websocket from './websocket.js' export { array, binary, broadcastchannel, buffer, conditions, decoding, diff, dom, encoding, environment, error, eventloop, func, indexeddb, iterator, json, logging, map, math, mutex, number, object, pair, prng, promise, // random, set, sort, statistics, string, symbol, // testing, time, tree, websocket } dmonad-lib0-c7e7806/indexeddb.js000066400000000000000000000155431512504553500164460ustar00rootroot00000000000000/* eslint-env browser */ /** * Helpers to work with IndexedDB. * * @module indexeddb */ import * as promise from './promise.js' import * as error from './error.js' /* c8 ignore start */ /** * IDB Request to Promise transformer * * @param {IDBRequest} request * @return {Promise} */ export const rtop = request => promise.create((resolve, reject) => { // @ts-ignore request.onerror = event => reject(new Error(event.target.error)) // @ts-ignore request.onsuccess = event => resolve(event.target.result) }) /** * @param {string} name * @param {function(IDBDatabase):any} initDB Called when the database is first created * @return {Promise} */ export const openDB = (name, initDB) => promise.create((resolve, reject) => { const request = indexedDB.open(name) /** * @param {any} event */ request.onupgradeneeded = event => initDB(event.target.result) /** * @param {any} event */ request.onerror = event => reject(error.create(event.target.error)) /** * @param {any} event */ request.onsuccess = event => { /** * @type {IDBDatabase} */ const db = event.target.result db.onversionchange = () => { db.close() } resolve(db) } }) /** * @param {string} name */ export const deleteDB = name => rtop(indexedDB.deleteDatabase(name)) /** * @param {IDBDatabase} db * @param {Array|Array>} definitions */ export const createStores = (db, definitions) => definitions.forEach(d => // @ts-ignore db.createObjectStore.apply(db, d) ) /** * @param {IDBDatabase} db * @param {Array} stores * @param {"readwrite"|"readonly"} [access] * @return {Array} */ export const transact = (db, stores, access = 'readwrite') => { const transaction = db.transaction(stores, access) return stores.map(store => getStore(transaction, store)) } /** * @param {IDBObjectStore} store * @param {IDBKeyRange} [range] * @return {Promise} */ export const count = (store, range) => rtop(store.count(range)) /** * @param {IDBObjectStore} store * @param {String | number | ArrayBuffer | Date | Array } key * @return {Promise>} */ export const get = (store, key) => rtop(store.get(key)) /** * @param {IDBObjectStore} store * @param {String | number | ArrayBuffer | Date | IDBKeyRange | Array } key */ export const del = (store, key) => rtop(store.delete(key)) /** * @param {IDBObjectStore} store * @param {String | number | ArrayBuffer | Date | boolean} item * @param {String | number | ArrayBuffer | Date | Array} [key] */ export const put = (store, item, key) => rtop(store.put(item, key)) /** * @param {IDBObjectStore} store * @param {String | number | ArrayBuffer | Date | boolean} item * @param {String | number | ArrayBuffer | Date | Array} key * @return {Promise} */ export const add = (store, item, key) => rtop(store.add(item, key)) /** * @param {IDBObjectStore} store * @param {String | number | ArrayBuffer | Date} item * @return {Promise} Returns the generated key */ export const addAutoKey = (store, item) => rtop(store.add(item)) /** * @param {IDBObjectStore} store * @param {IDBKeyRange} [range] * @param {number} [limit] * @return {Promise>} */ export const getAll = (store, range, limit) => rtop(store.getAll(range, limit)) /** * @param {IDBObjectStore} store * @param {IDBKeyRange} [range] * @param {number} [limit] * @return {Promise>} */ export const getAllKeys = (store, range, limit) => rtop(store.getAllKeys(range, limit)) /** * @param {IDBObjectStore} store * @param {IDBKeyRange|null} query * @param {'next'|'prev'|'nextunique'|'prevunique'} direction * @return {Promise} */ export const queryFirst = (store, query, direction) => { /** * @type {any} */ let first = null return iterateKeys(store, query, key => { first = key return false }, direction).then(() => first) } /** * @param {IDBObjectStore} store * @param {IDBKeyRange?} [range] * @return {Promise} */ export const getLastKey = (store, range = null) => queryFirst(store, range, 'prev') /** * @param {IDBObjectStore} store * @param {IDBKeyRange?} [range] * @return {Promise} */ export const getFirstKey = (store, range = null) => queryFirst(store, range, 'next') /** * @typedef KeyValuePair * @type {Object} * @property {any} k key * @property {any} v Value */ /** * @param {IDBObjectStore} store * @param {IDBKeyRange} [range] * @param {number} [limit] * @return {Promise>} */ export const getAllKeysValues = (store, range, limit) => // @ts-ignore promise.all([getAllKeys(store, range, limit), getAll(store, range, limit)]).then(([ks, vs]) => ks.map((k, i) => ({ k, v: vs[i] }))) /** * @param {any} request * @param {function(IDBCursorWithValue):void|boolean|Promise} f * @return {Promise} */ const iterateOnRequest = (request, f) => promise.create((resolve, reject) => { request.onerror = reject /** * @param {any} event */ request.onsuccess = async event => { const cursor = event.target.result if (cursor === null || (await f(cursor)) === false) { return resolve() } cursor.continue() } }) /** * Iterate on keys and values * @param {IDBObjectStore} store * @param {IDBKeyRange|null} keyrange * @param {function(any,any):void|boolean|Promise} f Callback that receives (value, key) * @param {'next'|'prev'|'nextunique'|'prevunique'} direction */ export const iterate = (store, keyrange, f, direction = 'next') => iterateOnRequest(store.openCursor(keyrange, direction), cursor => f(cursor.value, cursor.key)) /** * Iterate on the keys (no values) * * @param {IDBObjectStore} store * @param {IDBKeyRange|null} keyrange * @param {function(any):void|boolean|Promise} f callback that receives the key * @param {'next'|'prev'|'nextunique'|'prevunique'} direction */ export const iterateKeys = (store, keyrange, f, direction = 'next') => iterateOnRequest(store.openKeyCursor(keyrange, direction), cursor => f(cursor.key)) /** * Open store from transaction * @param {IDBTransaction} t * @param {String} store * @returns {IDBObjectStore} */ export const getStore = (t, store) => t.objectStore(store) /** * @param {any} lower * @param {any} upper * @param {boolean} lowerOpen * @param {boolean} upperOpen */ export const createIDBKeyRangeBound = (lower, upper, lowerOpen, upperOpen) => IDBKeyRange.bound(lower, upper, lowerOpen, upperOpen) /** * @param {any} upper * @param {boolean} upperOpen */ export const createIDBKeyRangeUpperBound = (upper, upperOpen) => IDBKeyRange.upperBound(upper, upperOpen) /** * @param {any} lower * @param {boolean} lowerOpen */ export const createIDBKeyRangeLowerBound = (lower, lowerOpen) => IDBKeyRange.lowerBound(lower, lowerOpen) /* c8 ignore stop */ dmonad-lib0-c7e7806/indexeddb.test.js000066400000000000000000000063761512504553500174300ustar00rootroot00000000000000import * as t from './testing.js' import * as idb from './indexeddb.js' import { isBrowser } from './environment.js' /* c8 ignore next */ /** * @param {IDBDatabase} db */ const initTestDB = db => idb.createStores(db, [['test', { autoIncrement: true }]]) const testDBName = 'idb-test' /* c8 ignore next */ /** * @param {IDBDatabase} db */ const createTransaction = db => db.transaction(['test'], 'readwrite') /* c8 ignore next */ /** * @param {IDBTransaction} t * @return {IDBObjectStore} */ const getStore = t => idb.getStore(t, 'test') /* c8 ignore next */ export const testRetrieveElements = async () => { t.skip(!isBrowser) t.describe('create, then iterate some keys') await idb.deleteDB(testDBName) const db = await idb.openDB(testDBName, initTestDB) const transaction = createTransaction(db) const store = getStore(transaction) await idb.put(store, 0, ['t', 1]) await idb.put(store, 1, ['t', 2]) const expectedKeys = [['t', 1], ['t', 2]] const expectedVals = [0, 1] const expectedKeysVals = [{ v: 0, k: ['t', 1] }, { v: 1, k: ['t', 2] }] t.describe('idb.getAll') const valsGetAll = await idb.getAll(store) t.compare(valsGetAll, expectedVals) t.describe('idb.getAllKeys') const valsGetAllKeys = await idb.getAllKeys(store) t.compare(valsGetAllKeys, expectedKeys) t.describe('idb.getAllKeysVals') const valsGetAllKeysVals = await idb.getAllKeysValues(store) t.compare(valsGetAllKeysVals, expectedKeysVals) /** * @param {string} desc * @param {IDBKeyRange?} keyrange */ const iterateTests = async (desc, keyrange) => { t.describe(`idb.iterate (${desc})`) /** * @type {Array<{v:any,k:any}>} */ const valsIterate = [] await idb.iterate(store, keyrange, (v, k) => { valsIterate.push({ v, k }) }) t.compare(valsIterate, expectedKeysVals) t.describe(`idb.iterateKeys (${desc})`) /** * @type {Array} */ const keysIterate = [] await idb.iterateKeys(store, keyrange, key => { keysIterate.push(key) }) t.compare(keysIterate, expectedKeys) } await iterateTests('range=null', null) const range = idb.createIDBKeyRangeBound(['t', 1], ['t', 2], false, false) // adding more items that should not be touched by iteration with above range await idb.put(store, 2, ['t', 3]) await idb.put(store, 2, ['t', 0]) await iterateTests('range!=null', range) t.describe('idb.get') const getV = await idb.get(store, ['t', 1]) t.assert(getV === 0) t.describe('idb.del') await idb.del(store, ['t', 0]) const getVDel = await idb.get(store, ['t', 0]) t.assert(getVDel === undefined) t.describe('idb.add') await idb.add(store, 99, 42) const idbVAdd = await idb.get(store, 42) t.assert(idbVAdd === 99) t.describe('idb.addAutoKey') const key = await idb.addAutoKey(store, 1234) const retrieved = await idb.get(store, key) t.assert(retrieved === 1234) } /* c8 ignore next */ export const testBlocked = async () => { t.skip(!isBrowser) t.describe('ignore blocked event') await idb.deleteDB(testDBName) const db = await idb.openDB(testDBName, initTestDB) const transaction = createTransaction(db) const store = getStore(transaction) await idb.put(store, 0, ['t', 1]) await idb.put(store, 1, ['t', 2]) db.close() await idb.deleteDB(testDBName) } dmonad-lib0-c7e7806/indexeddbV2.js000066400000000000000000000206711512504553500166540ustar00rootroot00000000000000/* eslint-env browser */ /** * Helpers to work with IndexedDB. * This is an experimental implementation using Pledge instead of Promise. * * @experimental * * @module indexeddbv2 */ import * as pledge from './pledge.js' /* c8 ignore start */ /** * IDB Request to Pledge transformer * * @param {pledge.PledgeInstance} p * @param {IDBRequest} request */ export const bindPledge = (p, request) => { // @ts-ignore request.onerror = event => p.cancel(event.target.error) // @ts-ignore request.onsuccess = event => p.resolve(event.target.result) } /** * @param {string} name * @param {function(IDBDatabase):any} initDB Called when the database is first created * @return {pledge.PledgeInstance} */ export const openDB = (name, initDB) => { /** * @type {pledge.PledgeInstance} */ const p = pledge.create() const request = indexedDB.open(name) /** * @param {any} event */ request.onupgradeneeded = event => initDB(event.target.result) /** * @param {any} event */ request.onerror = event => p.cancel(event.target.error) /** * @param {any} event */ request.onsuccess = event => { /** * @type {IDBDatabase} */ const db = event.target.result db.onversionchange = () => { db.close() } p.resolve(db) } return p } /** * @param {pledge.Pledge} name * @return {pledge.PledgeInstance} */ export const deleteDB = name => pledge.createWithDependencies((p, name) => bindPledge(p, indexedDB.deleteDatabase(name)), name) /** * @param {IDBDatabase} db * @param {Array|Array>} definitions */ export const createStores = (db, definitions) => definitions.forEach(d => // @ts-ignore db.createObjectStore.apply(db, d) ) /** * @param {pledge.Pledge} db * @param {pledge.Pledge>} stores * @param {"readwrite"|"readonly"} [access] * @return {pledge.Pledge>} */ export const transact = (db, stores, access = 'readwrite') => pledge.createWithDependencies((p, db, stores) => { const transaction = db.transaction(stores, access) p.resolve(stores.map(store => getStore(transaction, store))) }, db, stores) /** * @param {IDBObjectStore} store * @param {pledge.Pledge} [range] * @return {pledge.PledgeInstance} */ export const count = (store, range) => pledge.createWithDependencies((p, store, range) => bindPledge(p, store.count(range)), store, range) /** * @param {pledge.Pledge} store * @param {pledge.Pledge>} key * @return {pledge.PledgeInstance>} */ export const get = (store, key) => pledge.createWithDependencies((p, store, key) => bindPledge(p, store.get(key)), store, key) /** * @param {pledge.Pledge} store * @param {String | number | ArrayBuffer | Date | IDBKeyRange | Array } key */ export const del = (store, key) => pledge.createWithDependencies((p, store, key) => bindPledge(p, store.delete(key)), store, key) /** * @param {pledge.Pledge} store * @param {String | number | ArrayBuffer | Date | boolean} item * @param {String | number | ArrayBuffer | Date | Array} [key] */ export const put = (store, item, key) => pledge.createWithDependencies((p, store, item, key) => bindPledge(p, store.put(item, key)), store, item, key) /** * @param {pledge.Pledge} store * @param {String | number | ArrayBuffer | Date | boolean} item * @param {String | number | ArrayBuffer | Date | Array} key * @return {pledge.PledgeInstance} */ export const add = (store, item, key) => pledge.createWithDependencies((p, store, item, key) => bindPledge(p, store.add(item, key)), store, item, key) /** * @param {pledge.Pledge} store * @param {String | number | ArrayBuffer | Date} item * @return {pledge.PledgeInstance} Returns the generated key */ export const addAutoKey = (store, item) => pledge.createWithDependencies((p, store, item) => bindPledge(p, store.add(item)), store, item) /** * @param {pledge.Pledge} store * @param {IDBKeyRange} [range] * @param {number} [limit] * @return {pledge.PledgeInstance>} */ export const getAll = (store, range, limit) => pledge.createWithDependencies((p, store, range, limit) => bindPledge(p, store.getAll(range, limit)), store, range, limit) /** * @param {pledge.Pledge} store * @param {IDBKeyRange} [range] * @param {number} [limit] * @return {pledge.PledgeInstance>} */ export const getAllKeys = (store, range, limit) => pledge.createWithDependencies((p, store, range, limit) => bindPledge(p, store.getAllKeys(range, limit)), store, range, limit) /** * @param {IDBObjectStore} store * @param {IDBKeyRange|null} query * @param {'next'|'prev'|'nextunique'|'prevunique'} direction * @return {pledge.PledgeInstance} */ export const queryFirst = (store, query, direction) => { /** * @type {any} */ let first = null return iterateKeys(store, query, key => { first = key return false }, direction).map(() => first) } /** * @param {IDBObjectStore} store * @param {IDBKeyRange?} [range] * @return {pledge.PledgeInstance} */ export const getLastKey = (store, range = null) => queryFirst(store, range, 'prev') /** * @param {IDBObjectStore} store * @param {IDBKeyRange?} [range] * @return {pledge.PledgeInstance} */ export const getFirstKey = (store, range = null) => queryFirst(store, range, 'next') /** * @typedef KeyValuePair * @type {Object} * @property {any} k key * @property {any} v Value */ /** * @param {pledge.Pledge} store * @param {pledge.Pledge} [range] * @param {pledge.Pledge} [limit] * @return {pledge.PledgeInstance>} */ export const getAllKeysValues = (store, range, limit) => pledge.createWithDependencies((p, store, range, limit) => { pledge.all([getAllKeys(store, range, limit), getAll(store, range, limit)]).map(([ks, vs]) => ks.map((k, i) => ({ k, v: vs[i] }))).whenResolved(p.resolve.bind(p)) }, store, range, limit) /** * @param {pledge.PledgeInstance} p * @param {any} request * @param {function(IDBCursorWithValue):void|boolean|Promise} f */ const iterateOnRequest = (p, request, f) => { request.onerror = p.cancel.bind(p) /** * @param {any} event */ request.onsuccess = async event => { const cursor = event.target.result if (cursor === null || (await f(cursor)) === false) { p.resolve(undefined) return } cursor.continue() } } /** * Iterate on keys and values * @param {pledge.Pledge} store * @param {pledge.Pledge} keyrange * @param {function(any,any):void|boolean|Promise} f Callback that receives (value, key) * @param {'next'|'prev'|'nextunique'|'prevunique'} direction */ export const iterate = (store, keyrange, f, direction = 'next') => pledge.createWithDependencies((p, store, keyrange) => { iterateOnRequest(p, store.openCursor(keyrange, direction), cursor => f(cursor.value, cursor.key)) }, store, keyrange) /** * Iterate on the keys (no values) * * @param {pledge.Pledge} store * @param {pledge.Pledge} keyrange * @param {function(any):void|boolean|Promise} f callback that receives the key * @param {'next'|'prev'|'nextunique'|'prevunique'} direction */ export const iterateKeys = (store, keyrange, f, direction = 'next') => pledge.createWithDependencies((p, store, keyrange) => { iterateOnRequest(p, store.openKeyCursor(keyrange, direction), cursor => f(cursor.key)) }, store, keyrange) /** * Open store from transaction * @param {IDBTransaction} t * @param {String} store * @returns {IDBObjectStore} */ export const getStore = (t, store) => t.objectStore(store) /** * @param {any} lower * @param {any} upper * @param {boolean} lowerOpen * @param {boolean} upperOpen */ export const createIDBKeyRangeBound = (lower, upper, lowerOpen, upperOpen) => IDBKeyRange.bound(lower, upper, lowerOpen, upperOpen) /** * @param {any} upper * @param {boolean} upperOpen */ export const createIDBKeyRangeUpperBound = (upper, upperOpen) => IDBKeyRange.upperBound(upper, upperOpen) /** * @param {any} lower * @param {boolean} lowerOpen */ export const createIDBKeyRangeLowerBound = (lower, lowerOpen) => IDBKeyRange.lowerBound(lower, lowerOpen) /* c8 ignore stop */ dmonad-lib0-c7e7806/indexeddbV2.test.js000066400000000000000000000073321512504553500176310ustar00rootroot00000000000000import * as t from './testing.js' import * as idb from './indexeddbV2.js' import * as pledge from './pledge.js' import { isBrowser } from './environment.js' /* c8 ignore next */ /** * @param {IDBDatabase} db */ const initTestDB = db => idb.createStores(db, [['test', { autoIncrement: true }]]) const testDBName = 'idb-test' /* c8 ignore next */ /** * @param {pledge.Pledge} db */ const createTransaction = db => pledge.createWithDependencies((p, db) => p.resolve(db.transaction(['test'], 'readwrite')), db) /* c8 ignore next */ /** * @param {pledge.Pledge} t * @return {pledge.PledgeInstance} */ const getStore = t => pledge.createWithDependencies((p, t) => p.resolve(idb.getStore(t, 'test')), t) /* c8 ignore next */ export const testRetrieveElements = async () => { t.skip(!isBrowser) t.describe('create, then iterate some keys') await idb.deleteDB(testDBName).promise() const db = idb.openDB(testDBName, initTestDB) const transaction = createTransaction(db) const store = getStore(transaction) await idb.put(store, 0, ['t', 1]).promise() await idb.put(store, 1, ['t', 2]).promise() const expectedKeys = [['t', 1], ['t', 2]] const expectedVals = [0, 1] const expectedKeysVals = [{ v: 0, k: ['t', 1] }, { v: 1, k: ['t', 2] }] t.describe('idb.getAll') const valsGetAll = await idb.getAll(store).promise() t.compare(valsGetAll, expectedVals) t.describe('idb.getAllKeys') const valsGetAllKeys = await idb.getAllKeys(store).promise() t.compare(valsGetAllKeys, expectedKeys) t.describe('idb.getAllKeysVals') const valsGetAllKeysVals = await idb.getAllKeysValues(store).promise() t.compare(valsGetAllKeysVals, expectedKeysVals) /** * @param {string} desc * @param {IDBKeyRange?} keyrange */ const iterateTests = async (desc, keyrange) => { t.describe(`idb.iterate (${desc})`) /** * @type {Array<{v:any,k:any}>} */ const valsIterate = [] await idb.iterate(store, keyrange, (v, k) => { valsIterate.push({ v, k }) }).promise() t.compare(valsIterate, expectedKeysVals) t.describe(`idb.iterateKeys (${desc})`) /** * @type {Array} */ const keysIterate = [] await idb.iterateKeys(store, keyrange, key => { keysIterate.push(key) }).promise() t.compare(keysIterate, expectedKeys) } await iterateTests('range=null', null) const range = idb.createIDBKeyRangeBound(['t', 1], ['t', 2], false, false) // adding more items that should not be touched by iteration with above range await idb.put(store, 2, ['t', 3]).promise() await idb.put(store, 2, ['t', 0]).promise() await iterateTests('range!=null', range) t.describe('idb.get') const getV = await idb.get(store, ['t', 1]).promise() t.assert(getV === 0) t.describe('idb.del') await idb.del(store, ['t', 0]).promise() const getVDel = await idb.get(store, ['t', 0]).promise() t.assert(getVDel === undefined) t.describe('idb.add') await idb.add(store, 99, 42).promise() const idbVAdd = await idb.get(store, 42).promise() t.assert(idbVAdd === 99) t.describe('idb.addAutoKey') const key = await idb.addAutoKey(store, 1234).promise() const retrieved = await idb.get(store, key).promise() t.assert(retrieved === 1234) } /* c8 ignore next */ export const testBlocked = async () => { t.skip(!isBrowser) t.describe('ignore blocked event') await idb.deleteDB(testDBName).map(() => { const db = idb.openDB(testDBName, initTestDB) const transaction = createTransaction(db) const store = getStore(transaction) return pledge.all({ _req1: idb.put(store, 0, ['t', 1]), _req2: idb.put(store, 1, ['t', 2]), db }) }).map(({ db }) => { db.close() return idb.deleteDB(testDBName) }).promise() } dmonad-lib0-c7e7806/isomorphic.js000066400000000000000000000003021512504553500166570ustar00rootroot00000000000000/** * Isomorphic library exports from isomorphic.js. * * @todo remove this module * @deprecated * * @module isomorphic */ export { performance, cryptoRandomBuffer } from 'isomorphic.js' dmonad-lib0-c7e7806/iterator.js000066400000000000000000000023161512504553500163430ustar00rootroot00000000000000/** * Utility module to create and manipulate Iterators. * * @module iterator */ /** * @template T,R * @param {Iterator} iterator * @param {function(T):R} f * @return {IterableIterator} */ export const mapIterator = (iterator, f) => ({ [Symbol.iterator] () { return this }, // @ts-ignore next () { const r = iterator.next() return { value: r.done ? undefined : f(r.value), done: r.done } } }) /** * @template T * @param {function():IteratorResult} next * @return {IterableIterator} */ export const createIterator = next => ({ /** * @return {IterableIterator} */ [Symbol.iterator] () { return this }, // @ts-ignore next }) /** * @template T * @param {Iterator} iterator * @param {function(T):boolean} filter */ export const iteratorFilter = (iterator, filter) => createIterator(() => { let res do { res = iterator.next() } while (!res.done && !filter(res.value)) return res }) /** * @template T,M * @param {Iterator} iterator * @param {function(T):M} fmap */ export const iteratorMap = (iterator, fmap) => createIterator(() => { const { done, value } = iterator.next() return { done, value: done ? undefined : fmap(value) } }) dmonad-lib0-c7e7806/json.js000066400000000000000000000004501512504553500154600ustar00rootroot00000000000000/** * JSON utility functions. * * @module json */ /** * Transform JavaScript object to JSON. * * @param {any} object * @return {string} */ export const stringify = JSON.stringify /** * Parse JSON object. * * @param {string} json * @return {any} */ export const parse = JSON.parse dmonad-lib0-c7e7806/list.js000066400000000000000000000101731512504553500154650ustar00rootroot00000000000000import { id } from './function.js' import * as error from './error.js' import * as equalityTrait from './trait/equality.js' import * as f from './function.js' export class ListNode { constructor () { /** * @type {this|null} */ this.next = null /** * @type {this|null} */ this.prev = null } } /** * @template {ListNode} N */ export class List { constructor () { /** * @type {N | null} */ this.start = null /** * @type {N | null} */ this.end = null this.len = 0 } * [Symbol.iterator] () { let n = this.start while (n) { yield n n = n.next } } toArray () { return map(this, f.id) } /** * @param {function(N):any} f */ forEach (f) { forEach(this, f) } /** * @template M * @param {function(N):M} f * @return {Array} */ map (f) { return map(this, f) } /** * @param {List} other */ [equalityTrait.EqualityTraitSymbol] (other) { let n = this.start let m = other.start while (n && m) { if (!equalityTrait.equals(n, m)) return false n = n.next m = m.next } return n === m // only true iff n == null && m == null } } /** * @note The queue implementation is experimental and unfinished. * Don't use this in production yet. * * @template {ListNode} N * * @return {List} */ export const create = () => new List() /** * @template {ListNode} N * * @param {List} queue */ export const isEmpty = queue => queue.start === null /** * Remove a single node from the queue. Only works with Queues that operate on Doubly-linked lists of nodes. * * @template {ListNode} N * * @param {List} list * @param {N} node */ export const remove = (list, node) => { const prev = node.prev const next = node.next if (prev) { prev.next = next } else { list.start = next } if (next) { next.prev = prev } else { list.end = prev } list.len-- return node } /** * @deprecated @todo remove in next major release */ export const removeNode = remove /** * @template {ListNode} N * * @param {List} queue * @param {N| null} left * @param {N| null} right * @param {N} node */ export const insertBetween = (queue, left, right, node) => { /* c8 ignore start */ if (left != null && left.next !== right) { throw error.unexpectedCase() } /* c8 ignore stop */ if (left) { left.next = node } else { queue.start = node } if (right) { right.prev = node } else { queue.end = node } node.prev = left node.next = right queue.len++ } /** * Remove a single node from the queue. Only works with Queues that operate on Doubly-linked lists of nodes. * * @template {ListNode} N * * @param {List} queue * @param {N} node * @param {N} newNode */ export const replace = (queue, node, newNode) => { insertBetween(queue, node, node.next, newNode) remove(queue, node) } /** * @template {ListNode} N * * @param {List} queue * @param {N} n */ export const pushEnd = (queue, n) => insertBetween(queue, queue.end, null, n) /** * @template {ListNode} N * * @param {List} queue * @param {N} n */ export const pushFront = (queue, n) => insertBetween(queue, null, queue.start, n) /** * @template {ListNode} N * * @param {List} list * @return {N| null} */ export const popFront = list => list.start ? removeNode(list, list.start) : null /** * @template {ListNode} N * * @param {List} list * @return {N| null} */ export const popEnd = list => list.end ? removeNode(list, list.end) : null /** * @template {ListNode} N * @template M * * @param {List} list * @param {function(N):M} f * @return {Array} */ export const map = (list, f) => { /** * @type {Array} */ const arr = [] let n = list.start while (n) { arr.push(f(n)) n = n.next } return arr } /** * @template {ListNode} N * * @param {List} list */ export const toArray = list => map(list, id) /** * @template {ListNode} N * @param {List} list * @param {function(N):any} f */ export const forEach = (list, f) => { let n = list.start while (n) { f(n) n = n.next } } dmonad-lib0-c7e7806/list.test.js000066400000000000000000000055741512504553500164540ustar00rootroot00000000000000import * as t from './testing.js' import * as list from './list.js' import * as equalityTrait from './trait/equality.js' /** * @template [out V=number] */ class QueueItem extends list.ListNode { /** * @param {V} v */ constructor (v) { super() this.v = v } /** * @param {QueueItem} other */ [equalityTrait.EqualityTraitSymbol] (other) { return this.v === other.v } } /** * @param {t.TestCase} _tc */ export const testAssignability = _tc => { /** * @type {list.List>} */ let l1 = list.create() /** * @type {list.List>} */ let l2 = list.create() l2 = l1 // @ts-expect-error l1 = l2 } /** * @param {t.TestCase} _tc */ export const testEnqueueDequeue = _tc => { const N = 30 /** * @type {list.List} */ const q = list.create() t.assert(list.isEmpty(q)) t.assert(list.popFront(q) === null) for (let i = 0; i < N; i++) { list.pushEnd(q, new QueueItem(i)) t.assert(!list.isEmpty(q)) } for (let i = 0; i < N; i++) { const item = /** @type {QueueItem} */ (list.popFront(q)) t.assert(item !== null && item.v === i) } t.assert(list.isEmpty(q)) t.assert(list.popFront(q) === null) for (let i = 0; i < N; i++) { list.pushEnd(q, new QueueItem(i)) t.assert(!list.isEmpty(q)) } for (let i = 0; i < N; i++) { const item = /** @type {QueueItem} */ (list.popFront(q)) t.assert(item !== null && item.v === i) } t.assert(list.isEmpty(q)) t.assert(list.popFront(q) === null) } /** * @param {t.TestCase} _tc */ export const testSelectivePop = _tc => { /** * @type {list.List} */ const l = list.create() list.pushFront(l, new QueueItem(1)) const q3 = new QueueItem(3) list.pushEnd(l, q3) const middleNode = new QueueItem(2) list.insertBetween(l, l.start, l.end, middleNode) list.replace(l, q3, new QueueItem(4)) t.compare(list.map(l, n => n.v), [1, 2, 4]) t.compare(l.map(n => n.v), [1, 2, 4]) t.compare(list.toArray(l).map(n => n.v), [1, 2, 4]) t.compare(l.toArray().map(n => n.v), [1, 2, 4]) { let cnt = 0 list.forEach(l, () => cnt++) t.assert(cnt === l.len) } t.assert(l.len === 3) t.assert(list.remove(l, middleNode) === middleNode) t.assert(l.len === 2) t.compare(/** @type {QueueItem} */ (list.popEnd(l)).v, 4) t.assert(l.len === 1) t.compare(/** @type {QueueItem} */ (list.popEnd(l)).v, 1) t.assert(l.len === 0) t.compare(list.popEnd(l), null) t.assert(l.start === null) t.assert(l.end === null) t.assert(l.len === 0) } export const testEquality = () => { /** * @type {list.List} */ const a = list.create() /** * @type {list.List} */ const b = list.create() list.pushEnd(a, new QueueItem(1)) list.pushEnd(b, new QueueItem(1)) t.compare(a, b) list.pushFront(b, new QueueItem(0)) t.fails(() => { t.compare(a, b) }) } dmonad-lib0-c7e7806/logging.common.js000066400000000000000000000057701512504553500174360ustar00rootroot00000000000000import * as symbol from './symbol.js' import * as time from './time.js' import * as env from './environment.js' import * as func from './function.js' import * as json from './json.js' export const BOLD = symbol.create() export const UNBOLD = symbol.create() export const BLUE = symbol.create() export const GREY = symbol.create() export const GREEN = symbol.create() export const RED = symbol.create() export const PURPLE = symbol.create() export const ORANGE = symbol.create() export const UNCOLOR = symbol.create() /* c8 ignore start */ /** * @param {Array} args * @return {Array} */ export const computeNoColorLoggingArgs = args => { if (args.length === 1 && args[0]?.constructor === Function) { args = /** @type {Array} */ (/** @type {[function]} */ (args)[0]()) } const strBuilder = [] const logArgs = [] // try with formatting until we find something unsupported let i = 0 for (; i < args.length; i++) { const arg = args[i] if (arg === undefined) { break } else if (arg.constructor === String || arg.constructor === Number) { strBuilder.push(arg) } else if (arg.constructor === Object) { break } } if (i > 0) { // create logArgs with what we have so far logArgs.push(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 */ const loggingColors = [GREEN, PURPLE, ORANGE, BLUE] let nextColor = 0 let lastLoggingTime = time.getUnixTime() /* c8 ignore start */ /** * @param {function(...any):void} _print * @param {string} moduleName * @return {function(...any):void} */ export const createModuleLogger = (_print, moduleName) => { const color = loggingColors[nextColor] const debugRegexVar = env.getVariable('log') const doLogging = debugRegexVar !== null && (debugRegexVar === '*' || debugRegexVar === 'true' || new RegExp(debugRegexVar, 'gi').test(moduleName)) nextColor = (nextColor + 1) % loggingColors.length moduleName += ': ' return !doLogging ? func.nop : (...args) => { if (args.length === 1 && args[0]?.constructor === Function) { args = args[0]() } const timeNow = time.getUnixTime() const timeDiff = timeNow - lastLoggingTime lastLoggingTime = timeNow _print( color, moduleName, UNCOLOR, ...args.map((arg) => { if (arg != null && arg.constructor === Uint8Array) { arg = Array.from(arg) } const t = typeof arg switch (t) { case 'string': case 'symbol': return arg default: { return json.stringify(arg) } } }), color, ' +' + timeDiff + 'ms' ) } } /* c8 ignore stop */ dmonad-lib0-c7e7806/logging.js000066400000000000000000000231411512504553500161370ustar00rootroot00000000000000/** * Isomorphic logging module with support for colors! * * @module logging */ import * as env from './environment.js' import * as set from './set.js' import * as pair from './pair.js' import * as dom from './dom.js' import * as json from './json.js' import * as map from './map.js' import * as eventloop from './eventloop.js' import * as math from './math.js' import * as common from './logging.common.js' export { BOLD, UNBOLD, BLUE, GREY, GREEN, RED, PURPLE, ORANGE, UNCOLOR } from './logging.common.js' /** * @type {Object>} */ const _browserStyleMap = { [common.BOLD]: pair.create('font-weight', 'bold'), [common.UNBOLD]: pair.create('font-weight', 'normal'), [common.BLUE]: pair.create('color', 'blue'), [common.GREEN]: pair.create('color', 'green'), [common.GREY]: pair.create('color', 'grey'), [common.RED]: pair.create('color', 'red'), [common.PURPLE]: pair.create('color', 'purple'), [common.ORANGE]: pair.create('color', 'orange'), // not well supported in chrome when debugging node with inspector - TODO: deprecate [common.UNCOLOR]: pair.create('color', 'black') } /** * @param {Array} args * @return {Array} */ /* c8 ignore start */ const computeBrowserLoggingArgs = (args) => { if (args.length === 1 && args[0]?.constructor === Function) { args = /** @type {Array} */ (/** @type {[function]} */ (args)[0]()) } const strBuilder = [] const styles = [] const currentStyle = map.create() /** * @type {Array} */ 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 === undefined) { break } if (arg.constructor === String || arg.constructor === Number) { const style = dom.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 = env.supportsColor ? computeBrowserLoggingArgs : common.computeNoColorLoggingArgs /* c8 ignore stop */ /** * @param {Array} args */ export const print = (...args) => { console.log(...computeLoggingArgs(args)) /* c8 ignore next */ vconsoles.forEach((vc) => vc.print(args)) } /* c8 ignore start */ /** * @param {Array} args */ export const warn = (...args) => { console.warn(...computeLoggingArgs(args)) args.unshift(common.ORANGE) vconsoles.forEach((vc) => vc.print(args)) } /* c8 ignore stop */ /** * @param {Error} err */ /* c8 ignore start */ export 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 */ export const printImg = (url, height) => { if (env.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 */ export const printImgBase64 = (base64, height) => printImg(`data:image/gif;base64,${base64}`, height) /** * @param {Array} args */ export const group = (...args) => { console.group(...computeLoggingArgs(args)) /* c8 ignore next */ vconsoles.forEach((vc) => vc.group(args)) } /** * @param {Array} args */ export const groupCollapsed = (...args) => { console.groupCollapsed(...computeLoggingArgs(args)) /* c8 ignore next */ vconsoles.forEach((vc) => vc.groupCollapsed(args)) } export const groupEnd = () => { console.groupEnd() /* c8 ignore next */ vconsoles.forEach((vc) => vc.groupEnd()) } /** * @param {function():Node} createNode */ /* c8 ignore next 2 */ export const printDom = (createNode) => vconsoles.forEach((vc) => vc.printDom(createNode())) /** * @param {HTMLCanvasElement} canvas * @param {number} height */ /* c8 ignore next 2 */ export const printCanvas = (canvas, height) => printImg(canvas.toDataURL(), height) export const vconsoles = set.create() /** * @param {Array} args * @return {Array} */ /* 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++) { let arg = args[i] // @ts-ignore const style = _browserStyleMap[arg] if (style !== undefined) { currentStyle.set(style.left, style.right) } else { if (arg === undefined) { arg = 'undefined ' } if (arg.constructor === String || arg.constructor === Number) { // @ts-ignore const span = dom.element('span', [ pair.create('style', dom.mapToStyleString(currentStyle)) ], [dom.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 = ' ' + json.stringify(content) + ' ' } spans.push( dom.element('span', [], [dom.text(/** @type {string} */ (content))]) ) } } return spans } /* c8 ignore stop */ const lineStyle = 'font-family:monospace;border-bottom:1px solid #e2e2e2;padding:2px;' /* c8 ignore start */ export class VConsole { /** * @param {Element} dom */ constructor (dom) { this.dom = dom /** * @type {Element} */ this.ccontainer = this.dom this.depth = 0 vconsoles.add(this) } /** * @param {Array} args * @param {boolean} collapsed */ group (args, collapsed = false) { eventloop.enqueue(() => { const triangleDown = dom.element('span', [ pair.create('hidden', collapsed), pair.create('style', 'color:grey;font-size:120%;') ], [dom.text('▼')]) const triangleRight = dom.element('span', [ pair.create('hidden', !collapsed), pair.create('style', 'color:grey;font-size:125%;') ], [dom.text('▶')]) const content = dom.element( 'div', [pair.create( 'style', `${lineStyle};padding-left:${this.depth * 10}px` )], [triangleDown, triangleRight, dom.text(' ')].concat( _computeLineSpans(args) ) ) const nextContainer = dom.element('div', [ pair.create('hidden', collapsed) ]) const nextLine = dom.element('div', [], [content, nextContainer]) dom.append(this.ccontainer, [nextLine]) this.ccontainer = nextContainer this.depth++ // when header is clicked, collapse/uncollapse container dom.addEventListener(content, 'click', (_event) => { nextContainer.toggleAttribute('hidden') triangleDown.toggleAttribute('hidden') triangleRight.toggleAttribute('hidden') }) }) } /** * @param {Array} args */ groupCollapsed (args) { this.group(args, true) } groupEnd () { eventloop.enqueue(() => { if (this.depth > 0) { this.depth-- // @ts-ignore this.ccontainer = this.ccontainer.parentElement.parentElement } }) } /** * @param {Array} args */ print (args) { eventloop.enqueue(() => { dom.append(this.ccontainer, [ dom.element('div', [ pair.create( 'style', `${lineStyle};padding-left:${this.depth * 10}px` ) ], _computeLineSpans(args)) ]) }) } /** * @param {Error} err */ printError (err) { this.print([common.RED, common.BOLD, err.toString()]) } /** * @param {string} url * @param {number} height */ printImg (url, height) { eventloop.enqueue(() => { dom.append(this.ccontainer, [ dom.element('img', [ pair.create('src', url), pair.create('height', `${math.round(height * 1.5)}px`) ]) ]) }) } /** * @param {Node} node */ printDom (node) { eventloop.enqueue(() => { dom.append(this.ccontainer, [node]) }) } destroy () { eventloop.enqueue(() => { vconsoles.delete(this) }) } } /* c8 ignore stop */ /** * @param {Element} dom */ /* c8 ignore next */ export const createVConsole = (dom) => new VConsole(dom) /** * @param {string} moduleName * @return {function(...any):void} */ export const createModuleLogger = (moduleName) => common.createModuleLogger(print, moduleName) dmonad-lib0-c7e7806/logging.node.js000066400000000000000000000074751512504553500170770ustar00rootroot00000000000000/** * Isomorphic logging module with support for colors! * * @module logging */ import * as env from './environment.js' import * as common from './logging.common.js' export { BOLD, UNBOLD, BLUE, GREY, GREEN, RED, PURPLE, ORANGE, UNCOLOR } from './logging.common.js' const _nodeStyleMap = { [common.BOLD]: '\u001b[1m', [common.UNBOLD]: '\u001b[2m', [common.BLUE]: '\x1b[34m', [common.GREEN]: '\x1b[32m', [common.GREY]: '\u001b[37m', [common.RED]: '\x1b[31m', [common.PURPLE]: '\x1b[35m', [common.ORANGE]: '\x1b[38;5;208m', [common.UNCOLOR]: '\x1b[0m' } /* c8 ignore start */ /** * @param {Array>} args * @return {Array} */ const computeNodeLoggingArgs = (args) => { if (args.length === 1 && args[0]?.constructor === Function) { args = /** @type {Array} */ (/** @type {[function]} */ (args)[0]()) } const strBuilder = [] const 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 = _nodeStyleMap[arg] if (style !== undefined) { strBuilder.push(style) } else { if (arg === undefined) { break } else if (arg.constructor === String || arg.constructor === Number) { strBuilder.push(arg) } else { break } } } if (i > 0) { // create logArgs with what we have so far strBuilder.push('\x1b[0m') logArgs.push(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 = env.supportsColor ? computeNodeLoggingArgs : common.computeNoColorLoggingArgs /* c8 ignore stop */ /** * @param {Array} args */ export const print = (...args) => { console.log(...computeLoggingArgs(args)) } /* c8 ignore start */ /** * @param {Array} args */ export const warn = (...args) => { console.warn(...computeLoggingArgs(args)) } /* c8 ignore stop */ /** * @param {Error} err */ /* c8 ignore start */ export const printError = (err) => { console.error(err) } /* c8 ignore stop */ /** * @param {string} _url image location * @param {number} _height height of the image in pixel */ /* c8 ignore start */ export const printImg = (_url, _height) => { // console.log('%c ', `font-size: ${height}x; background: url(${url}) no-repeat;`) } /* c8 ignore stop */ /** * @param {string} base64 * @param {number} height */ /* c8 ignore next 2 */ export const printImgBase64 = (base64, height) => printImg(`data:image/gif;base64,${base64}`, height) /** * @param {Array} args */ /* c8 ignore next 3 */ export const group = (...args) => { console.group(...computeLoggingArgs(args)) } /** * @param {Array} args */ /* c8 ignore next 3 */ export const groupCollapsed = (...args) => { console.groupCollapsed(...computeLoggingArgs(args)) } /* c8 ignore next 3 */ export const groupEnd = () => { console.groupEnd() } /** * @param {function():Node} _createNode */ /* c8 ignore next 2 */ export const printDom = (_createNode) => {} /** * @param {HTMLCanvasElement} canvas * @param {number} height */ /* c8 ignore next 2 */ export const printCanvas = (canvas, height) => printImg(canvas.toDataURL(), height) /** * @param {Element} _dom */ /* c8 ignore next */ export const createVConsole = (_dom) => {} /** * @param {string} moduleName * @return {function(...any):void} */ /* c8 ignore next */ export const createModuleLogger = (moduleName) => common.createModuleLogger(print, moduleName) dmonad-lib0-c7e7806/logging.test.js000066400000000000000000000023001512504553500171070ustar00rootroot00000000000000import * as log from 'lib0/logging' export const testLogging = () => { log.print(log.BLUE, 'blue ') log.print(log.BLUE, 'blue ', log.BOLD, 'blue,bold') log.print(log.GREEN, log.RED, 'red ', 'red') log.print(log.ORANGE, 'orange') log.print(log.BOLD, 'bold ', log.UNBOLD, 'nobold') log.print(log.GREEN, 'green ', log.UNCOLOR, 'nocolor') log.print('expecting objects from now on!') log.print({ 'my-object': 'isLogged' }) log.print(log.GREEN, 'green ', { 'my-object': 'isLogged' }) log.print(log.GREEN, 'green ', { 'my-object': 'isLogged' }, 'unformatted') log.print(log.BLUE, log.BOLD, 'number', 1) log.print(log.BLUE, log.BOLD, 'number', 1, {}, 's', 2) log.print({}, 'dtrn') log.print(() => [log.GREEN, 'can lazyprint stuff ', log.RED, 'with formatting']) log.print(undefined, 'supports undefined') } export const testModuleLogger = () => { // if you want to see the messages, enable logging: LOG=* npm run test --filter logging const mlog = log.createModuleLogger('testing') mlog('can print ', log.GREEN, 'with colors') mlog(() => ['can lazyprint ', log.GREEN, 'with colors']) mlog(undefined, 'supports undefined') mlog(() => [undefined, 'supports lazyprint undefined']) } dmonad-lib0-c7e7806/map.js000066400000000000000000000043531512504553500152720ustar00rootroot00000000000000/** * Utility module to work with key-value stores. * * @module map */ /** * @template K * @template V * @typedef {Map} GlobalMap */ /** * Creates a new Map instance. * * @function * @return {Map} * * @function */ export const create = () => new Map() /** * Copy a Map object into a fresh Map object. * * @function * @template K,V * @param {Map} m * @return {Map} */ export const copy = m => { const r = create() 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 {Map} MAP * @template {MAP extends Map ? function():V : unknown} CF * @param {MAP} map * @param {MAP extends Map ? K : unknown} key * @param {CF} createT * @return {ReturnType} */ export 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} m * @param {function(V,K):R} f * @return {Array} */ export const map = (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} m * @param {function(V,K):boolean} f * @return {boolean} */ export const any = (m, f) => { for (const [key, value] of m) { if (f(value, key)) { return true } } return false } /** * Tests whether all key-value pairs pass the test implemented by `f(value, key)`. * * @function * @template K * @template V * @param {Map} m * @param {function(V,K):boolean} f * @return {boolean} */ export const all = (m, f) => { for (const [key, value] of m) { if (!f(value, key)) { return false } } return true } dmonad-lib0-c7e7806/map.test.js000066400000000000000000000024711512504553500162470ustar00rootroot00000000000000import * as map from './map.js' import * as math from './math.js' import * as t from './testing.js' /** * @param {t.TestCase} _tc */ export const testMap = _tc => { /** * @type {Map} */ const m = map.create() m.set(1, 2) m.set(2, 3) t.assert(map.map(m, (value, key) => value * 2 + key).reduce(math.add) === 13) let numberOfWrites = 0 const createT = () => ++numberOfWrites map.setIfUndefined(m, 3, createT) map.setIfUndefined(m, 3, createT) map.setIfUndefined(m, 3, createT) t.compare(map.copy(m), m) t.assert(numberOfWrites === 1) t.assert(map.any(m, (v, k) => k === 1 && v === 2)) t.assert(map.any(m, (v, k) => k === 2 && v === 3)) t.assert(!map.any(m, () => false)) t.assert(!map.all(m, (v, k) => k === 1 && v === 2)) t.assert(map.all(m, (v) => v === 2 || v === 3 || v === numberOfWrites)) } /** * @param {t.TestCase} _tc */ export const testTypeDefinitions = _tc => { // setIfUndefined supports inheritance properly: See https://github.com/dmonad/lib0/issues/82 class A { constructor () { this.a = 4 } } class B extends A { constructor () { super() this.b = 4 } } /** * @type {Map} */ const m = map.create() /** * @type {B} */ const b = map.setIfUndefined(m, 0, () => new B()) console.log(b) } dmonad-lib0-c7e7806/math.js000066400000000000000000000026111512504553500154410ustar00rootroot00000000000000/** * Common Math expressions. * * @module math */ export const floor = Math.floor export const ceil = Math.ceil export const abs = Math.abs export const imul = Math.imul export const round = Math.round export const log10 = Math.log10 export const log2 = Math.log2 export const log = Math.log export const sqrt = Math.sqrt /** * @function * @param {number} a * @param {number} b * @return {number} The sum of a and b */ export const add = (a, b) => a + b /** * @function * @param {number} a * @param {number} b * @return {number} The smaller element of a and b */ export const min = (a, b) => a < b ? a : b /** * @function * @param {number} a * @param {number} b * @return {number} The bigger element of a and b */ export const max = (a, b) => a > b ? a : b export const isNaN = Number.isNaN export const pow = Math.pow /** * Base 10 exponential function. Returns the value of 10 raised to the power of pow. * * @param {number} exp * @return {number} */ export const exp10 = exp => Math.pow(10, exp) export const sign = Math.sign /** * Check whether n is negative, while considering the -0 edge case. While `-0 < 0` is false, this * function returns true for -0,-1,,.. and returns false for 0,1,2,... * @param {number} n * @return {boolean} Wether n is negative. This function also distinguishes between -0 and +0 */ export const isNegativeZero = n => n !== 0 ? n < 0 : 1 / n < 0 dmonad-lib0-c7e7806/math.test.js000066400000000000000000000020231512504553500164140ustar00rootroot00000000000000import * as t from './testing.js' import * as math from './math.js' import * as array from './array.js' /** * @param {t.TestCase} tc */ export const testMath = tc => { t.describe('math.abs') t.assert(math.abs(-1) === 1) t.assert(math.abs(Number.MIN_SAFE_INTEGER) === Number.MAX_SAFE_INTEGER) t.assert(math.abs(Number.MAX_SAFE_INTEGER) === Number.MAX_SAFE_INTEGER) t.describe('math.add') t.assert(array.fold([1, 2, 3, 4, 5], 0, math.add) === 15) t.describe('math.ceil') t.assert(math.ceil(1.5) === 2) t.assert(math.ceil(-1.5) === -1) t.describe('math.floor') t.assert(math.floor(1.5) === 1) t.assert(math.floor(-1.5) === -2) t.describe('math.isNaN') t.assert(math.isNaN(NaN)) // @ts-ignore t.assert(!math.isNaN(null)) t.describe('math.max') t.assert([1, 3, 65, 1, 314, 25, 3475, 2, 1].reduce(math.max) === 3475) t.describe('math.min') t.assert([1, 3, 65, 1, 314, 25, 3475, 2, 1].reduce(math.min) === 1) t.describe('math.round') t.assert(math.round(0.5) === 1) t.assert(math.round(-0.5) === 0) } dmonad-lib0-c7e7806/metric.js000066400000000000000000000030341512504553500157730ustar00rootroot00000000000000/** * Utility module to convert metric values. * * @module metric */ import * as math from './math.js' export const yotta = 1e24 export const zetta = 1e21 export const exa = 1e18 export const peta = 1e15 export const tera = 1e12 export const giga = 1e9 export const mega = 1e6 export const kilo = 1e3 export const hecto = 1e2 export const deca = 10 export const deci = 0.1 export const centi = 0.01 export const milli = 1e-3 export const micro = 1e-6 export const nano = 1e-9 export const pico = 1e-12 export const femto = 1e-15 export const atto = 1e-18 export const zepto = 1e-21 export const yocto = 1e-24 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}} */ export const prefix = (n, baseMultiplier = 0) => { const nPow = n === 0 ? 0 : math.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: math.round((mult > 0 ? n / math.exp10(mult * 3) : n * math.exp10(mult * -3)) * 1e12) / 1e12, prefix } } dmonad-lib0-c7e7806/metric.test.js000066400000000000000000000031341512504553500167520ustar00rootroot00000000000000import * as t from './testing.js' import * as metric from './metric.js' /** * @param {t.TestCase} tc */ export const testMetricPrefix = tc => { t.compare(metric.prefix(0), { n: 0, prefix: '' }) t.compare(metric.prefix(1, -1), { n: 1, prefix: 'm' }) t.compare(metric.prefix(1.5), { n: 1.5, prefix: '' }) t.compare(metric.prefix(100.5), { n: 100.5, prefix: '' }) t.compare(metric.prefix(1000.5), { n: 1.0005, prefix: 'k' }) t.compare(metric.prefix(0.3), { n: 300, prefix: 'm' }) t.compare(metric.prefix(0.001), { n: 1, prefix: 'm' }) // up t.compare(metric.prefix(10000), { n: 10, prefix: 'k' }) t.compare(metric.prefix(1e7), { n: 10, prefix: 'M' }) t.compare(metric.prefix(1e11), { n: 100, prefix: 'G' }) t.compare(metric.prefix(1e12 + 3), { n: (1e12 + 3) / 1e12, prefix: 'T' }) t.compare(metric.prefix(1e15), { n: 1, prefix: 'P' }) t.compare(metric.prefix(1e20), { n: 100, prefix: 'E' }) t.compare(metric.prefix(1e22), { n: 10, prefix: 'Z' }) t.compare(metric.prefix(1e24), { n: 1, prefix: 'Y' }) t.compare(metric.prefix(1e28), { n: 10000, prefix: 'Y' }) // down t.compare(metric.prefix(0.01), { n: 10, prefix: 'm' }) t.compare(metric.prefix(1e-4), { n: 100, prefix: 'μ' }) t.compare(metric.prefix(1e-9), { n: 1, prefix: 'n' }) t.compare(metric.prefix(1e-12), { n: 1, prefix: 'p' }) t.compare(metric.prefix(1e-14), { n: 10, prefix: 'f' }) t.compare(metric.prefix(1e-18), { n: 1, prefix: 'a' }) t.compare(metric.prefix(1e-21), { n: 1, prefix: 'z' }) t.compare(metric.prefix(1e-22), { n: 100, prefix: 'y' }) t.compare(metric.prefix(1e-30), { n: 0.000001, prefix: 'y' }) } dmonad-lib0-c7e7806/mutex.js000066400000000000000000000015311512504553500156520ustar00rootroot00000000000000/** * Mutual exclude for JavaScript. * * @module mutex */ /** * @callback mutex * @param {function():void} cb Only executed when this mutex is not in the current stack * @param {function():void} [elseCb] Executed when this mutex is in the current stack */ /** * Creates a mutual exclude function with the following property: * * ```js * const mutex = createMutex() * mutex(() => { * // This function is immediately executed * mutex(() => { * // This function is not executed, as the mutex is already active. * }) * }) * ``` * * @return {mutex} A mutual exclude function * @public */ export const createMutex = () => { let token = true return (f, g) => { if (token) { token = false try { f() } finally { token = true } } else if (g !== undefined) { g() } } } dmonad-lib0-c7e7806/mutex.test.js000066400000000000000000000006141512504553500166310ustar00rootroot00000000000000import * as t from './testing.js' import * as mutex from './mutex.js' /** * @param {t.TestCase} _tc */ export const testMutex = _tc => { const mux = mutex.createMutex() let res = '' mux(() => { res += '1' mux(() => { res += 'Y' }, () => { res += '2' mux(() => { res += '3' }) }) }, () => { res += 'X' }) t.assert(res === '12') } dmonad-lib0-c7e7806/number.js000066400000000000000000000015641512504553500160060ustar00rootroot00000000000000/** * Utility helpers for working with numbers. * * @module number */ import * as math from './math.js' import * as binary from './binary.js' export const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER export const MIN_SAFE_INTEGER = Number.MIN_SAFE_INTEGER export const LOWEST_INT32 = 1 << 31 export const HIGHEST_INT32 = binary.BITS31 export const HIGHEST_UINT32 = binary.BITS32 /* c8 ignore next */ export const isInteger = Number.isInteger || (num => typeof num === 'number' && isFinite(num) && math.floor(num) === num) export const isNaN = Number.isNaN export const parseInt = Number.parseInt /** * Count the number of "1" bits in an unsigned 32bit number. * * Super fun bitcount algorithm by Brian Kernighan. * * @param {number} n */ export const countBits = n => { n &= binary.BITS32 let count = 0 while (n) { n &= (n - 1) count++ } return count } dmonad-lib0-c7e7806/number.test.js000066400000000000000000000036651512504553500167700ustar00rootroot00000000000000import * as t from './testing.js' import * as number from './number.js' import * as random from './random.js' import * as math from './math.js' /** * @param {t.TestCase} _tc */ export const testNumber = _tc => { t.describe('isNaN') t.assert(number.isNaN(NaN)) t.assert(!number.isNaN(1 / 0)) // @ts-ignore t.assert(number.isNaN('a' / 0)) t.assert(!number.isNaN(0)) t.describe('isInteger') t.assert(!number.isInteger(1 / 0)) t.assert(!number.isInteger(NaN)) t.assert(number.isInteger(0)) t.assert(number.isInteger(-1)) t.assert(number.countBits(1) === 1) t.assert(number.countBits(3) === 2) t.assert(number.countBits(128 + 3) === 3) } /** * This benchmark confirms performance of division vs shifting numbers. * * @param {t.TestCase} tc */ export const testShiftVsDivision = tc => { /** * @type {Array} */ const numbers = [] for (let i = 0; i < 10000; i++) { numbers.push(random.uint32()) } t.measureTime('comparison', () => { for (let i = 0; i < numbers.length; i++) { let n = numbers[i] while (n > 0) { const ns = n >>> 7 const nd = math.floor(n / 128) t.assert(ns === nd) n = nd } } }) t.measureTime('shift', () => { let x = 0 for (let i = 0; i < numbers.length; i++) { x = numbers[i] >>> 7 } t.info('' + x) }) t.measureTime('division', () => { for (let i = 0; i < numbers.length; i++) { math.floor(numbers[i] / 128) } }) { /** * @type {Array} */ const divided = [] /** * @type {Array} */ const shifted = [] t.measureTime('division', () => { for (let i = 0; i < numbers.length; i++) { divided.push(math.floor(numbers[i] / 128)) } }) t.measureTime('shift', () => { for (let i = 0; i < numbers.length; i++) { shifted.push(numbers[i] >>> 7) } }) t.compareArrays(shifted, divided) } } dmonad-lib0-c7e7806/object.js000066400000000000000000000067131512504553500157650ustar00rootroot00000000000000import * as equalityTrait from './trait/equality.js' /** * Utility functions for working with EcmaScript objects. * * @module object */ /** * @return {Object} obj */ export const create = () => Object.create(null) /** * @param {any} o * @return {o is { [k:string]:any }} */ export const isObject = o => typeof o === 'object' /** * Object.assign */ export const assign = Object.assign /** * @param {Object} obj */ export const keys = Object.keys /** * @template V * @param {{[key:string]: V}} obj * @return {Array} */ export const values = Object.values /** * @template V * @param {{[k:string]:V}} obj * @param {function(V,string):any} f */ export const forEach = (obj, f) => { for (const key in obj) { f(obj[key], key) } } /** * @todo implement mapToArray & map * * @template R * @param {Object} obj * @param {function(any,string):R} f * @return {Array} */ export const map = (obj, f) => { const results = [] for (const key in obj) { results.push(f(obj[key], key)) } return results } /** * @deprecated use object.size instead * @param {Object} obj * @return {number} */ export const length = obj => keys(obj).length /** * @param {Object} obj * @return {number} */ export const size = obj => keys(obj).length /** * @template {{ [key:string|number|symbol]: any }} T * @param {T} obj * @param {(v:T[keyof T],k:keyof T)=>boolean} f * @return {boolean} */ export const some = (obj, f) => { for (const key in obj) { if (f(obj[key], key)) { return true } } return false } /** * @param {Object|null|undefined} obj */ export const isEmpty = obj => { // eslint-disable-next-line no-unreachable-loop for (const _k in obj) { return false } return true } /** * @template {{ [key:string|number|symbol]: any }} T * @param {T} obj * @param {(v:T[keyof T],k:keyof T)=>boolean} f * @return {boolean} */ export 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|number|symbol} key * @return {boolean} */ export const hasProperty = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key) /** * @param {Object} a * @param {Object} b * @return {boolean} */ export const equalFlat = (a, b) => a === b || (size(a) === size(b) && every(a, (val, key) => (val !== undefined || hasProperty(b, key)) && equalityTrait.equals(b[key], val))) /** * Make an object immutable. This hurts performance and is usually not needed if you perform good * coding practices. */ export const freeze = Object.freeze /** * Make an object and all its children immutable. * This *really* hurts performance and is usually not needed if you perform good coding practices. * * @template {any} T * @param {T} o * @return {Readonly} */ export const deepFreeze = (o) => { for (const key in o) { const c = o[key] if (typeof c === 'object' || typeof c === 'function') { deepFreeze(o[key]) } } return freeze(o) } /** * Get object property. Create T if property is undefined and set T on object. * * @function * @template {object} KV * @template {keyof KV} [K=keyof KV] * @param {KV} o * @param {K} key * @param {() => KV[K]} createT * @return {KV[K]} */ export const setIfUndefined = (o, key, createT) => hasProperty(o, key) ? o[key] : (o[key] = createT()) dmonad-lib0-c7e7806/object.test.js000066400000000000000000000057151512504553500167440ustar00rootroot00000000000000import * as t from './testing.js' import * as object from './object.js' import * as math from './math.js' export const testEqualFlat = () => { t.assert(!object.equalFlat({ fontFamily: 'MSYahei' }, { fontFamily: null })) } /** * @param {t.TestCase} _tc */ export const testObject = _tc => { t.assert(object.create().constructor === undefined, 'object.create creates an empty object without constructor') t.describe('object.equalFlat') t.assert(object.equalFlat({}, {}), 'comparing equal objects') t.assert(object.equalFlat({ x: 1 }, { x: 1 }), 'comparing equal objects') t.assert(object.equalFlat({ x: 'dtrn' }, { x: 'dtrn' }), 'comparing equal objects') t.assert(!object.equalFlat({ x: {} }, { x: {} }), 'flatEqual does not dive deep') t.assert(object.equalFlat({ x: undefined }, { x: undefined }), 'flatEqual handles undefined') t.assert(!object.equalFlat({ x: undefined }, { y: {} }), 'flatEqual handles undefined') t.describe('object.every') // @ts-expect-error k has no overlap with "c" t.assert(object.every({ a: 1, b: 3 }, (v, k) => (v % 2) === 1 && k !== 'c')) t.assert(!object.every({ a: 1, b: 3, c: 5 }, (v, k) => (v % 2) === 1 && k !== 'c')) t.describe('object.some') t.assert(object.some({ a: 1, b: 3 }, (v, k) => v === 3 && k === 'b')) t.assert(!object.some({ a: 1, b: 5 }, (v, _k) => v === 3)) t.assert(object.some({ a: 1, b: 5 }, () => true)) t.assert(!object.some({ a: 1, b: 5 }, (_v, _k) => false)) t.describe('object.forEach') let forEachSum = 0 const r = { x: 1, y: 3 } object.forEach(r, (v, _k) => { forEachSum += v }) t.assert(forEachSum === 4) t.describe('object.map') t.assert(object.map({ x: 1, z: 5 }, (v, _k) => v).reduce(math.add) === 6) t.describe('object.length') t.assert(object.length({}) === 0) t.assert(object.length({ x: 1 }) === 1) t.assert(object.isEmpty({})) t.assert(!object.isEmpty({ a: 3 })) t.assert(object.isEmpty(null)) t.assert(object.isEmpty(undefined)) /** * @type {Array} */ const keys = object.keys({ a: 1, b: 2 }) t.compare(keys, ['a', 'b']) /** * @type {Array} */ const vals = object.values({ a: 1 }) t.compare(vals, [1]) } /** * @param {t.TestCase} _tc */ export const testFreeze = _tc => { const o1 = { a: { b: [1, 2, 3] } } const o2 = [1, 2, { a: 'hi' }] object.deepFreeze(o1) object.deepFreeze(o2) t.fails(() => { o1.a.b.push(4) }) t.fails(() => { o1.a.b = [1] }) t.fails(() => { o2.push(4) }) t.fails(() => { o2[2] = 42 }) t.fails(() => { // @ts-ignore-next-line o2[2].a = 'hello' }) } /** * @param {t.TestCase} _tc */ export const testSetifundefined = _tc => { const o = { a: 42, b: '42' } object.setIfUndefined(o, 'a', () => 43) object.setIfUndefined(o, 'b', () => '43') /** * @type {{ [key: number]: string}} */ const o2 = {} object.setIfUndefined(o2, 42, () => '52') /** * @type {{ [key: string]: number}} */ const o3 = {} object.setIfUndefined(o3, '42', () => 52) } dmonad-lib0-c7e7806/observable.js000066400000000000000000000071401512504553500166360ustar00rootroot00000000000000/** * Observable class prototype. * * @module observable */ import * as map from './map.js' import * as set from './set.js' import * as array from './array.js' /** * Handles named events. * @experimental * * This is basically a (better typed) duplicate of Observable, which will replace Observable in the * next release. * * @template {{[key in keyof EVENTS]: function(...any):void}} EVENTS */ export class ObservableV2 { constructor () { /** * Some desc. * @type {Map>} */ this._observers = map.create() } /** * @template {keyof EVENTS & string} NAME * @param {NAME} name * @param {EVENTS[NAME]} f */ on (name, f) { map.setIfUndefined(this._observers, /** @type {string} */ (name), set.create).add(f) return f } /** * @template {keyof EVENTS & string} NAME * @param {NAME} name * @param {EVENTS[NAME]} f */ once (name, f) { /** * @param {...any} args */ const _f = (...args) => { this.off(name, /** @type {any} */ (_f)) f(...args) } this.on(name, /** @type {any} */ (_f)) } /** * @template {keyof EVENTS & string} NAME * @param {NAME} name * @param {EVENTS[NAME]} f */ off (name, f) { const observers = this._observers.get(name) if (observers !== undefined) { observers.delete(f) if (observers.size === 0) { this._observers.delete(name) } } } /** * Emit a named event. All registered event listeners that listen to the * specified name will receive the event. * * @todo This should catch exceptions * * @template {keyof EVENTS & string} NAME * @param {NAME} name The event name. * @param {Parameters} args The arguments that are applied to the event listener. */ emit (name, args) { // copy all listeners to an array first to make sure that no event is emitted to listeners that are subscribed while the event handler is called. return array.from((this._observers.get(name) || map.create()).values()).forEach(f => f(...args)) } destroy () { this._observers = map.create() } } /* c8 ignore start */ /** * Handles named events. * * @deprecated * @template N */ export class Observable { constructor () { /** * Some desc. * @type {Map} */ this._observers = map.create() } /** * @param {N} name * @param {function} f */ on (name, f) { map.setIfUndefined(this._observers, name, set.create).add(f) } /** * @param {N} name * @param {function} f */ once (name, f) { /** * @param {...any} args */ const _f = (...args) => { this.off(name, _f) f(...args) } this.on(name, _f) } /** * @param {N} name * @param {function} f */ off (name, f) { const observers = this._observers.get(name) if (observers !== undefined) { observers.delete(f) if (observers.size === 0) { this._observers.delete(name) } } } /** * Emit a named event. All registered event listeners that listen to the * specified name will receive the event. * * @todo This should catch exceptions * * @param {N} name The event name. * @param {Array} args The arguments that are applied to the event listener. */ emit (name, args) { // copy all listeners to an array first to make sure that no event is emitted to listeners that are subscribed while the event handler is called. return array.from((this._observers.get(name) || map.create()).values()).forEach(f => f(...args)) } destroy () { this._observers = map.create() } } /* c8 ignore end */ dmonad-lib0-c7e7806/observable.test.js000066400000000000000000000023161512504553500176140ustar00rootroot00000000000000import * as t from './testing.js' import { ObservableV2 } from './observable.js' /** * @param {t.TestCase} _tc */ export const testTypedObservable = _tc => { /** * @type {ObservableV2<{ "hey": function(number, string):any, listen: function(string):any }>} */ const o = new ObservableV2() let calls = 0 /** * Test "hey" */ /** * @param {number} n * @param {string} s */ const listener = (n, s) => { t.assert(typeof n === 'number') t.assert(typeof s === 'string') calls++ } o.on('hey', listener) o.on('hey', (arg1) => t.assert(typeof arg1 === 'number')) // o.emit('hey', ['four']) // should emit type error // o.emit('hey', [4]) // should emit type error o.emit('hey', [4, 'four']) t.assert(calls === 1) o.emit('hey', [5, 'five']) t.assert(calls === 2) o.off('hey', listener) o.emit('hey', [6, 'six']) t.assert(calls === 2) /** * Test "listen" */ o.once('listen', n => { t.assert(typeof n === 'string') calls++ }) // o.emit('listen', [4]) // should emit type error o.emit('listen', ['four']) o.emit('listen', ['five']) // shouldn't trigger t.assert(calls === 3) o.destroy() o.emit('hey', [7, 'seven']) t.assert(calls === 3) } dmonad-lib0-c7e7806/package-lock.json000066400000000000000000004230151512504553500173730ustar00rootroot00000000000000{ "name": "lib0", "version": "0.2.117", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "lib0", "version": "0.2.117", "license": "MIT", "dependencies": { "isomorphic.js": "^0.2.4" }, "bin": { "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js", "0gentesthtml": "bin/gentesthtml.js", "0serve": "bin/0serve.js" }, "devDependencies": { "@types/node": "^24.0.14", "c8": "^10.1.3", "jsdoc-api": "^8.0.0", "jsdoc-plugin-typescript": "^2.2.1", "rollup": "^2.42.1", "standard": "^17.1.0", "typescript": "^5.9.3" }, "engines": { "node": ">=16" }, "funding": { "type": "GitHub Sponsors ❤", "url": "https://github.com/sponsors/dmonad" } }, "node_modules/@aashutoshrathi/word-wrap": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/@babel/parser": { "version": "7.21.3", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.3.tgz", "integrity": "sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==", "dev": true, "bin": { "parser": "bin/babel-parser.js" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@bcoe/v8-coverage": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", "dev": true, "license": "MIT", "engines": { "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", "dev": true, "dependencies": { "eslint-visitor-keys": "^3.3.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "node_modules/@eslint-community/regexpp": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz", "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.1.tgz", "integrity": "sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/@eslint/js": { "version": "8.46.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.46.0.tgz", "integrity": "sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", "minimatch": "^3.0.5" }, "engines": { "node": ">=10.10.0" } }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, "engines": { "node": ">=12.22" }, "funding": { "type": "github", "url": "https://github.com/sponsors/nzakas" } }, "node_modules/@humanwhocodes/object-schema": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, "license": "ISC", "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "engines": { "node": ">=12" } }, "node_modules/@isaacs/cliui/node_modules/ansi-regex": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, "license": "MIT", "engines": { "node": ">=12" }, "funding": { "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/@isaacs/cliui/node_modules/ansi-styles": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, "license": "MIT", "engines": { "node": ">=12" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/@isaacs/cliui/node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, "license": "MIT" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" }, "engines": { "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@isaacs/cliui/node_modules/strip-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, "engines": { "node": ">=12" }, "funding": { "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" }, "engines": { "node": ">=12" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", "dev": true, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "dev": true }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.17", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "3.1.0", "@jridgewell/sourcemap-codec": "1.4.14" } }, "node_modules/@jsdoc/salty": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.5.tgz", "integrity": "sha512-TfRP53RqunNe2HBobVBJ0VLhK1HbfvBYeTC1ahnN64PWvyYyGebmMiPkuwvD9fpw2ZbkoPb8Q7mwy0aR8Z9rvw==", "dev": true, "dependencies": { "lodash": "^4.17.21" }, "engines": { "node": ">=v12.0.0" } }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" }, "engines": { "node": ">= 8" } }, "node_modules/@nodelib/fs.stat": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, "engines": { "node": ">= 8" } }, "node_modules/@nodelib/fs.walk": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" }, "engines": { "node": ">= 8" } }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, "license": "MIT", "optional": true, "engines": { "node": ">=14" } }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", "dev": true }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, "node_modules/@types/linkify-it": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", "dev": true }, "node_modules/@types/markdown-it": { "version": "12.2.3", "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", "dev": true, "dependencies": { "@types/linkify-it": "*", "@types/mdurl": "*" } }, "node_modules/@types/mdurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", "dev": true }, "node_modules/@types/node": { "version": "24.0.14", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.14.tgz", "integrity": "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw==", "dev": true, "license": "MIT", "dependencies": { "undici-types": "~7.8.0" } }, "node_modules/acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "dev": true, "bin": { "acorn": "bin/acorn" }, "engines": { "node": ">=0.4.0" } }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" }, "funding": { "type": "github", "url": "https://github.com/sponsors/epoberezkin" } }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "engines": { "node": ">=8" } }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { "color-convert": "^2.0.1" }, "engines": { "node": ">=8" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, "node_modules/array-back": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz", "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==", "dev": true, "engines": { "node": ">=12.17" } }, "node_modules/array-buffer-byte-length": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "is-array-buffer": "^3.0.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/array-includes": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", "es-abstract": "^1.20.4", "get-intrinsic": "^1.1.3", "is-string": "^1.0.7" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/array.prototype.findlastindex": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.2.tgz", "integrity": "sha512-tb5thFFlUcp7NdNF6/MpDk/1r/4awWG1FIz3YqDf+/zJSTezBb+/5WViH41obXULHVpDzoiCLpJ/ZO9YbJMsdw==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", "es-abstract": "^1.20.4", "es-shim-unscopables": "^1.0.0", "get-intrinsic": "^1.1.3" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/array.prototype.flat": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", "es-abstract": "^1.20.4", "es-shim-unscopables": "^1.0.0" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/array.prototype.flatmap": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", "es-abstract": "^1.20.4", "es-shim-unscopables": "^1.0.0" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/array.prototype.tosorted": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz", "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", "es-abstract": "^1.20.4", "es-shim-unscopables": "^1.0.0", "get-intrinsic": "^1.1.3" } }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", "dev": true, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "node_modules/builtins": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", "dev": true, "dependencies": { "semver": "^7.0.0" } }, "node_modules/builtins/node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" }, "bin": { "semver": "bin/semver.js" }, "engines": { "node": ">=10" } }, "node_modules/c8": { "version": "10.1.3", "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz", "integrity": "sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==", "dev": true, "license": "ISC", "dependencies": { "@bcoe/v8-coverage": "^1.0.1", "@istanbuljs/schema": "^0.1.3", "find-up": "^5.0.0", "foreground-child": "^3.1.1", "istanbul-lib-coverage": "^3.2.0", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.1.6", "test-exclude": "^7.0.1", "v8-to-istanbul": "^9.0.0", "yargs": "^17.7.2", "yargs-parser": "^21.1.1" }, "bin": { "c8": "bin/c8.js" }, "engines": { "node": ">=18" }, "peerDependencies": { "monocart-coverage-reports": "^2" }, "peerDependenciesMeta": { "monocart-coverage-reports": { "optional": true } } }, "node_modules/cache-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/cache-point/-/cache-point-2.0.0.tgz", "integrity": "sha512-4gkeHlFpSKgm3vm2gJN5sPqfmijYRFYCQ6tv5cLw0xVmT6r1z1vd4FNnpuOREco3cBs1G709sZ72LdgddKvL5w==", "dev": true, "dependencies": { "array-back": "^4.0.1", "fs-then-native": "^2.0.0", "mkdirp2": "^1.0.4" }, "engines": { "node": ">=8" } }, "node_modules/cache-point/node_modules/array-back": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", "dev": true, "engines": { "node": ">=8" } }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", "dev": true, "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, "engines": { "node": ">=6" } }, "node_modules/catharsis": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", "dev": true, "dependencies": { "lodash": "^4.17.15" }, "engines": { "node": ">= 10" } }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" }, "engines": { "node": ">=12" } }, "node_modules/collect-all": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/collect-all/-/collect-all-1.0.4.tgz", "integrity": "sha512-RKZhRwJtJEP5FWul+gkSMEnaK6H3AGPTTWOiRimCcs+rc/OmQE3Yhy1Q7A7KsdkG3ZXVdZq68Y6ONSdvkeEcKA==", "dev": true, "dependencies": { "stream-connect": "^1.0.2", "stream-via": "^1.0.4" }, "engines": { "node": ">=0.10.0" } }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "dependencies": { "color-name": "~1.1.4" }, "engines": { "node": ">=7.0.0" } }, "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" }, "engines": { "node": ">= 8" } }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { "ms": "2.1.2" }, "engines": { "node": ">=6.0" }, "peerDependenciesMeta": { "supports-color": { "optional": true } } }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, "node_modules/define-properties": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", "dev": true, "dependencies": { "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "dependencies": { "esutils": "^2.0.2" }, "engines": { "node": ">=6.0.0" } }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, "license": "MIT" }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, "node_modules/entities": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", "dev": true, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, "dependencies": { "is-arrayish": "^0.2.1" } }, "node_modules/es-abstract": { "version": "1.21.2", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.0", "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", "es-set-tostringtag": "^2.0.1", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.5", "get-intrinsic": "^1.2.0", "get-symbol-description": "^1.0.0", "globalthis": "^1.0.3", "gopd": "^1.0.1", "has": "^1.0.3", "has-property-descriptors": "^1.0.0", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "internal-slot": "^1.0.5", "is-array-buffer": "^3.0.2", "is-callable": "^1.2.7", "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", "is-typed-array": "^1.1.10", "is-weakref": "^1.0.2", "object-inspect": "^1.12.3", "object-keys": "^1.1.1", "object.assign": "^4.1.4", "regexp.prototype.flags": "^1.4.3", "safe-regex-test": "^1.0.0", "string.prototype.trim": "^1.2.7", "string.prototype.trimend": "^1.0.6", "string.prototype.trimstart": "^1.0.6", "typed-array-length": "^1.0.4", "unbox-primitive": "^1.0.2", "which-typed-array": "^1.1.9" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/es-set-tostringtag": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", "dev": true, "dependencies": { "get-intrinsic": "^1.1.3", "has": "^1.0.3", "has-tostringtag": "^1.0.0" }, "engines": { "node": ">= 0.4" } }, "node_modules/es-shim-unscopables": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", "dev": true, "dependencies": { "has": "^1.0.3" } }, "node_modules/es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, "dependencies": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", "is-symbol": "^1.0.2" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/escape-string-regexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, "engines": { "node": ">=8" } }, "node_modules/eslint": { "version": "8.46.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.46.0.tgz", "integrity": "sha512-cIO74PvbW0qU8e0mIvk5IV3ToWdCq5FYG6gWPHHkx6gNdjlbAYvtfHmlCMXxjcoVaIdwy/IAt3+mDkZkfvb2Dg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.1", "@eslint/js": "^8.46.0", "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.2", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-config-standard": { "version": "17.1.0", "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz", "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==", "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/feross" }, { "type": "patreon", "url": "https://www.patreon.com/feross" }, { "type": "consulting", "url": "https://feross.org/support" } ], "engines": { "node": ">=12.0.0" }, "peerDependencies": { "eslint": "^8.0.1", "eslint-plugin-import": "^2.25.2", "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", "eslint-plugin-promise": "^6.0.0" } }, "node_modules/eslint-config-standard-jsx": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/eslint-config-standard-jsx/-/eslint-config-standard-jsx-11.0.0.tgz", "integrity": "sha512-+1EV/R0JxEK1L0NGolAr8Iktm3Rgotx3BKwgaX+eAuSX8D952LULKtjgZD3F+e6SvibONnhLwoTi9DPxN5LvvQ==", "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/feross" }, { "type": "patreon", "url": "https://www.patreon.com/feross" }, { "type": "consulting", "url": "https://feross.org/support" } ], "peerDependencies": { "eslint": "^8.8.0", "eslint-plugin-react": "^7.28.0" } }, "node_modules/eslint-import-resolver-node": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", "dev": true, "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", "resolve": "^1.22.4" } }, "node_modules/eslint-import-resolver-node/node_modules/debug": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "dependencies": { "ms": "^2.1.1" } }, "node_modules/eslint-module-utils": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", "dev": true, "dependencies": { "debug": "^3.2.7" }, "engines": { "node": ">=4" }, "peerDependenciesMeta": { "eslint": { "optional": true } } }, "node_modules/eslint-module-utils/node_modules/debug": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "dependencies": { "ms": "^2.1.1" } }, "node_modules/eslint-plugin-es": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", "dev": true, "dependencies": { "eslint-utils": "^2.0.0", "regexpp": "^3.0.0" }, "engines": { "node": ">=8.10.0" }, "funding": { "url": "https://github.com/sponsors/mysticatea" }, "peerDependencies": { "eslint": ">=4.19.1" } }, "node_modules/eslint-plugin-es/node_modules/eslint-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "dev": true, "dependencies": { "eslint-visitor-keys": "^1.1.0" }, "engines": { "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/mysticatea" } }, "node_modules/eslint-plugin-es/node_modules/eslint-visitor-keys": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true, "engines": { "node": ">=4" } }, "node_modules/eslint-plugin-import": { "version": "2.28.0", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.28.0.tgz", "integrity": "sha512-B8s/n+ZluN7sxj9eUf7/pRFERX0r5bnFA2dCaLHy2ZeaQEAz0k+ZZkFWRFHJAqxfxQDx6KLv9LeIki7cFdwW+Q==", "dev": true, "dependencies": { "array-includes": "^3.1.6", "array.prototype.findlastindex": "^1.2.2", "array.prototype.flat": "^1.3.1", "array.prototype.flatmap": "^1.3.1", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.7", "eslint-module-utils": "^2.8.0", "has": "^1.0.3", "is-core-module": "^2.12.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", "object.fromentries": "^2.0.6", "object.groupby": "^1.0.0", "object.values": "^1.1.6", "resolve": "^1.22.3", "semver": "^6.3.1", "tsconfig-paths": "^3.14.2" }, "engines": { "node": ">=4" }, "peerDependencies": { "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" } }, "node_modules/eslint-plugin-import/node_modules/debug": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "dependencies": { "ms": "^2.1.1" } }, "node_modules/eslint-plugin-import/node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "dependencies": { "esutils": "^2.0.2" }, "engines": { "node": ">=0.10.0" } }, "node_modules/eslint-plugin-n": { "version": "15.7.0", "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.7.0.tgz", "integrity": "sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==", "dev": true, "dependencies": { "builtins": "^5.0.1", "eslint-plugin-es": "^4.1.0", "eslint-utils": "^3.0.0", "ignore": "^5.1.1", "is-core-module": "^2.11.0", "minimatch": "^3.1.2", "resolve": "^1.22.1", "semver": "^7.3.8" }, "engines": { "node": ">=12.22.0" }, "funding": { "url": "https://github.com/sponsors/mysticatea" }, "peerDependencies": { "eslint": ">=7.0.0" } }, "node_modules/eslint-plugin-n/node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" }, "bin": { "semver": "bin/semver.js" }, "engines": { "node": ">=10" } }, "node_modules/eslint-plugin-promise": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0" } }, "node_modules/eslint-plugin-react": { "version": "7.33.1", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.1.tgz", "integrity": "sha512-L093k0WAMvr6VhNwReB8VgOq5s2LesZmrpPdKz/kZElQDzqS7G7+DnKoqT+w4JwuiGeAhAvHO0fvy0Eyk4ejDA==", "dev": true, "dependencies": { "array-includes": "^3.1.6", "array.prototype.flatmap": "^1.3.1", "array.prototype.tosorted": "^1.1.1", "doctrine": "^2.1.0", "estraverse": "^5.3.0", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.6", "object.fromentries": "^2.0.6", "object.hasown": "^1.1.2", "object.values": "^1.1.6", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.4", "semver": "^6.3.1", "string.prototype.matchall": "^4.0.8" }, "engines": { "node": ">=4" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" } }, "node_modules/eslint-plugin-react/node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "dependencies": { "esutils": "^2.0.2" }, "engines": { "node": ">=0.10.0" } }, "node_modules/eslint-plugin-react/node_modules/resolve": { "version": "2.0.0-next.4", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", "dev": true, "dependencies": { "is-core-module": "^2.9.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/eslint-scope": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-utils": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", "dev": true, "dependencies": { "eslint-visitor-keys": "^2.0.0" }, "engines": { "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" }, "funding": { "url": "https://github.com/sponsors/mysticatea" }, "peerDependencies": { "eslint": ">=5" } }, "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true, "engines": { "node": ">=10" } }, "node_modules/eslint-visitor-keys": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz", "integrity": "sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/esquery": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "dependencies": { "estraverse": "^5.1.0" }, "engines": { "node": ">=0.10" } }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "dependencies": { "estraverse": "^5.2.0" }, "engines": { "node": ">=4.0" } }, "node_modules/estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "engines": { "node": ">=4.0" } }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, "node_modules/fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", "dev": true, "dependencies": { "reusify": "^1.0.4" } }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "dependencies": { "flat-cache": "^3.0.4" }, "engines": { "node": "^10.12.0 || >=12.0.0" } }, "node_modules/file-set": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/file-set/-/file-set-4.0.2.tgz", "integrity": "sha512-fuxEgzk4L8waGXaAkd8cMr73Pm0FxOVkn8hztzUW7BAHhOGH90viQNXbiOsnecCWmfInqU6YmAMwxRMdKETceQ==", "dev": true, "dependencies": { "array-back": "^5.0.0", "glob": "^7.1.6" }, "engines": { "node": ">=10" } }, "node_modules/file-set/node_modules/array-back": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/array-back/-/array-back-5.0.0.tgz", "integrity": "sha512-kgVWwJReZWmVuWOQKEOohXKJX+nD02JAZ54D1RRWlv8L0NebauKAaFxACKzB74RTclt1+WNz5KHaLRDAPZbDEw==", "dev": true, "engines": { "node": ">=10" } }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "dev": true, "dependencies": { "flatted": "^3.1.0", "rimraf": "^3.0.2" }, "engines": { "node": "^10.12.0 || >=12.0.0" } }, "node_modules/flatted": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", "dev": true, "dependencies": { "is-callable": "^1.1.3" } }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, "license": "ISC", "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" }, "engines": { "node": ">=14" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/fs-then-native": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fs-then-native/-/fs-then-native-2.0.0.tgz", "integrity": "sha512-X712jAOaWXkemQCAmWeg5rOT2i+KOpWz1Z/txk/cW0qlOu2oQ9H61vc5w3X/iyuUEfq/OyaFJ78/cZAQD1/bgA==", "dev": true, "engines": { "node": ">=4.0.0" } }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, "hasInstallScript": true, "optional": true, "os": [ "darwin" ], "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, "node_modules/function.prototype.name": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", "es-abstract": "^1.19.0", "functions-have-names": "^1.2.2" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/functions-have-names": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } }, "node_modules/get-intrinsic": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", "dev": true, "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", "has-proto": "^1.0.1", "has-symbols": "^1.0.3" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/get-stdin": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", "dev": true, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.1" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" }, "engines": { "node": "*" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "dependencies": { "is-glob": "^4.0.3" }, "engines": { "node": ">=10.13.0" } }, "node_modules/globals": { "version": "13.20.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" }, "engines": { "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/globalthis": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", "dev": true, "dependencies": { "define-properties": "^1.1.3" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "dev": true }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "dependencies": { "function-bind": "^1.1.1" }, "engines": { "node": ">= 0.4.0" } }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "engines": { "node": ">=8" } }, "node_modules/has-property-descriptors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", "dev": true, "dependencies": { "get-intrinsic": "^1.1.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", "dev": true, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "dev": true, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-tostringtag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true, "license": "MIT" }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true, "engines": { "node": ">= 4" } }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" }, "engines": { "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "engines": { "node": ">=0.8.19" } }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, "node_modules/internal-slot": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", "dev": true, "dependencies": { "get-intrinsic": "^1.2.0", "has": "^1.0.3", "side-channel": "^1.0.4" }, "engines": { "node": ">= 0.4" } }, "node_modules/is-array-buffer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.0", "is-typed-array": "^1.1.10" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, "node_modules/is-bigint": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", "dev": true, "dependencies": { "has-bigints": "^1.0.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-boolean-object": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-core-module": { "version": "2.13.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", "dev": true, "dependencies": { "has": "^1.0.3" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-date-object": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, "engines": { "node": ">=0.10.0" } }, "node_modules/is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", "dev": true, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-number-object": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, "engines": { "node": ">=8" } }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-shared-array-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", "dev": true, "dependencies": { "call-bind": "^1.0.2" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-string": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-symbol": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-typed-array": { "version": "1.1.10", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", "for-each": "^0.3.3", "gopd": "^1.0.1", "has-tostringtag": "^1.0.0" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-weakref": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", "dev": true, "dependencies": { "call-bind": "^1.0.2" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, "node_modules/isomorphic.js": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz", "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==", "funding": { "type": "GitHub Sponsors ❤", "url": "https://github.com/sponsors/dmonad" } }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=8" } }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" }, "engines": { "node": ">=10" } }, "node_modules/istanbul-reports": { "version": "3.1.7", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" }, "engines": { "node": ">=8" } }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, "funding": { "url": "https://github.com/sponsors/isaacs" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "node_modules/js2xmlparser": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", "dev": true, "dependencies": { "xmlcreate": "^2.0.4" } }, "node_modules/jsdoc": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.2.tgz", "integrity": "sha512-e8cIg2z62InH7azBBi3EsSEqrKx+nUtAS5bBcYTSpZFA+vhNPyhv8PTFZ0WsjOPDj04/dOLlm08EDcQJDqaGQg==", "dev": true, "dependencies": { "@babel/parser": "^7.20.15", "@jsdoc/salty": "^0.2.1", "@types/markdown-it": "^12.2.3", "bluebird": "^3.7.2", "catharsis": "^0.9.0", "escape-string-regexp": "^2.0.0", "js2xmlparser": "^4.0.2", "klaw": "^3.0.0", "markdown-it": "^12.3.2", "markdown-it-anchor": "^8.4.1", "marked": "^4.0.10", "mkdirp": "^1.0.4", "requizzle": "^0.2.3", "strip-json-comments": "^3.1.0", "underscore": "~1.13.2" }, "bin": { "jsdoc": "jsdoc.js" }, "engines": { "node": ">=12.0.0" } }, "node_modules/jsdoc-api": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/jsdoc-api/-/jsdoc-api-8.0.0.tgz", "integrity": "sha512-Rnhor0suB1Ds1abjmFkFfKeD+kSMRN9oHMTMZoJVUrmtCGDwXty+sWMA9sa4xbe4UyxuPjhC7tavZ40mDKK6QQ==", "dev": true, "dependencies": { "array-back": "^6.2.2", "cache-point": "^2.0.0", "collect-all": "^1.0.4", "file-set": "^4.0.2", "fs-then-native": "^2.0.0", "jsdoc": "^4.0.0", "object-to-spawn-args": "^2.0.1", "temp-path": "^1.0.0", "walk-back": "^5.1.0" }, "engines": { "node": ">=12.17" } }, "node_modules/jsdoc-plugin-typescript": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/jsdoc-plugin-typescript/-/jsdoc-plugin-typescript-2.2.1.tgz", "integrity": "sha512-xxuiqJ1O5+KIoOd8G8+iIZ89ns/ZvuzerrhCQhLPmeSIVsv5Ra42D9/YOHPi2DndUJbbkEpJCB95i7EXIOmhnA==", "dev": true, "dependencies": { "string.prototype.matchall": "^4.0.0" } }, "node_modules/json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, "node_modules/json5": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", "dev": true, "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" }, "engines": { "node": ">=4.0" } }, "node_modules/klaw": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", "dev": true, "dependencies": { "graceful-fs": "^4.1.9" } }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" }, "engines": { "node": ">= 0.8.0" } }, "node_modules/linkify-it": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", "dev": true, "dependencies": { "uc.micro": "^1.0.1" } }, "node_modules/load-json-file": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-5.3.0.tgz", "integrity": "sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==", "dev": true, "dependencies": { "graceful-fs": "^4.1.15", "parse-json": "^4.0.0", "pify": "^4.0.1", "strip-bom": "^3.0.0", "type-fest": "^0.3.0" }, "engines": { "node": ">=6" } }, "node_modules/load-json-file/node_modules/type-fest": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", "dev": true, "engines": { "node": ">=6" } }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "dependencies": { "p-locate": "^5.0.0" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "dev": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "dependencies": { "yallist": "^4.0.0" }, "engines": { "node": ">=10" } }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "license": "MIT", "dependencies": { "semver": "^7.5.3" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/make-dir/node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" }, "engines": { "node": ">=10" } }, "node_modules/markdown-it": { "version": "12.3.2", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", "dev": true, "dependencies": { "argparse": "^2.0.1", "entities": "~2.1.0", "linkify-it": "^3.0.1", "mdurl": "^1.0.1", "uc.micro": "^1.0.5" }, "bin": { "markdown-it": "bin/markdown-it.js" } }, "node_modules/markdown-it-anchor": { "version": "8.6.7", "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", "dev": true, "peerDependencies": { "@types/markdown-it": "*", "markdown-it": "*" } }, "node_modules/marked": { "version": "4.2.12", "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz", "integrity": "sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==", "dev": true, "bin": { "marked": "bin/marked.js" }, "engines": { "node": ">= 12" } }, "node_modules/mdurl": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", "dev": true }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, "engines": { "node": "*" } }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true, "bin": { "mkdirp": "bin/cmd.js" }, "engines": { "node": ">=10" } }, "node_modules/mkdirp2": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/mkdirp2/-/mkdirp2-1.0.5.tgz", "integrity": "sha512-xOE9xbICroUDmG1ye2h4bZ8WBie9EGmACaco8K8cx6RlkJJrxGIqjGqztAI+NMhexXBcdGbSEzI6N3EJPevxZw==", "dev": true }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true, "engines": { "node": ">= 0.4" } }, "node_modules/object-to-spawn-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/object-to-spawn-args/-/object-to-spawn-args-2.0.1.tgz", "integrity": "sha512-6FuKFQ39cOID+BMZ3QaphcC8Y4cw6LXBLyIgPU+OhIYwviJamPAn+4mITapnSBQrejB+NNp+FMskhD8Cq+Ys3w==", "dev": true, "engines": { "node": ">=8.0.0" } }, "node_modules/object.assign": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", "has-symbols": "^1.0.3", "object-keys": "^1.1.1" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/object.entries": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", "es-abstract": "^1.20.4" }, "engines": { "node": ">= 0.4" } }, "node_modules/object.fromentries": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", "es-abstract": "^1.20.4" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/object.groupby": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.0.tgz", "integrity": "sha512-70MWG6NfRH9GnbZOikuhPPYzpUpof9iW2J9E4dW7FXTqPNb6rllE6u39SKwwiNh8lCwX3DDb5OgcKGiEBrTTyw==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", "es-abstract": "^1.21.2", "get-intrinsic": "^1.2.1" } }, "node_modules/object.hasown": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz", "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==", "dev": true, "dependencies": { "define-properties": "^1.1.4", "es-abstract": "^1.20.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/object.values": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", "es-abstract": "^1.20.4" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "dependencies": { "wrappy": "1" } }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, "dependencies": { "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0" }, "engines": { "node": ">= 0.8.0" } }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "dependencies": { "yocto-queue": "^0.1.0" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-locate": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "dependencies": { "p-limit": "^3.0.2" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, "engines": { "node": ">=6" } }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "dependencies": { "callsites": "^3.0.0" }, "engines": { "node": ">=6" } }, "node_modules/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", "dev": true, "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" }, "engines": { "node": ">=4" } }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "engines": { "node": ">=8" } }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "engines": { "node": ">=8" } }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC" }, "node_modules/pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true, "engines": { "node": ">=6" } }, "node_modules/pkg-conf": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-3.1.0.tgz", "integrity": "sha512-m0OTbR/5VPNPqO1ph6Fqbj7Hv6QU7gR/tQW40ZqrL1rjgCU85W6C1bJn0BItuJqnR98PWzw7Z8hHeChD1WrgdQ==", "dev": true, "dependencies": { "find-up": "^3.0.0", "load-json-file": "^5.2.0" }, "engines": { "node": ">=6" } }, "node_modules/pkg-conf/node_modules/find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "dependencies": { "locate-path": "^3.0.0" }, "engines": { "node": ">=6" } }, "node_modules/pkg-conf/node_modules/locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" }, "engines": { "node": ">=6" } }, "node_modules/pkg-conf/node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "dependencies": { "p-try": "^2.0.0" }, "engines": { "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/pkg-conf/node_modules/p-locate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "dependencies": { "p-limit": "^2.0.0" }, "engines": { "node": ">=6" } }, "node_modules/pkg-conf/node_modules/path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", "dev": true, "engines": { "node": ">=4" } }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "engines": { "node": ">= 0.8.0" } }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "dev": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", "dev": true, "engines": { "node": ">=6" } }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/feross" }, { "type": "patreon", "url": "https://www.patreon.com/feross" }, { "type": "consulting", "url": "https://feross.org/support" } ] }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, "node_modules/regexp.prototype.flags": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", "functions-have-names": "^1.2.2" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true, "engines": { "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/mysticatea" } }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/requizzle": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", "dev": true, "dependencies": { "lodash": "^4.17.21" } }, "node_modules/resolve": { "version": "1.22.4", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", "dev": true, "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "engines": { "node": ">=4" } }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" } }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/rollup": { "version": "2.79.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", "dev": true, "bin": { "rollup": "dist/bin/rollup" }, "engines": { "node": ">=10.0.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/feross" }, { "type": "patreon", "url": "https://www.patreon.com/feross" }, { "type": "consulting", "url": "https://feross.org/support" } ], "dependencies": { "queue-microtask": "^1.2.2" } }, "node_modules/safe-regex-test": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.3", "is-regex": "^1.1.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" } }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, "engines": { "node": ">=8" } }, "node_modules/shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "engines": { "node": ">=8" } }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", "dev": true, "dependencies": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", "object-inspect": "^1.9.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", "engines": { "node": ">=14" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/standard": { "version": "17.1.0", "resolved": "https://registry.npmjs.org/standard/-/standard-17.1.0.tgz", "integrity": "sha512-jaDqlNSzLtWYW4lvQmU0EnxWMUGQiwHasZl5ZEIwx3S/ijZDjZOzs1y1QqKwKs5vqnFpGtizo4NOYX2s0Voq/g==", "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/feross" }, { "type": "patreon", "url": "https://www.patreon.com/feross" }, { "type": "consulting", "url": "https://feross.org/support" } ], "dependencies": { "eslint": "^8.41.0", "eslint-config-standard": "17.1.0", "eslint-config-standard-jsx": "^11.0.0", "eslint-plugin-import": "^2.27.5", "eslint-plugin-n": "^15.7.0", "eslint-plugin-promise": "^6.1.1", "eslint-plugin-react": "^7.32.2", "standard-engine": "^15.0.0", "version-guard": "^1.1.1" }, "bin": { "standard": "bin/cmd.cjs" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/standard-engine": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/standard-engine/-/standard-engine-15.1.0.tgz", "integrity": "sha512-VHysfoyxFu/ukT+9v49d4BRXIokFRZuH3z1VRxzFArZdjSCFpro6rEIU3ji7e4AoAtuSfKBkiOmsrDqKW5ZSRw==", "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/feross" }, { "type": "patreon", "url": "https://www.patreon.com/feross" }, { "type": "consulting", "url": "https://feross.org/support" } ], "dependencies": { "get-stdin": "^8.0.0", "minimist": "^1.2.6", "pkg-conf": "^3.1.0", "xdg-basedir": "^4.0.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/stream-connect": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/stream-connect/-/stream-connect-1.0.2.tgz", "integrity": "sha512-68Kl+79cE0RGKemKkhxTSg8+6AGrqBt+cbZAXevg2iJ6Y3zX4JhA/sZeGzLpxW9cXhmqAcE7KnJCisUmIUfnFQ==", "dev": true, "dependencies": { "array-back": "^1.0.2" }, "engines": { "node": ">=0.10.0" } }, "node_modules/stream-connect/node_modules/array-back": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz", "integrity": "sha512-1WxbZvrmyhkNoeYcizokbmh5oiOCIfyvGtcqbK3Ls1v1fKcquzxnQSceOx6tzq7jmai2kFLWIpGND2cLhH6TPw==", "dev": true, "dependencies": { "typical": "^2.6.0" }, "engines": { "node": ">=0.12.0" } }, "node_modules/stream-via": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/stream-via/-/stream-via-1.0.4.tgz", "integrity": "sha512-DBp0lSvX5G9KGRDTkR/R+a29H+Wk2xItOF+MpZLLNDWbEV9tGPnqLPxHEYjmiz8xGtJHRIqmI+hCjmNzqoA4nQ==", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8" } }, "node_modules/string-width-cjs": { "name": "string-width", "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8" } }, "node_modules/string.prototype.matchall": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", "es-abstract": "^1.20.4", "get-intrinsic": "^1.1.3", "has-symbols": "^1.0.3", "internal-slot": "^1.0.3", "regexp.prototype.flags": "^1.4.3", "side-channel": "^1.0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trim": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", "es-abstract": "^1.20.4" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimend": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", "es-abstract": "^1.20.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimstart": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", "es-abstract": "^1.20.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" } }, "node_modules/strip-ansi-cjs": { "name": "strip-ansi", "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" } }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, "engines": { "node": ">=4" } }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "engines": { "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { "has-flag": "^4.0.0" }, "engines": { "node": ">=8" } }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/temp-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/temp-path/-/temp-path-1.0.0.tgz", "integrity": "sha512-TvmyH7kC6ZVTYkqCODjJIbgvu0FKiwQpZ4D1aknE7xpcDf/qEOB8KZEK5ef2pfbVoiBhNWs3yx4y+ESMtNYmlg==", "dev": true }, "node_modules/test-exclude": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", "dev": true, "license": "ISC", "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^10.4.1", "minimatch": "^9.0.4" }, "engines": { "node": ">=18" } }, "node_modules/test-exclude/node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/test-exclude/node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/test-exclude/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, "node_modules/tsconfig-paths": { "version": "3.14.2", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", "dev": true, "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "dependencies": { "prelude-ls": "^1.2.1" }, "engines": { "node": ">= 0.8.0" } }, "node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/typed-array-length": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "for-each": "^0.3.3", "is-typed-array": "^1.1.9" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { "node": ">=14.17" } }, "node_modules/typical": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", "integrity": "sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==", "dev": true }, "node_modules/uc.micro": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", "dev": true }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-bigints": "^1.0.2", "has-symbols": "^1.0.3", "which-boxed-primitive": "^1.0.2" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/underscore": { "version": "1.13.6", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", "dev": true }, "node_modules/undici-types": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", "dev": true, "license": "MIT" }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "dependencies": { "punycode": "^2.1.0" } }, "node_modules/v8-to-istanbul": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", "convert-source-map": "^1.6.0" }, "engines": { "node": ">=10.12.0" } }, "node_modules/version-guard": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/version-guard/-/version-guard-1.1.1.tgz", "integrity": "sha512-MGQLX89UxmYHgDvcXyjBI0cbmoW+t/dANDppNPrno64rYr8nH4SHSuElQuSYdXGEs0mUzdQe1BY+FhVPNsAmJQ==", "dev": true, "engines": { "node": ">=0.10.48" } }, "node_modules/walk-back": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-5.1.0.tgz", "integrity": "sha512-Uhxps5yZcVNbLEAnb+xaEEMdgTXl9qAQDzKYejG2AZ7qPwRQ81lozY9ECDbjLPNWm7YsO1IK5rsP1KoQzXAcGA==", "dev": true, "engines": { "node": ">=12.17" } }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" }, "engines": { "node": ">= 8" } }, "node_modules/which-boxed-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", "dev": true, "dependencies": { "is-bigint": "^1.0.1", "is-boolean-object": "^1.1.0", "is-number-object": "^1.0.4", "is-string": "^1.0.5", "is-symbol": "^1.0.3" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/which-typed-array": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", "for-each": "^0.3.3", "gopd": "^1.0.1", "has-tostringtag": "^1.0.0", "is-typed-array": "^1.1.10" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, "node_modules/xdg-basedir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", "dev": true, "engines": { "node": ">=8" } }, "node_modules/xmlcreate": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", "dev": true }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, "license": "ISC", "engines": { "node": ">=10" } }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" }, "engines": { "node": ">=12" } }, "node_modules/yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, "license": "ISC", "engines": { "node": ">=12" } }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } } } } dmonad-lib0-c7e7806/package.json000066400000000000000000000406301512504553500164430ustar00rootroot00000000000000{ "name": "lib0", "version": "0.2.117", "description": "", "sideEffects": false, "type": "module", "main": "./dist/index.cjs", "module": "./index.js", "types": "./index.d.ts", "funding": { "type": "GitHub Sponsors ❤", "url": "https://github.com/sponsors/dmonad" }, "bin": { "0gentesthtml": "./bin/gentesthtml.js", "0serve": "./bin/0serve.js", "0ecdsa-generate-keypair": "./bin/0ecdsa-generate-keypair.js" }, "exports": { "./package.json": "./package.json", ".": { "types": "./index.d.ts", "module": "./index.js", "import": "./index.js", "require": "./dist/index.cjs" }, "./array.js": "./array.js", "./dist/array.cjs": "./dist/array.cjs", "./array": { "types": "./array.d.ts", "module": "./array.js", "import": "./array.js", "require": "./dist/array.cjs" }, "./binary.js": "./binary.js", "./dist/binary.cjs": "./dist/binary.cjs", "./binary": { "types": "./binary.d.ts", "module": "./binary.js", "import": "./binary.js", "require": "./dist/binary.cjs" }, "./broadcastchannel.js": "./broadcastchannel.js", "./dist/broadcastchannel.cjs": "./dist/broadcastchannel.cjs", "./broadcastchannel": { "types": "./broadcastchannel.d.ts", "module": "./broadcastchannel.js", "import": "./broadcastchannel.js", "require": "./dist/broadcastchannel.cjs" }, "./buffer.js": "./buffer.js", "./dist/buffer.cjs": "./dist/buffer.cjs", "./buffer": { "types": "./buffer.d.ts", "module": "./buffer.js", "import": "./buffer.js", "require": "./dist/buffer.cjs" }, "./cache.js": "./cache.js", "./dist/cache.cjs": "./dist/cache.cjs", "./cache": { "types": "./cache.d.ts", "module": "./cache.js", "import": "./cache.js", "require": "./dist/cache.cjs" }, "./component.js": "./component.js", "./dist/component.cjs": "./dist/component.cjs", "./component": { "types": "./component.d.ts", "module": "./component.js", "import": "./component.js", "require": "./dist/component.cjs" }, "./conditions.js": "./conditions.js", "./dist/conditions.cjs": "./dist/conditions.cjs", "./conditions": { "types": "./conditions.d.ts", "module": "./conditions.js", "import": "./conditions.js", "require": "./dist/conditions.cjs" }, "./crypto/jwt": { "types": "./crypto/jwt.d.ts", "module": "./crypto/jwt.js", "import": "./crypto/jwt.js", "require": "./dist/jwt.cjs" }, "./crypto/aes-gcm": { "types": "./crypto/aes-gcm.d.ts", "module": "./crypto/aes-gcm.js", "import": "./crypto/aes-gcm.js", "require": "./dist/aes-gcm.cjs" }, "./delta": { "types": "./delta/delta.d.ts", "module": "./delta/delta.js", "import": "./delta/delta.js", "require": "./dist/delta.cjs" }, "./crypto/ecdsa": { "types": "./crypto/ecdsa.d.ts", "module": "./crypto/ecdsa.js", "import": "./crypto/ecdsa.js", "require": "./dist/ecdsa.cjs" }, "./crypto/rsa-oaep": { "types": "./crypto/rsa-oaep.d.ts", "module": "./crypto/rsa-oaep.js", "import": "./crypto/rsa-oaep.js", "require": "./dist/rsa-oaep.cjs" }, "./hash/rabin": { "types": "./hash/rabin.d.ts", "module": "./hash/rabin.js", "import": "./hash/rabin.js", "require": "./dist/rabin.cjs" }, "./hash/sha256": { "types": "./hash/sha256.d.ts", "browser": { "module": "./hash/sha256.js", "require": "./dist/sha256.cjs", "default": "./hash/sha256.js" }, "node": { "require": "./dist/sha256.node.cjs", "default": "./hash/sha256.node.js" }, "default": { "module": "./hash/sha256.js", "require": "./dist/sha256.cjs", "default": "./hash/sha256.js" } }, "./decoding.js": "./decoding.js", "./dist/decoding.cjs": "./dist/decoding.cjs", "./decoding": { "types": "./decoding.d.ts", "module": "./decoding.js", "import": "./decoding.js", "require": "./dist/decoding.cjs" }, "./diff/patience": { "types": "./diff/patience.d.ts", "module": "./diff/patience.js", "import": "./diff/patience.js", "require": "./dist/patience.cjs" }, "./diff.js": "./diff.js", "./dist/diff.cjs": "./dist/diff.cjs", "./diff": { "types": "./diff.d.ts", "module": "./diff.js", "import": "./diff.js", "require": "./dist/diff.cjs" }, "./dom.js": "./dom.js", "./dist/dom.cjs": "./dist/dom.cjs", "./dom": { "types": "./dom.d.ts", "module": "./dom.js", "import": "./dom.js", "require": "./dist/dom.cjs" }, "./encoding.js": "./encoding.js", "./dist/encoding.cjs": "./dist/encoding.cjs", "./encoding": { "types": "./encoding.d.ts", "module": "./encoding.js", "import": "./encoding.js", "require": "./dist/encoding.cjs" }, "./environment.js": "./environment.js", "./dist/environment.cjs": "./dist/environment.cjs", "./environment": { "types": "./environment.d.ts", "module": "./environment.js", "import": "./environment.js", "require": "./dist/environment.cjs" }, "./error.js": "./error.js", "./dist/error.cjs": "./dist/error.cjs", "./error": { "types": "./error.d.ts", "module": "./error.js", "import": "./error.js", "require": "./dist/error.cjs" }, "./eventloop.js": "./eventloop.js", "./dist/eventloop.cjs": "./dist/eventloop.cjs", "./eventloop": { "types": "./eventloop.d.ts", "module": "./eventloop.js", "import": "./eventloop.js", "require": "./dist/eventloop.cjs" }, "./function.js": "./function.js", "./dist/function.cjs": "./dist/function.cjs", "./function": { "types": "./function.d.ts", "module": "./function.js", "import": "./function.js", "require": "./dist/function.cjs" }, "./indexeddb.js": "./indexeddb.js", "./dist/indexeddb.cjs": "./dist/indexeddb.cjs", "./indexeddb": { "types": "./indexeddb.d.ts", "module": "./indexeddb.js", "import": "./indexeddb.js", "require": "./dist/indexeddb.cjs" }, "./isomorphic.js": "./isomorphic.js", "./dist/isomorphic.cjs": "./dist/isomorphic.cjs", "./isomorphic": { "types": "./isomorphic.d.ts", "module": "./isomorphic.js", "import": "./isomorphic.js", "require": "./dist/isomorphic.cjs" }, "./iterator.js": "./iterator.js", "./dist/iterator.cjs": "./dist/iterator.cjs", "./iterator": { "types": "./iterator.d.ts", "module": "./iterator.js", "import": "./iterator.js", "require": "./dist/iterator.cjs" }, "./json.js": "./json.js", "./dist/json.cjs": "./dist/json.cjs", "./json": { "types": "./json.d.ts", "module": "./json.js", "import": "./json.js", "require": "./dist/json.cjs" }, "./list.js": "./list.js", "./dist/list.cjs": "./dist/list.cjs", "./list": { "types": "./list.d.ts", "module": "./list.js", "import": "./list.js", "require": "./dist/list.cjs" }, "./logging.js": "./logging.js", "./dist/logging.cjs": "./dist/logging.node.cjs", "./logging": { "types": "./logging.node.d.ts", "deno": "./logging.node.js", "bun": "./logging.js", "browser": { "module": "./logging.js", "require": "./dist/logging.cjs", "default": "./logging.js" }, "node": { "module": "./logging.node.js", "require": "./dist/logging.node.cjs", "default": "./logging.node.js" }, "default": { "module": "./logging.js", "require": "./dist/logging.cjs", "default": "./logging.js" } }, "./map.js": "./map.js", "./dist/map.cjs": "./dist/map.cjs", "./map": { "types": "./map.d.ts", "module": "./map.js", "import": "./map.js", "require": "./dist/map.cjs" }, "./math.js": "./math.js", "./dist/math.cjs": "./dist/math.cjs", "./math": { "types": "./math.d.ts", "module": "./math.js", "import": "./math.js", "require": "./dist/math.cjs" }, "./metric.js": "./metric.js", "./dist/metric.cjs": "./dist/metric.cjs", "./metric": { "types": "./metric.d.ts", "module": "./metric.js", "import": "./metric.js", "require": "./dist/metric.cjs" }, "./mutex.js": "./mutex.js", "./dist/mutex.cjs": "./dist/mutex.cjs", "./mutex": { "types": "./mutex.d.ts", "module": "./mutex.js", "import": "./mutex.js", "require": "./dist/mutex.cjs" }, "./number.js": "./number.js", "./dist/number.cjs": "./dist/number.cjs", "./number": { "types": "./number.d.ts", "module": "./number.js", "import": "./number.js", "require": "./dist/number.cjs" }, "./object.js": "./object.js", "./dist/object.cjs": "./dist/object.cjs", "./object": { "types": "./object.d.ts", "module": "./object.js", "import": "./object.js", "require": "./dist/object.cjs" }, "./observable.js": "./observable.js", "./dist/observable.cjs": "./dist/observable.cjs", "./observable": { "types": "./observable.d.ts", "module": "./observable.js", "import": "./observable.js", "require": "./dist/observable.cjs" }, "./pair.js": "./pair.js", "./dist/pair.cjs": "./dist/pair.cjs", "./pair": { "types": "./pair.d.ts", "module": "./pair.js", "import": "./pair.js", "require": "./dist/pair.cjs" }, "./prng.js": "./prng.js", "./dist/prng.cjs": "./dist/prng.cjs", "./prng": { "types": "./prng.d.ts", "module": "./prng.js", "import": "./prng.js", "require": "./dist/prng.cjs" }, "./promise.js": "./promise.js", "./dist/promise.cjs": "./dist/promise.cjs", "./promise": { "types": "./promise.d.ts", "module": "./promise.js", "import": "./promise.js", "require": "./dist/promise.cjs" }, "./queue.js": "./queue.js", "./dist/queue.cjs": "./dist/queue.cjs", "./queue": { "types": "./queue.d.ts", "module": "./queue.js", "import": "./queue.js", "require": "./dist/queue.cjs" }, "./random.js": "./random.js", "./dist/random.cjs": "./dist/random.cjs", "./random": { "types": "./random.d.ts", "module": "./random.js", "import": "./random.js", "require": "./dist/random.cjs" }, "./set.js": "./set.js", "./dist/set.cjs": "./dist/set.cjs", "./set": { "types": "./set.d.ts", "module": "./set.js", "import": "./set.js", "require": "./dist/set.cjs" }, "./sort.js": "./sort.js", "./dist/sort.cjs": "./dist/sort.cjs", "./sort": { "types": "./sort.d.ts", "module": "./sort.js", "import": "./sort.js", "require": "./dist/sort.cjs" }, "./statistics.js": "./statistics.js", "./dist/statistics.cjs": "./dist/statistics.cjs", "./statistics": { "types": "./statistics.d.ts", "module": "./statistics.js", "import": "./statistics.js", "require": "./dist/statistics.cjs" }, "./storage.js": "./storage.js", "./dist/storage.cjs": "./dist/storage.cjs", "./storage": { "types": "./storage.d.ts", "module": "./storage.js", "import": "./storage.js", "require": "./dist/storage.cjs" }, "./string.js": "./string.js", "./dist/string.cjs": "./dist/string.cjs", "./string": { "types": "./string.d.ts", "module": "./string.js", "import": "./string.js", "require": "./dist/string.cjs" }, "./symbol.js": "./symbol.js", "./dist/symbol.cjs": "./dist/symbol.cjs", "./symbol": { "types": "./symbol.d.ts", "module": "./symbol.js", "import": "./symbol.js", "require": "./dist/symbol.cjs" }, "./traits": { "types": "./trait/traits.d.ts", "module": "./trait/traits.js", "import": "./trait/traits.js", "require": "./dist/traits.cjs" }, "./testing.js": "./testing.js", "./dist/testing.cjs": "./dist/testing.cjs", "./testing": { "types": "./testing.d.ts", "module": "./testing.js", "import": "./testing.js", "require": "./dist/testing.cjs" }, "./time.js": "./time.js", "./dist/time.cjs": "./dist/time.cjs", "./time": { "types": "./time.d.ts", "module": "./time.js", "import": "./time.js", "require": "./dist/time.cjs" }, "./tree.js": "./tree.js", "./dist/tree.cjs": "./dist/tree.cjs", "./tree": { "types": "./tree.d.ts", "module": "./tree.js", "import": "./tree.js", "require": "./dist/tree.cjs" }, "./url.js": "./url.js", "./dist/url.cjs": "./dist/url.cjs", "./url": { "types": "./url.d.ts", "module": "./url.js", "import": "./url.js", "require": "./dist/url.cjs" }, "./websocket.js": "./websocket.js", "./dist/websocket.cjs": "./dist/websocket.cjs", "./websocket": { "types": "./websocket.d.ts", "module": "./websocket.js", "import": "./websocket.js", "require": "./dist/websocket.cjs" }, "./webcrypto": { "types": "./webcrypto.d.ts", "deno": "./webcrypto.deno.js", "bun": "./webcrypto.js", "react-native": "./dist/webcrypto.react-native.cjs", "browser": { "module": "./webcrypto.js", "require": "./dist/webcrypto.cjs", "default": "./webcrypto.js" }, "node": { "module": "./webcrypto.node.js", "require": "./dist/webcrypto.node.cjs", "default": "./webcrypto.node.js" }, "default": { "module": "./webcrypto.js", "require": "./dist/webcrypto.cjs", "default": "./webcrypto.js" } }, "./performance.js": "./performance.js", "./dist/performance.cjs": "./dist/performance.node.cjs", "./performance": { "types": "./performance.d.ts", "deno": "./performance.node.js", "bun": "./performance.node.js", "browser": { "module": "./performance.js", "require": "./dist/performance.cjs", "default": "./performance.js" }, "node": { "module": "./performance.node.js", "require": "./dist/performance.node.cjs", "default": "./performance.node.js" }, "default": { "module": "./performance.js", "require": "./dist/performance.cjs", "default": "./performance.js" } }, "./schema": { "types": "./schema.d.ts", "module": "./schema.js", "import": "./schema.js", "require": "./dist/schema.cjs" } }, "dependencies": { "isomorphic.js": "^0.2.4" }, "devDependencies": { "@types/node": "^24.0.14", "c8": "^10.1.3", "jsdoc-api": "^8.0.0", "jsdoc-plugin-typescript": "^2.2.1", "rollup": "^2.42.1", "standard": "^17.1.0", "typescript": "^5.9.3" }, "scripts": { "clean": "rm -rf dist *.d.ts */*.d.ts *.d.ts.map */*.d.ts.map", "types": "tsc --outDir .", "dist": "rollup -c", "debug": "npm run gentesthtml && node ./bin/0serve.js -o test.html", "test": "c8 --check-coverage --lines 100 --branches 100 --functions 100 --statements 100 node --unhandled-rejections=strict ./test.js --repetition-time 50", "test-inspect": "node --inspect-brk --unhandled-rejections=strict ./test.js --repetition-time 50", "test-extensive": "c8 --check-coverage --lines 100 --branches 100 --functions 100 --statements 100 node test.js --repetition-time 30000 --extensive", "trace-deopt": "clear && rollup -c && node --trace-deopt dist/test.cjs", "trace-opt": "clear && rollup -c && node --trace-opt dist/test.cjs", "lint": "standard && tsc", "gendocs": "node ./bin/gendocs.js", "preversion": "npm run clean && npm run lint && npm run types && npm run dist && git add README.md", "postpublish": "npm run clean", "gentesthtml": "node ./bin/gentesthtml.js --script test.js > test.html" }, "repository": { "type": "git", "url": "git+https://github.com/dmonad/lib0.git" }, "author": "Kevin Jahns ", "license": "MIT", "bugs": { "url": "https://github.com/dmonad/lib0/issues" }, "homepage": "https://github.com/dmonad/lib0#readme", "standard": { "ignore": [ "/dist", "/node_modules", "/docs" ] }, "engines": { "node": ">=16" } } dmonad-lib0-c7e7806/pair.js000066400000000000000000000015651512504553500154520ustar00rootroot00000000000000/** * Working with value pairs. * * @module pair */ /** * @template L,R */ export 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} */ export const create = (left, right) => new Pair(left, right) /** * @template L,R * @param {R} right * @param {L} left * @return {Pair} */ export const createReversed = (right, left) => new Pair(left, right) /** * @template L,R * @param {Array>} arr * @param {function(L, R):any} f */ export const forEach = (arr, f) => arr.forEach(p => f(p.left, p.right)) /** * @template L,R,X * @param {Array>} arr * @param {function(L, R):X} f * @return {Array} */ export const map = (arr, f) => arr.map(p => f(p.left, p.right)) dmonad-lib0-c7e7806/pair.test.js000066400000000000000000000011741512504553500164240ustar00rootroot00000000000000import * as t from './testing.js' import * as pair from './pair.js' import * as math from './math.js' /** * @param {t.TestCase} tc */ export const testPair = tc => { const ps = [pair.create(1, 2), pair.create(3, 4), pair.createReversed(6, 5)] t.describe('Counting elements in pair list') let countLeft = 0 let countRight = 0 pair.forEach(ps, (left, right) => { countLeft += left countRight += right }) t.assert(countLeft === 9) t.assert(countRight === 12) t.assert(countLeft === pair.map(ps, left => left).reduce(math.add)) t.assert(countRight === pair.map(ps, (left, right) => right).reduce(math.add)) } dmonad-lib0-c7e7806/performance.js000066400000000000000000000003031512504553500170050ustar00rootroot00000000000000/* eslint-env browser */ export const measure = performance.measure.bind(performance) export const now = performance.now.bind(performance) export const mark = performance.mark.bind(performance) dmonad-lib0-c7e7806/performance.node.js000066400000000000000000000011461512504553500177370ustar00rootroot00000000000000import { performance } from 'node:perf_hooks' import { nop } from './function.js' import * as time from './time.js' /** * @type {typeof performance.measure} */ /* c8 ignore next */ export const measure = performance.measure ? performance.measure.bind(performance) : /** @type {any} */ (nop) /** * @type {typeof performance.now} */ /* c8 ignore next */ export const now = performance.now ? performance.now.bind(performance) : time.getUnixTime /** * @type {typeof performance.mark} */ /* c8 ignore next */ export const mark = performance.mark ? performance.mark.bind(performance) : /** @type {any} */ (nop) dmonad-lib0-c7e7806/pledge.js000066400000000000000000000137461512504553500157630ustar00rootroot00000000000000/** * @experimental Use of this module is not encouraged! * This is just an experiment. * @todo remove `c8 ignore` line once this is moved to "non-experimental" */ import * as queue from './queue.js' import * as object from './object.js' /* c8 ignore start */ /** * @type {queue.Queuevoid>>} */ const ctxFs = queue.create() /** * @param {() => void} f */ const runInGlobalContext = f => { const isEmpty = queue.isEmpty(ctxFs) queue.enqueue(ctxFs, new queue.QueueValue(f)) if (isEmpty) { while (!queue.isEmpty(ctxFs)) { /** @type {queue.QueueValue<()=>{}>} */ (ctxFs.start).v() queue.dequeue(ctxFs) } } } /** * @template V * @typedef {V | PledgeInstance} Pledge */ /** * @template {any} Val * @template {any} [CancelReason=Error] */ export class PledgeInstance { constructor () { /** * @type {Val | CancelReason | null} */ this._v = null this.isResolved = false /** * @type {Array | null} */ this._whenResolved = [] /** * @type {Array | null} */ this._whenCanceled = [] } get isDone () { return this._whenResolved === null } get isCanceled () { return !this.isResolved && this._whenResolved === null } /** * @param {Val} v */ resolve (v) { const whenResolved = this._whenResolved if (whenResolved === null) return this._v = v this.isResolved = true this._whenResolved = null this._whenCanceled = null for (let i = 0; i < whenResolved.length; i++) { whenResolved[i](v) } } /** * @param {CancelReason} reason */ cancel (reason) { const whenCanceled = this._whenCanceled if (whenCanceled === null) return this._v = reason this._whenResolved = null this._whenCanceled = null for (let i = 0; i < whenCanceled.length; i++) { whenCanceled[i](reason) } } /** * @template R * @param {function(Val):Pledge} f * @return {PledgeInstance} */ map (f) { /** * @type {PledgeInstance} */ const p = new PledgeInstance() this.whenResolved(v => { const result = f(v) if (result instanceof PledgeInstance) { if (result._whenResolved === null) { result.resolve(/** @type {R} */ (result._v)) } else { result._whenResolved.push(p.resolve.bind(p)) } } else { p.resolve(result) } }) return p } /** * @param {function(Val):void} f */ whenResolved (f) { if (this.isResolved) { f(/** @type {Val} */ (this._v)) } else { this._whenResolved?.push(f) } } /** * @param {(reason: CancelReason) => void} f */ whenCanceled (f) { if (this.isCanceled) { f(/** @type {CancelReason} */ (this._v)) } else { this._whenCanceled?.push(f) } } /** * @return {Promise} */ promise () { return new Promise((resolve, reject) => { this.whenResolved(resolve) this.whenCanceled(reject) }) } } /** * @template T * @return {PledgeInstance} */ export const create = () => new PledgeInstance() /** * @typedef {Array> | Object>} PledgeMap */ /** * @template {Pledge | PledgeMap} P * @typedef {P extends PledgeMap ? { [K in keyof P]: P[K] extends Pledge ? V : P[K]} : (P extends Pledge ? V : never)} Resolved

*/ /** * @todo Create a "resolveHelper" that will simplify creating indxeddbv2 functions. Double arguments * are not necessary. * * @template V * @template {Array>} DEPS * @param {(p: PledgeInstance, ...deps: Resolved) => void} init * @param {DEPS} deps * @return {PledgeInstance} */ export const createWithDependencies = (init, ...deps) => { /** * @type {PledgeInstance} */ const p = new PledgeInstance() // @ts-ignore @todo remove this all(deps).whenResolved(ds => init(p, ...ds)) return p } /** * @template R * @param {Pledge} p * @param {function(R):void} f */ export const whenResolved = (p, f) => { if (p instanceof PledgeInstance) { return p.whenResolved(f) } return f(p) } /** * @template {Pledge} P * @param {P} p * @param {P extends PledgeInstance ? function(CancelReason):void : function(any):void} f */ export const whenCanceled = (p, f) => { if (p instanceof PledgeInstance) { p.whenCanceled(f) } } /** * @template P * @template Q * @param {Pledge

} p * @param {(r: P) => Q} f * @return {Pledge} */ export const map = (p, f) => { if (p instanceof PledgeInstance) { return p.map(f) } return f(p) } /** * @template {PledgeMap} PS * @param {PS} ps * @return {PledgeInstance>} */ export const all = ps => { /** * @type {any} */ const pall = create() /** * @type {any} */ const result = ps instanceof Array ? new Array(ps.length) : {} let waitingPs = ps instanceof Array ? ps.length : object.size(ps) for (const key in ps) { const p = ps[key] whenResolved(p, r => { result[key] = r if (--waitingPs === 0) { // @ts-ignore pall.resolve(result) } }) } return pall } /** * @template Result * @template {any} YieldResults * @param {() => Generator | PledgeInstance, Result, any>} f * @return {PledgeInstance} */ export const coroutine = f => { const p = create() const gen = f() /** * @param {any} [yv] */ const handleGen = (yv) => { const res = gen.next(yv) if (res.done) { p.resolve(res.value) return } // @ts-ignore whenCanceled(res.value, (reason) => { gen.throw(reason) }) runInGlobalContext(() => whenResolved(res.value, handleGen) ) } handleGen() return p } /** * @param {number} timeout * @return {PledgeInstance} */ export const wait = timeout => { const p = create() setTimeout(p.resolve.bind(p), timeout) return p } /* c8 ignore end */ dmonad-lib0-c7e7806/pledge.test.js000066400000000000000000000042611512504553500167310ustar00rootroot00000000000000import * as t from './testing.js' import * as pledge from './pledge.js' import * as promise from './promise.js' /** * @param {t.TestCase} _tc */ export const testPledgeCoroutine = async _tc => { let called = false const p = pledge.coroutine(function * () { const y = pledge.wait(10).map(() => 42) const num = yield y console.log({ num }) t.assert(num === 42) called = true return 42 }) t.assert(!called) await p.promise() t.assert(called) } /** * @param {t.TestCase} _tc */ export const testPledgeVsPromisePerformanceTimeout = async _tc => { const iterations = 100 const waitTime = 0 await t.measureTimeAsync(`Awaiting ${iterations} callbacks (promise)`, async () => { for (let i = 0; i < iterations; i++) { await promise.wait(waitTime) } }) await t.measureTimeAsync(`Awaiting ${iterations} callbacks (pledge)`, () => pledge.coroutine(function * () { for (let i = 0; i < iterations; i++) { yield pledge.wait(waitTime) } }).promise() ) } /** * @typedef {Promise | number} MaybePromise */ /** * @param {t.TestCase} _tc */ export const testPledgeVsPromisePerformanceResolved = async _tc => { const iterations = 100 t.measureTime(`Awaiting ${iterations} callbacks (only iterate)`, () => { for (let i = 0; i < iterations; i++) { /* nop */ } }) await t.measureTimeAsync(`Awaiting ${iterations} callbacks (promise)`, async () => { for (let i = 0; i < iterations; i++) { await promise.resolve(0) } }) await t.measureTimeAsync(`Awaiting ${iterations} callbacks (await, no resolve)`, async () => { for (let i = 0; i < iterations; i++) { /** * @type {Promise | number} */ const x = 0 await x } }) await t.measureTimeAsync(`Awaiting ${iterations} callbacks (pledge)`, () => pledge.coroutine(function * () { for (let i = 0; i < iterations; i++) { yield 0 } }).promise() ) t.measureTime(`Awaiting ${iterations} callbacks (pledge, manual wrap)`, () => { /** * @type {pledge.Pledge} */ let val = 0 for (let i = 0; i < iterations; i++) { val = pledge.map(val, _v => 0) } }) } dmonad-lib0-c7e7806/prng.js000066400000000000000000000137141512504553500154640ustar00rootroot00000000000000/** * Fast Pseudo Random Number Generators. * * 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. * * @module prng */ import * as binary from './binary.js' import { fromCharCode, fromCodePoint } from './string.js' import * as math from './math.js' import { Xoroshiro128plus } from './prng/Xoroshiro128plus.js' import * as buffer from './buffer.js' /** * Description of the function * @callback generatorNext * @return {number} A random float in the cange of [0,1) */ /** * A random type generator. * * @typedef {Object} PRNG * @property {generatorNext} next Generate new number */ export const DefaultPRNG = Xoroshiro128plus /** * Create 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/. * * @param {number} seed A positive 32bit integer. Do not use negative numbers. * @return {PRNG} */ export const create = seed => new DefaultPRNG(seed) /** * Generates a single random bool. * * @param {PRNG} gen A random number generator. * @return {Boolean} A random boolean */ export const bool = gen => (gen.next() >= 0.5) /** * Generates a random integer with 53 bit resolution. * * @param {PRNG} gen A random number generator. * @param {Number} min The lower bound of the allowed return values (inclusive). * @param {Number} max The upper bound of the allowed return values (inclusive). * @return {Number} A random integer on [min, max] */ export const int53 = (gen, min, max) => math.floor(gen.next() * (max + 1 - min) + min) /** * Generates a random integer with 53 bit resolution. * * @param {PRNG} gen A random number generator. * @param {Number} min The lower bound of the allowed return values (inclusive). * @param {Number} max The upper bound of the allowed return values (inclusive). * @return {Number} A random integer on [min, max] */ export const uint53 = (gen, min, max) => math.abs(int53(gen, min, max)) /** * Generates a random integer with 32 bit resolution. * * @param {PRNG} gen A random number generator. * @param {Number} min The lower bound of the allowed return values (inclusive). * @param {Number} max The upper bound of the allowed return values (inclusive). * @return {Number} A random integer on [min, max] */ export const int32 = (gen, min, max) => math.floor(gen.next() * (max + 1 - min) + min) /** * Generates a random integer with 53 bit resolution. * * @param {PRNG} gen A random number generator. * @param {Number} min The lower bound of the allowed return values (inclusive). * @param {Number} max The upper bound of the allowed return values (inclusive). * @return {Number} A random integer on [min, max] */ export const uint32 = (gen, min, max) => int32(gen, min, max) >>> 0 /** * @deprecated * Optimized version of prng.int32. It has the same precision as prng.int32, but should be preferred when * openaring on smaller ranges. * * @param {PRNG} gen A random number generator. * @param {Number} min The lower bound of the allowed return values (inclusive). * @param {Number} max The upper bound of the allowed return values (inclusive). The max inclusive number is `binary.BITS31-1` * @return {Number} A random integer on [min, max] */ export const int31 = (gen, min, max) => int32(gen, min, max) /** * Generates a random real on [0, 1) with 53 bit resolution. * * @param {PRNG} gen A random number generator. * @return {Number} A random real number on [0, 1). */ export const real53 = gen => gen.next() // (((gen.next() >>> 5) * binary.BIT26) + (gen.next() >>> 6)) / MAX_SAFE_INTEGER /** * Generates a random character from char code 32 - 126. I.e. Characters, Numbers, special characters, and Space: * * @param {PRNG} gen A random number generator. * @return {string} * * (Space)!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[/]^_`abcdefghijklmnopqrstuvwxyz{|}~ */ export const char = gen => fromCharCode(int31(gen, 32, 126)) /** * @param {PRNG} gen * @return {string} A single letter (a-z) */ export const letter = gen => fromCharCode(int31(gen, 97, 122)) /** * @param {PRNG} gen * @param {number} [minLen=0] * @param {number} [maxLen=20] * @return {string} A random word (0-20 characters) without spaces consisting of letters (a-z) */ export const word = (gen, minLen = 0, maxLen = 20) => { const len = int31(gen, minLen, maxLen) let str = '' for (let i = 0; i < len; i++) { str += letter(gen) } return str } /** * TODO: this function produces invalid runes. Does not cover all of utf16!! * * @param {PRNG} gen * @return {string} */ export const utf16Rune = gen => { const codepoint = int31(gen, 0, 256) return fromCodePoint(codepoint) } /** * @param {PRNG} gen * @param {number} [maxlen = 20] */ export const utf16String = (gen, maxlen = 20) => { const len = int31(gen, 0, maxlen) let str = '' for (let i = 0; i < len; i++) { str += utf16Rune(gen) } return str } /** * Returns one element of a given array. * * @param {PRNG} gen A random number generator. * @param {Array} array Non empty Array of possible values. * @return {T} One of the values of the supplied Array. * @template T */ export const oneOf = (gen, array) => array[int31(gen, 0, array.length - 1)] /** * @param {PRNG} gen * @param {number} len * @return {Uint8Array} */ export const uint8Array = (gen, len) => { const buf = buffer.createUint8ArrayFromLen(len) for (let i = 0; i < buf.length; i++) { buf[i] = int32(gen, 0, binary.BITS8) } return buf } /* c8 ignore start */ /** * @param {PRNG} gen * @param {number} len * @return {Uint16Array} */ export const uint16Array = (gen, len) => new Uint16Array(uint8Array(gen, len * 2).buffer) /** * @param {PRNG} gen * @param {number} len * @return {Uint32Array} */ export const uint32Array = (gen, len) => new Uint32Array(uint8Array(gen, len * 4).buffer) /* c8 ignore stop */ dmonad-lib0-c7e7806/prng.test.js000066400000000000000000000157301512504553500164420ustar00rootroot00000000000000import { Xoroshiro128plus } from './prng/Xoroshiro128plus.js' import * as prng from './prng.js' import { MAX_SAFE_INTEGER } from './number.js' import * as binary from './binary.js' import * as t from './testing.js' import { Xorshift32 } from './prng/Xorshift32.js' import { Mt19937 } from './prng/Mt19937.js' import * as dom from './dom.js' import { isBrowser } from './environment.js' import * as math from './math.js' const genTestData = 5000 /** * @param {t.TestCase} _tc * @param {prng.PRNG} gen */ const runGenTest = (_tc, gen) => { t.group('next - average distribution', () => { let sum = 0 for (let i = 0; i < genTestData; i++) { const next = gen.next() if (next >= 1) { t.fail('unexpected prng result') } sum += next } const avg = sum / genTestData t.assert(avg >= 0.45) t.assert(avg <= 0.55) }) t.group('bool - bool distribution is fair', () => { let head = 0 let tail = 0 let b let i for (i = 0; i < genTestData; i++) { b = prng.bool(gen) if (b) { head++ } else { tail++ } } t.info(`Generated ${head} heads and ${tail} tails.`) t.assert(tail >= math.floor(genTestData * 0.45), 'Generated enough tails.') t.assert(head >= math.floor(genTestData * 0.45), 'Generated enough heads.') }) t.group('int31 - integers average correctly', () => { let count = 0 let i for (i = 0; i < genTestData; i++) { count += prng.uint32(gen, 0, 100) } const average = count / genTestData const expectedAverage = 100 / 2 t.info(`Average is: ${average}. Expected average is ${expectedAverage}.`) t.assert(math.abs(average - expectedAverage) <= 2, 'Expected average is at most 1 off.') }) t.group('int32 - generates integer with 32 bits', () => { let largest = 0 let smallest = 0 let i let newNum for (i = 0; i < genTestData; i++) { newNum = prng.int32(gen, -binary.BITS31, binary.BITS31) if (newNum > largest) { largest = newNum } if (newNum < smallest) { smallest = newNum } } t.assert(smallest < -1000, 'Smallest number is negative') t.assert(largest > 1000, 'Largest number is positive') t.info(`Largest number generated is ${largest} (0x${largest.toString(16)})`) t.info(`Smallest number generated is ${smallest} (0x${smallest.toString(16)})`) t.assert((smallest & binary.BIT32) !== 0, 'Largest number is 32 bits long') // largest.. assuming we convert int to uint }) t.group('uint32 - generates unsigned integer with 32 bits', () => { let num = 0 let i let newNum for (i = 0; i < genTestData; i++) { newNum = prng.uint32(gen, 0, binary.BITS32) if (newNum > num) { num = newNum } } t.info(`Largest number generated is ${num} (0x${num.toString(16)})`) t.assert((num & binary.BIT32) !== 0, 'Largest number is 32 bits long.') }) t.group('int53 - generates integer exceeding 32 bits', () => { let largest = 0 let smallest = 0 let i let newNum for (i = 0; i < genTestData; i++) { newNum = prng.int53(gen, Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER) if (newNum > largest) { largest = newNum } if (newNum < smallest) { smallest = newNum } } t.assert(smallest < -1000, 'Smallest number is negative') t.assert(largest > 1000, 'Largest number is positive') t.info(`Largest number generated is ${largest}`) t.info(`Smallest number generated is ${smallest}`) t.assert(largest > (binary.BITS32 >>> 0), 'Largest number exceeds BITS32') t.assert(smallest < binary.BITS32, 'Smallest Number is smaller than BITS32 (negative)') }) t.group('uint53 - generates integer exceeding 32 bits', () => { let largest = 0 let smallest = 10000 let i let newNum for (i = 0; i < genTestData; i++) { newNum = prng.uint53(gen, 0, Number.MAX_SAFE_INTEGER) if (newNum > largest) { largest = newNum } /* c8 ignore next */ if (newNum < smallest) { smallest = newNum } } t.assert(smallest >= 0, 'Smallest number is not negative') t.assert(largest > 1000, 'Largest number is positive') t.info(`Largest number generated is ${largest}`) t.info(`Smallest number generated is ${smallest}`) t.assert(largest > (binary.BITS32 >>> 0), 'Largest number exceeds BITS32') }) t.group('int31 - generates integer with 31 bits', () => { let num = 0 let i let newNum for (i = 0; i < genTestData; i++) { newNum = prng.uint32(gen, 0, binary.BITS31) if (newNum > num) { num = newNum } } t.info(`Largest number generated is ${num} (0x${num.toString(16)})`) t.assert((num & binary.BIT31) !== 0, 'Largest number is 31 bits long.') }) t.group('real - has 53 bit resolution', () => { let num = 0 let i let newNum for (i = 0; i < genTestData; i++) { newNum = prng.real53(gen) * MAX_SAFE_INTEGER if (newNum > num) { num = newNum } } t.info(`Largest number generated is ${num}.`) t.assert((MAX_SAFE_INTEGER - num) / MAX_SAFE_INTEGER < 0.01, 'Largest number is close to MAX_SAFE_INTEGER (at most 1% off).') }) t.group('char - generates all ascii characters', () => { const charSet = new Set() const chars = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[/]^_`abcdefghijklmnopqrstuvwxyz{|}~"' for (let i = chars.length - 1; i >= 0; i--) { charSet.add(chars[i]) } for (let i = 0; i < genTestData; i++) { const char = prng.char(gen) charSet.delete(char) } t.info(`Charactes missing: ${charSet.size} - generating all of "${chars}"`) t.assert(charSet.size === 0, 'Generated all documented characters.') }) } /** * @param {t.TestCase} tc */ export const testGeneratorXoroshiro128plus = tc => runGenTest(tc, new Xoroshiro128plus(tc.seed)) /** * @param {t.TestCase} tc */ export const testGeneratorXorshift32 = tc => { runGenTest(tc, new Xorshift32(tc.seed)) } /** * @param {t.TestCase} tc */ export const testGeneratorMt19937 = tc => { runGenTest(tc, new Mt19937(tc.seed)) } /* c8 ignore next */ /** * @param {prng.PRNG} gen * @param {t.TestCase} _tc */ const printDistribution = (gen, _tc) => { const DIAMETER = genTestData / 50 const canvas = dom.canvas(DIAMETER * 3, DIAMETER) const ctx = canvas.getContext('2d') if (ctx == null) { return t.skip() } ctx.fillStyle = 'blue' for (let i = 0; i < genTestData; i++) { const x = prng.int32(gen, 0, DIAMETER * 3) const y = prng.int32(gen, 0, DIAMETER) ctx.fillRect(x, y, 1, 2) } t.printCanvas(canvas, DIAMETER) } /* c8 ignore next */ /** * @param {t.TestCase} tc */ export const testNumberDistributions = tc => { t.skip(!isBrowser) t.group('Xoroshiro128plus', () => printDistribution(new Xoroshiro128plus(tc.seed), tc)) t.group('Xorshift32', () => printDistribution(new Xorshift32(tc.seed), tc)) t.group('MT19937', () => printDistribution(new Mt19937(tc.seed), tc)) } dmonad-lib0-c7e7806/prng/000077500000000000000000000000001512504553500151205ustar00rootroot00000000000000dmonad-lib0-c7e7806/prng/Mt19937.js000066400000000000000000000037271512504553500164640ustar00rootroot00000000000000import * as binary from '../binary.js' import * as math from '../math.js' /** * @module prng */ const N = 624 const M = 397 /** * @param {number} u * @param {number} v */ const twist = (u, v) => ((((u & 0x80000000) | (v & 0x7fffffff)) >>> 1) ^ ((v & 1) ? 0x9908b0df : 0)) /** * @param {Uint32Array} state */ const nextState = state => { let p = 0 let j for (j = N - M + 1; --j; p++) { state[p] = state[p + M] ^ twist(state[p], state[p + 1]) } for (j = M; --j; p++) { state[p] = state[p + M - N] ^ twist(state[p], state[p + 1]) } state[p] = state[p + M - N] ^ twist(state[p], state[0]) } /** * This is a port of Shawn Cokus's implementation of the original Mersenne Twister algorithm (http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/MT2002/CODES/MTARCOK/mt19937ar-cok.c). * MT has a very high period of 2^19937. Though the authors of xorshift describe that a high period is not * very relevant (http://vigna.di.unimi.it/xorshift/). It is four times slower than xoroshiro128plus and * needs to recompute its state after generating 624 numbers. * * ```js * const gen = new Mt19937(new Date().getTime()) * console.log(gen.next()) * ``` * * @public */ export class Mt19937 { /** * @param {number} seed Unsigned 32 bit number */ constructor (seed) { this.seed = seed const state = new Uint32Array(N) state[0] = seed for (let i = 1; i < N; i++) { state[i] = (math.imul(1812433253, (state[i - 1] ^ (state[i - 1] >>> 30))) + i) & binary.BITS32 } this._state = state this._i = 0 nextState(this._state) } /** * Generate a random signed integer. * * @return {Number} A 32 bit signed integer. */ next () { if (this._i === N) { // need to compute a new state nextState(this._state) this._i = 0 } let y = this._state[this._i++] y ^= (y >>> 11) y ^= (y << 7) & 0x9d2c5680 y ^= (y << 15) & 0xefc60000 y ^= (y >>> 18) return (y >>> 0) / (binary.BITS32 + 1) } } dmonad-lib0-c7e7806/prng/Xoroshiro128plus.js000066400000000000000000000065201512504553500206140ustar00rootroot00000000000000/** * @module prng */ import { Xorshift32 } from './Xorshift32.js' import * as binary from '../binary.js' /** * 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) */ export 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() * binary.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) / (binary.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) / (binary.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 #include 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; } */ dmonad-lib0-c7e7806/prng/Xorshift32.js000066400000000000000000000011331512504553500174270ustar00rootroot00000000000000/** * @module prng */ import * as binary from '../binary.js' /** * Xorshift32 is a very simple but elegang PRNG with a period of `2^32-1`. */ export 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) / (binary.BITS32 + 1) } } dmonad-lib0-c7e7806/promise.js000066400000000000000000000057511512504553500161760ustar00rootroot00000000000000/** * Utility helpers to work with promises. * * @module promise */ import * as time from './time.js' /** * @template T * @callback PromiseResolve * @param {T|PromiseLike} [result] */ /** * @template T * @param {function(PromiseResolve,function(Error):void):any} f * @return {Promise} */ export const create = f => /** @type {Promise} */ (new Promise(f)) /** * @param {function(function():void,function(Error):void):void} f * @return {Promise} */ export const createEmpty = f => new Promise(f) /** * `Promise.all` wait for all promises in the array to resolve and return the result * @template {unknown[] | []} PS * * @param {PS} ps * @return {Promise<{ -readonly [P in keyof PS]: Awaited }>} */ export const all = Promise.all.bind(Promise) /** * @param {Error} [reason] * @return {Promise} */ export const reject = reason => Promise.reject(reason) /** * @template T * @param {T|void} res * @return {Promise} */ export const resolve = res => Promise.resolve(res) /** * @template T * @param {T} res * @return {Promise} */ export const resolveWith = res => Promise.resolve(res) /** * @todo Next version, reorder parameters: check, [timeout, [intervalResolution]] * @deprecated use untilAsync instead * * @param {number} timeout * @param {function():boolean} check * @param {number} [intervalResolution] * @return {Promise} */ export const until = (timeout, check, intervalResolution = 10) => create((resolve, reject) => { const startTime = time.getUnixTime() const hasTimeout = timeout > 0 const untilInterval = () => { if (check()) { clearInterval(intervalHandle) resolve() } else if (hasTimeout) { /* c8 ignore else */ if (time.getUnixTime() - startTime > timeout) { clearInterval(intervalHandle) reject(new Error('Timeout')) } } } const intervalHandle = setInterval(untilInterval, intervalResolution) }) /** * @param {()=>Promise|boolean} check * @param {number} timeout * @param {number} intervalResolution * @return {Promise} */ export const untilAsync = async (check, timeout = 0, intervalResolution = 10) => { const startTime = time.getUnixTime() const noTimeout = timeout <= 0 // eslint-disable-next-line no-unmodified-loop-condition while (noTimeout || time.getUnixTime() - startTime <= timeout) { if (await check()) return await wait(intervalResolution) } throw new Error('Timeout') } /** * @param {number} timeout * @return {Promise} */ export const wait = timeout => create((resolve, _reject) => setTimeout(resolve, timeout)) /** * Checks 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. * * @param {any} p * @return {boolean} */ export const isPromise = p => p instanceof Promise || (p && p.then && p.catch && p.finally) dmonad-lib0-c7e7806/promise.test.js000066400000000000000000000047431512504553500171540ustar00rootroot00000000000000import * as promise from './promise.js' import * as t from './testing.js' import * as time from './time.js' import * as error from './error.js' /** * @param {Promise} p * @param {number} min * @param {number} max */ const measureP = (p, min, max) => { const start = time.getUnixTime() return p.then(() => { const duration = time.getUnixTime() - start t.assert(duration <= max, 'Expected promise to take less time') t.assert(duration >= min, 'Expected promise to take more time') }) } /** * @template T * @param {Promise} p * @return {Promise} */ const failsP = p => promise.create((resolve, reject) => p.then(() => reject(error.create('Promise should fail')), resolve)) /** * @param {t.TestCase} _tc */ export const testRepeatPromise = async _tc => { t.assert(promise.createEmpty(r => r()).constructor === Promise, 'p.create() creates a Promise') t.assert(promise.resolve().constructor === Promise, 'p.reject() creates a Promise') const rejectedP = promise.reject() t.assert(rejectedP.constructor === Promise, 'p.reject() creates a Promise') rejectedP.catch(() => {}) await promise.createEmpty(r => r()) await failsP(promise.reject()) await promise.resolve() await measureP(promise.wait(10), 7, 1000) await measureP(failsP(promise.until(15, () => false)), 15, 1000) await measureP(failsP(promise.untilAsync(() => false, 15)), 15, 1000) const startTime = time.getUnixTime() await measureP(promise.until(0, () => (time.getUnixTime() - startTime) > 100), 100, 1000) const startTime2 = time.getUnixTime() await measureP(promise.untilAsync(() => (time.getUnixTime() - startTime2) > 100), 100, 1000) await promise.all([promise.wait(5), promise.wait(10)]) } /** * @param {t.TestCase} _tc */ export const testispromise = _tc => { t.assert(promise.isPromise(new Promise(() => {}))) t.assert(promise.isPromise(promise.create(() => {}))) const rej = promise.reject() t.assert(promise.isPromise(rej)) rej.catch(() => {}) t.assert(promise.isPromise(promise.resolve())) t.assert(promise.isPromise({ then: () => {}, catch: () => {}, finally: () => {} })) t.fails(() => { t.assert(promise.isPromise({ then: () => {}, catch: () => {} })) }) } /** * @param {t.TestCase} _tc */ export const testTypings = async _tc => { const ps = await promise.all([promise.resolveWith(4), 'string']) /** * @type {number} */ const a = ps[0] /** * @type {string} */ const b = ps[1] t.assert(typeof a === 'number' && typeof b === 'string') } dmonad-lib0-c7e7806/queue.js000066400000000000000000000024721512504553500156410ustar00rootroot00000000000000export class QueueNode { constructor () { /** * @type {QueueNode|null} */ this.next = null } } /** * @template V */ export class QueueValue extends QueueNode { /** * @param {V} v */ constructor (v) { super() this.v = v } } /** * @template {QueueNode} N */ export class Queue { constructor () { /** * @type {N | null} */ this.start = null /** * @type {N | null} */ this.end = null } } /** * @note The queue implementation is experimental and unfinished. * Don't use this in production yet. * * @template {QueueNode} N * @return {Queue} */ export const create = () => new Queue() /** * @param {Queue} queue */ export const isEmpty = queue => queue.start === null /** * @template {Queue} Q * @param {Q} queue * @param {Q extends Queue ? N : never} n */ export const enqueue = (queue, n) => { if (queue.end !== null) { queue.end.next = n queue.end = n } else { queue.end = n queue.start = n } } /** * @template {QueueNode} N * @param {Queue} queue * @return {N | null} */ export const dequeue = queue => { const n = queue.start if (n !== null) { // @ts-ignore queue.start = n.next if (queue.start === null) { queue.end = null } return n } return null } dmonad-lib0-c7e7806/queue.test.js000066400000000000000000000016211512504553500166120ustar00rootroot00000000000000import * as t from './testing.js' import * as queue from './queue.js' /** * @param {t.TestCase} _tc */ export const testEnqueueDequeue = _tc => { const N = 30 /** * @type {queue.Queue>} */ const q = queue.create() t.assert(queue.isEmpty(q)) t.assert(queue.dequeue(q) === null) for (let i = 0; i < N; i++) { queue.enqueue(q, new queue.QueueValue(i)) t.assert(!queue.isEmpty(q)) } for (let i = 0; i < N; i++) { const item = queue.dequeue(q) t.assert(item !== null && item.v === i) } t.assert(queue.isEmpty(q)) t.assert(queue.dequeue(q) === null) for (let i = 0; i < N; i++) { queue.enqueue(q, new queue.QueueValue(i)) t.assert(!queue.isEmpty(q)) } for (let i = 0; i < N; i++) { const item = queue.dequeue(q) t.assert(item !== null && item.v === i) } t.assert(queue.isEmpty(q)) t.assert(queue.dequeue(q) === null) } dmonad-lib0-c7e7806/random.js000066400000000000000000000016331512504553500157730ustar00rootroot00000000000000/** * Isomorphic module for true random numbers / buffers / uuids. * * Attention: falls back to Math.random if the browser does not support crypto. * * @module random */ import * as math from './math.js' import * as binary from './binary.js' import { getRandomValues } from 'lib0/webcrypto' export const rand = Math.random export const uint32 = () => getRandomValues(new Uint32Array(1))[0] export const uint53 = () => { const arr = getRandomValues(new Uint32Array(8)) return (arr[0] & binary.BITS21) * (binary.BITS32 + 1) + (arr[1] >>> 0) } /** * @template T * @param {Array} arr * @return {T} */ export const oneOf = arr => arr[math.floor(rand() * arr.length)] // @ts-ignore const uuidv4Template = [1e7] + -1e3 + -4e3 + -8e3 + -1e11 /** * @return {string} */ export const uuidv4 = () => uuidv4Template.replace(/[018]/g, /** @param {number} c */ c => (c ^ uint32() & 15 >> c / 4).toString(16) ) dmonad-lib0-c7e7806/random.test.js000066400000000000000000000053071512504553500167530ustar00rootroot00000000000000import * as random from './random.js' import * as t from './testing.js' import * as binary from './binary.js' import * as math from './math.js' import * as number from './number.js' /** * @param {t.TestCase} tc */ export const testRandom = tc => { const res = random.oneOf([1, 2, 3]) t.assert(res > 0) } /** * @param {t.TestCase} tc */ export const testUint32 = tc => { const iterations = 10000 let largest = 0 let smallest = number.HIGHEST_INT32 let newNum = 0 let lenSum = 0 let ones = 0 for (let i = 0; i < iterations; i++) { newNum = random.uint32() lenSum += newNum.toString().length ones += newNum.toString(2).split('').filter(x => x === '1').length if (newNum > largest) { largest = newNum } if (newNum < smallest) { smallest = newNum } } t.info(`Largest number generated is ${largest} (0x${largest.toString(16)})`) t.info(`Smallest number generated is ${smallest} (0x${smallest.toString(16)})`) t.info(`Average decimal length of number is ${lenSum / iterations}`) t.info(`Average number of 1s in number is ${ones / iterations} (expecting ~16)`) t.assert(((largest & binary.BITS32) >>> 0) === largest, 'Largest number is 32 bits long.') t.assert(((smallest & binary.BITS32) >>> 0) === smallest, 'Smallest number is 32 bits long.') } /** * @param {t.TestCase} tc */ export const testUint53 = tc => { const iterations = 10000 let largest = 0 let smallest = number.MAX_SAFE_INTEGER let newNum = 0 let lenSum = 0 let ones = 0 for (let i = 0; i < iterations; i++) { newNum = random.uint53() lenSum += newNum.toString().length ones += newNum.toString(2).split('').filter(x => x === '1').length if (newNum > largest) { largest = newNum } if (newNum < smallest) { smallest = newNum } } t.info(`Largest number generated is ${largest}`) t.info(`Smallest number generated is ${smallest}`) t.info(`Average decimal length of number is ${lenSum / iterations}`) t.info(`Average number of 1s in number is ${ones / iterations} (expecting ~26.5)`) t.assert(largest > number.MAX_SAFE_INTEGER * 0.9) } /** * @param {t.TestCase} tc */ export const testUuidv4 = tc => { t.info(`Generated a UUIDv4: ${random.uuidv4()}`) } /** * @param {t.TestCase} tc */ export const testUuidv4Overlaps = tc => { const iterations = t.extensive ? 1000000 : 10000 const uuids = new Set() for (let i = 0; i < iterations; i++) { const uuid = random.uuidv4() if (uuids.has(uuid)) { t.fail('uuid already exists') } else { uuids.add(uuid) } if (uuids.size % (iterations / 20) === 0) { t.info(`${math.round(uuids.size * 100 / iterations)}% complete`) } } t.assert(uuids.size === iterations) } dmonad-lib0-c7e7806/rollup.config.js000066400000000000000000000011541512504553500172720ustar00rootroot00000000000000import * as fs from 'fs' const roots = ['./', './crypto/', './hash/', './diff/', './delta/', './trait/'] const files = roots.map(root => fs.readdirSync(root).map(f => root + f)).flat().filter(file => /(? ? X : T} Unwrap */ /** * @template T * @typedef {T extends Schema ? X : T} TypeOf */ /** * @template {readonly unknown[]} T * @typedef {T extends readonly [Schema, ...infer Rest] ? [First, ...UnwrapArray] : [] } UnwrapArray */ /** * @template T * @typedef {T extends Schema ? Schema : never} CastToSchema */ /** * @template {unknown[]} Arr * @typedef {Arr extends [...unknown[], infer L] ? L : never} TupleLast */ /** * @template {unknown[]} Arr * @typedef {Arr extends [...infer Fs, unknown] ? Fs : never} TuplePop */ /** * @template {readonly unknown[]} T * @typedef {T extends [] * ? {} * : T extends [infer First] * ? First * : T extends [infer First, ...infer Rest] * ? First & Intersect * : never * } Intersect */ const schemaSymbol = Symbol('0schema') export class ValidationError { constructor () { /** * Reverse errors * @type {Array<{ path: string?, expected: string, has: string, message: string? }>} */ this._rerrs = [] } /** * @param {string?} path * @param {string} expected * @param {string} has * @param {string?} message */ extend (path, expected, has, message = null) { this._rerrs.push({ path, expected, has, message }) } toString () { const s = [] for (let i = this._rerrs.length - 1; i > 0; i--) { const r = this._rerrs[i] /* c8 ignore next */ s.push(string.repeat(' ', (this._rerrs.length - i) * 2) + `${r.path != null ? `[${r.path}] ` : ''}${r.has} doesn't match ${r.expected}. ${r.message}`) } return s.join('\n') } } /** * @param {any} a * @param {any} b * @return {boolean} */ const shapeExtends = (a, b) => { if (a === b) return true if (a == null || b == null || a.constructor !== b.constructor) return false if (a[equalityTraits.EqualityTraitSymbol]) return equalityTraits.equals(a, b) // last resort: check equality (do this before array and obj check which don't implement the equality trait) if (arr.isArray(a)) { return arr.every(a, aitem => arr.some(b, bitem => shapeExtends(aitem, bitem)) ) } else if (obj.isObject(a)) { return obj.every(a, (aitem, akey) => shapeExtends(aitem, b[akey]) ) } /* c8 ignore next */ return false } /** * @template T * @implements {equalityTraits.EqualityTrait} */ export class Schema { // this.shape must not be defined on Schema. Otherwise typecheck on metatypes (e.g. $$object) won't work as expected anymore /** * If true, the more things are added to the shape the more objects this schema will accept (e.g. * union). By default, the more objects are added, the the fewer objects this schema will accept. * @protected */ static _dilutes = false /** * @param {Schema} other */ extends (other) { let [a, b] = [/** @type {any} */(this).shape, /** @type {any} */ (other).shape] if (/** @type {typeof Schema} */ (this.constructor)._dilutes) [b, a] = [a, b] return shapeExtends(a, b) } /** * Overwrite this when necessary. By default, we only check the `shape` property which every shape * should have. * @param {Schema} other */ equals (other) { // @ts-ignore return this.constructor === other.constructor && fun.equalityDeep(this.shape, other.shape) } [schemaSymbol] () { return true } /** * @param {object} other */ [equalityTraits.EqualityTraitSymbol] (other) { return this.equals(/** @type {any} */ (other)) } /** * Use `schema.validate(obj)` with a typed parameter that is already of typed to be an instance of * Schema. Validate will check the structure of the parameter and return true iff the instance * really is an instance of Schema. * * @param {T} o * @return {boolean} */ validate (o) { return this.check(o) } /* c8 ignore start */ /** * Similar to validate, but this method accepts untyped parameters. * * @param {any} _o * @param {ValidationError} [_err] * @return {_o is T} */ check (_o, _err) { error.methodUnimplemented() } /* c8 ignore stop */ /** * @type {Schema} */ get nullable () { // @ts-ignore return $union(this, $null) } /** * @type {$Optional>} */ get optional () { return new $Optional(/** @type {Schema} */ (this)) } /** * Cast a variable to a specific type. Returns the casted value, or throws an exception otherwise. * Use this if you know that the type is of a specific type and you just want to convince the type * system. * * **Do not rely on these error messages!** * Performs an assertion check only if not in a production environment. * * @template OO * @param {OO} o * @return {Extract extends never ? T : (OO extends Array ? T : Extract)} */ cast (o) { assert(o, this) return /** @type {any} */ (o) } /** * EXPECTO PATRONUM!! 🪄 * This function protects against type errors. Though it may not work in the real world. * * "After all this time?" * "Always." - Snape, talking about type safety * * Ensures that a variable is a a specific type. Returns the value, or throws an exception if the assertion check failed. * Use this if you know that the type is of a specific type and you just want to convince the type * system. * * Can be useful when defining lambdas: `s.lambda(s.$number, s.$void).expect((n) => n + 1)` * * **Do not rely on these error messages!** * Performs an assertion check if not in a production environment. * * @param {T} o * @return {o extends T ? T : never} */ expect (o) { assert(o, this) return o } } /** * @template {(new (...args:any[]) => any) | ((...args:any[]) => any)} Constr * @typedef {Constr extends ((...args:any[]) => infer T) ? T : (Constr extends (new (...args:any[]) => any) ? InstanceType : never)} Instance */ /** * @template {(new (...args:any[]) => any) | ((...args:any[]) => any)} C * @extends {Schema>} */ export class $ConstructedBy extends Schema { /** * @param {C} c * @param {((o:Instance)=>boolean)|null} check */ constructor (c, check) { super() this.shape = c this._c = check } /** * @param {any} o * @param {ValidationError} [err] * @return {o is C extends ((...args:any[]) => infer T) ? T : (C extends (new (...args:any[]) => any) ? InstanceType : never)} o */ check (o, err = undefined) { const c = o?.constructor === this.shape && (this._c == null || this._c(o)) /* c8 ignore next */ !c && err?.extend(null, this.shape.name, o?.constructor.name, o?.constructor !== this.shape ? 'Constructor match failed' : 'Check failed') return c } } /** * @template {(new (...args:any[]) => any) | ((...args:any[]) => any)} C * @param {C} c * @param {((o:Instance) => boolean)|null} check * @return {CastToSchema<$ConstructedBy>} */ export const $constructedBy = (c, check = null) => new $ConstructedBy(c, check) export const $$constructedBy = $constructedBy($ConstructedBy) /** * Check custom properties on any object. You may want to overwrite the generated Schema. * * @extends {Schema} */ export class $Custom extends Schema { /** * @param {(o:any) => boolean} check */ constructor (check) { super() /** * @type {(o:any) => boolean} */ this.shape = check } /** * @param {any} o * @param {ValidationError} err * @return {o is any} */ check (o, err) { const c = this.shape(o) /* c8 ignore next */ !c && err?.extend(null, 'custom prop', o?.constructor.name, 'failed to check custom prop') return c } } /** * @param {(o:any) => boolean} check * @return {Schema} */ export const $custom = (check) => new $Custom(check) export const $$custom = $constructedBy($Custom) /** * @template {Primitive} T * @extends {Schema} */ export class $Literal extends Schema { /** * @param {Array} literals */ constructor (literals) { super() this.shape = literals } /** * * @param {any} o * @param {ValidationError} [err] * @return {o is T} */ check (o, err) { const c = this.shape.some(a => a === o) /* c8 ignore next */ !c && err?.extend(null, this.shape.join(' | '), o.toString()) return c } } /** * @template {Primitive[]} T * @param {T} literals * @return {CastToSchema<$Literal>} */ export const $literal = (...literals) => new $Literal(literals) export const $$literal = $constructedBy($Literal) /** * @template {Array>} Ts * @typedef {Ts extends [] ? `` : (Ts extends [infer T] ? (Unwrap extends (string|number) ? Unwrap : never) : (Ts extends [infer T1, ...infer Rest] ? `${Unwrap extends (string|number) ? Unwrap : never}${Rest extends Array> ? CastStringTemplateArgsToTemplate : never}` : never))} CastStringTemplateArgsToTemplate */ /** * @param {string} str * @return {string} */ const _regexEscape = /** @type {any} */ (RegExp).escape || /** @type {(str:string) => string} */ (str => str.replace(/[().|&,$^[\]]/g, s => '\\' + s) ) /** * @param {string|Schema} s * @return {string[]} */ const _schemaStringTemplateToRegex = s => { if ($string.check(s)) { return [_regexEscape(s)] } if ($$literal.check(s)) { return /** @type {Array} */ (s.shape).map(v => v + '') } if ($$number.check(s)) { return ['[+-]?\\d+.?\\d*'] } if ($$string.check(s)) { return ['.*'] } if ($$union.check(s)) { return s.shape.map(_schemaStringTemplateToRegex).flat(1) } /* c8 ignore next 2 */ // unexpected schema structure (only supports unions and string in literal types) error.unexpectedCase() } /** * @template {Array>} T * @extends {Schema>} */ export class $StringTemplate extends Schema { /** * @param {T} shape */ constructor (shape) { super() this.shape = shape this._r = new RegExp('^' + shape.map(_schemaStringTemplateToRegex).map(opts => `(${opts.join('|')})`).join('') + '$') } /** * @param {any} o * @param {ValidationError} [err] * @return {o is CastStringTemplateArgsToTemplate} */ check (o, err) { const c = this._r.exec(o) != null /* c8 ignore next */ !c && err?.extend(null, this._r.toString(), o.toString(), 'String doesn\'t match string template.') return c } } /** * @template {Array>} T * @param {T} literals * @return {CastToSchema<$StringTemplate>} */ export const $stringTemplate = (...literals) => new $StringTemplate(literals) export const $$stringTemplate = $constructedBy($StringTemplate) const isOptionalSymbol = Symbol('optional') /** * @template {Schema} S * @extends Schema|undefined> */ class $Optional extends Schema { /** * @param {S} shape */ constructor (shape) { super() this.shape = shape } /** * @param {any} o * @param {ValidationError} [err] * @return {o is (Unwrap|undefined)} */ check (o, err) { const c = o === undefined || this.shape.check(o) /* c8 ignore next */ !c && err?.extend(null, 'undefined (optional)', '()') return c } get [isOptionalSymbol] () { return true } } export const $$optional = $constructedBy($Optional) /** * @extends Schema */ class $Never extends Schema { /** * @param {any} _o * @param {ValidationError} [err] * @return {_o is never} */ check (_o, err) { /* c8 ignore next */ err?.extend(null, 'never', typeof _o) return false } } /** * @type {Schema} */ export const $never = new $Never() export const $$never = $constructedBy($Never) /** * @template {{ [key: string|symbol|number]: Schema }} S * @typedef {{ [Key in keyof S as S[Key] extends $Optional> ? Key : never]?: S[Key] extends $Optional> ? Type : never } & { [Key in keyof S as S[Key] extends $Optional> ? never : Key]: S[Key] extends Schema ? Type : never }} $ObjectToType */ /** * @template {{[key:string|symbol|number]: Schema}} S * @extends {Schema<$ObjectToType>} */ export class $Object extends Schema { /** * @param {S} shape * @param {boolean} partial */ constructor (shape, partial = false) { super() /** * @type {S} */ this.shape = shape this._isPartial = partial } static _dilutes = true /** * @type {Schema>>} */ get partial () { return new $Object(this.shape, true) } /** * @param {any} o * @param {ValidationError} err * @return {o is $ObjectToType} */ check (o, err) { if (o == null) { /* c8 ignore next */ err?.extend(null, 'object', 'null') return false } return obj.every(this.shape, (vv, vk) => { const c = (this._isPartial && !obj.hasProperty(o, vk)) || vv.check(o[vk], err) !c && err?.extend(vk.toString(), vv.toString(), typeof o[vk], 'Object property does not match') return c }) } } /** * @template S * @typedef {Schema<{ [Key in keyof S as S[Key] extends $Optional> ? Key : never]?: S[Key] extends $Optional> ? Type : never } & { [Key in keyof S as S[Key] extends $Optional> ? never : Key]: S[Key] extends Schema ? Type : never }>} _ObjectDefToSchema */ // I used an explicit type annotation instead of $ObjectToType, so that the user doesn't see the // weird type definitions when inspecting type definions. /** * @template {{ [key:string|symbol|number]: Schema }} S * @param {S} def * @return {_ObjectDefToSchema extends Schema ? Schema<{ [K in keyof S]: S[K] }> : never} */ export const $object = def => /** @type {any} */ (new $Object(def)) export const $$object = $constructedBy($Object) /** * @type {Schema<{[key:string]: any}>} */ export const $objectAny = $custom(o => o != null && (o.constructor === Object || o.constructor == null)) /** * @template {Schema} Keys * @template {Schema} Values * @extends {Schema<{ [key in Unwrap]: Unwrap }>} */ export class $Record extends Schema { /** * @param {Keys} keys * @param {Values} values */ constructor (keys, values) { super() this.shape = { keys, values } } /** * @param {any} o * @param {ValidationError} err * @return {o is { [key in Unwrap]: Unwrap }} */ check (o, err) { return o != null && obj.every(o, (vv, vk) => { const ck = this.shape.keys.check(vk, err) /* c8 ignore next */ !ck && err?.extend(vk + '', 'Record', typeof o, ck ? 'Key doesn\'t match schema' : 'Value doesn\'t match value') return ck && this.shape.values.check(vv, err) }) } } /** * @template {Schema} Keys * @template {Schema} Values * @param {Keys} keys * @param {Values} values * @return {CastToSchema<$Record>} */ export const $record = (keys, values) => new $Record(keys, values) export const $$record = $constructedBy($Record) /** * @template {Schema[]} S * @extends {Schema<{ [Key in keyof S]: S[Key] extends Schema ? Type : never }>} */ export class $Tuple extends Schema { /** * @param {S} shape */ constructor (shape) { super() this.shape = shape } /** * @param {any} o * @param {ValidationError} err * @return {o is { [K in keyof S]: S[K] extends Schema ? Type : never }} */ check (o, err) { return o != null && obj.every(this.shape, (vv, vk) => { const c = /** @type {Schema} */ (vv).check(o[vk], err) /* c8 ignore next */ !c && err?.extend(vk.toString(), 'Tuple', typeof vv) return c }) } } /** * @template {Array>} T * @param {T} def * @return {CastToSchema<$Tuple>} */ export const $tuple = (...def) => new $Tuple(def) export const $$tuple = $constructedBy($Tuple) /** * @template {Schema} S * @extends {Schema ? T : never>>} */ export class $Array extends Schema { /** * @param {Array} v */ constructor (v) { super() /** * @type {Schema ? T : never>} */ this.shape = v.length === 1 ? v[0] : new $Union(v) } /** * @param {any} o * @param {ValidationError} [err] * @return {o is Array ? T : never>} o */ check (o, err) { const c = arr.isArray(o) && arr.every(o, oi => this.shape.check(oi)) /* c8 ignore next */ !c && err?.extend(null, 'Array', '') return c } } /** * @template {Array>} T * @param {T} def * @return {Schema> ? S : never>>} */ export const $array = (...def) => new $Array(def) export const $$array = $constructedBy($Array) /** * @type {Schema>} */ export const $arrayAny = $custom(o => arr.isArray(o)) /** * @template T * @extends {Schema} */ export class $InstanceOf extends Schema { /** * @param {new (...args:any) => T} constructor * @param {((o:T) => boolean)|null} check */ constructor (constructor, check) { super() this.shape = constructor this._c = check } /** * @param {any} o * @param {ValidationError} err * @return {o is T} */ check (o, err) { const c = o instanceof this.shape && (this._c == null || this._c(o)) /* c8 ignore next */ !c && err?.extend(null, this.shape.name, o?.constructor.name) return c } } /** * @template T * @param {new (...args:any) => T} c * @param {((o:T) => boolean)|null} check * @return {Schema} */ export const $instanceOf = (c, check = null) => new $InstanceOf(c, check) export const $$instanceOf = $constructedBy($InstanceOf) export const $$schema = $instanceOf(Schema) /** * @template {Schema[]} Args * @typedef {(...args:UnwrapArray>)=>Unwrap>} _LArgsToLambdaDef */ /** * @template {Array>} Args * @extends {Schema<_LArgsToLambdaDef>} */ export class $Lambda extends Schema { /** * @param {Args} args */ constructor (args) { super() this.len = args.length - 1 this.args = $tuple(...args.slice(-1)) this.res = args[this.len] } /** * @param {any} f * @param {ValidationError} err * @return {f is _LArgsToLambdaDef} */ check (f, err) { const c = f.constructor === Function && f.length <= this.len /* c8 ignore next */ !c && err?.extend(null, 'function', typeof f) return c } } /** * @template {Schema[]} Args * @param {Args} args * @return {Schema<(...args:UnwrapArray>)=>Unwrap>>} */ export const $lambda = (...args) => new $Lambda(args.length > 0 ? args : [$void]) export const $$lambda = $constructedBy($Lambda) /** * @type {Schema} */ export const $function = $custom(o => typeof o === 'function') /** * @template {Array>} T * @extends {Schema>>} */ export class $Intersection extends Schema { /** * @param {T} v */ constructor (v) { super() /** * @type {T} */ this.shape = v } /** * @param {any} o * @param {ValidationError} [err] * @return {o is Intersect>} */ check (o, err) { // @ts-ignore const c = arr.every(this.shape, check => check.check(o, err)) /* c8 ignore next */ !c && err?.extend(null, 'Intersectinon', typeof o) return c } } /** * @template {Schema[]} T * @param {T} def * @return {CastToSchema<$Intersection>} */ export const $intersect = (...def) => new $Intersection(def) export const $$intersect = $constructedBy($Intersection, o => o.shape.length > 0) // Intersection with length=0 is considered "any" /** * @template S * @extends {Schema} */ export class $Union extends Schema { static _dilutes = true /** * @param {Array>} v */ constructor (v) { super() this.shape = v } /** * @param {any} o * @param {ValidationError} [err] * @return {o is S} */ check (o, err) { const c = arr.some(this.shape, (vv) => vv.check(o, err)) err?.extend(null, 'Union', typeof o) return c } } /** * @template {Array} T * @param {T} schemas * @return {CastToSchema<$Union>>>} */ export const $union = (...schemas) => schemas.findIndex($s => $$union.check($s)) >= 0 ? $union(...schemas.map($s => $($s)).map($s => $$union.check($s) ? $s.shape : [$s]).flat(1)) : (schemas.length === 1 ? schemas[0] : new $Union(schemas)) export const $$union = /** @type {Schema<$Union>} */ ($constructedBy($Union)) const _t = () => true /** * @type {Schema} */ export const $any = $custom(_t) export const $$any = /** @type {Schema>} */ ($constructedBy($Custom, o => o.shape === _t)) /** * @type {Schema} */ export const $bigint = $custom(o => typeof o === 'bigint') export const $$bigint = /** @type {Schema>} */ ($custom(o => o === $bigint)) /** * @type {Schema} */ export const $symbol = $custom(o => typeof o === 'symbol') export const $$symbol = /** @type {Schema>} */ ($custom(o => o === $symbol)) /** * @type {Schema} */ export const $number = $custom(o => typeof o === 'number') export const $$number = /** @type {Schema>} */ ($custom(o => o === $number)) /** * @type {Schema} */ export const $string = $custom(o => typeof o === 'string') export const $$string = /** @type {Schema>} */ ($custom(o => o === $string)) /** * @type {Schema} */ export const $boolean = $custom(o => typeof o === 'boolean') export const $$boolean = /** @type {Schema>} */ ($custom(o => o === $boolean)) /** * @type {Schema} */ export const $undefined = $literal(undefined) export const $$undefined = /** @type {Schema>} */ ($constructedBy($Literal, o => o.shape.length === 1 && o.shape[0] === undefined)) /** * @type {Schema} */ export const $void = $literal(undefined) export const $$void = /** @type {Schema>} */ ($$undefined) export const $null = $literal(null) export const $$null = /** @type {Schema>} */ ($constructedBy($Literal, o => o.shape.length === 1 && o.shape[0] === null)) export const $uint8Array = $constructedBy(Uint8Array) export const $$uint8Array = /** @type {Schema>} */ ($constructedBy($ConstructedBy, o => o.shape === Uint8Array)) /** * @type {Schema} */ export const $primitive = $union($number, $string, $null, $undefined, $bigint, $boolean, $symbol) /** * @typedef {JSON[]} JSONArray */ /** * @typedef {Primitive|JSONArray|{ [key:string]:JSON }} JSON */ /** * @type {Schema} */ export const $json = (() => { const $jsonArr = /** @type {$Array<$any>} */ ($array($any)) const $jsonRecord = /** @type {$Record<$string,$any>} */ ($record($string, $any)) const $json = $union($number, $string, $null, $boolean, $jsonArr, $jsonRecord) $jsonArr.shape = $json $jsonRecord.shape.values = $json return $json })() /** * @template {any} IN * @typedef {IN extends Schema ? IN * : (IN extends string|number|boolean|null ? Schema * : (IN extends new (...args:any[])=>any ? Schema> * : (IN extends any[] ? Schema<{ [K in keyof IN]: Unwrap> }[number]> * : (IN extends object ? (_ObjectDefToSchema<{[K in keyof IN]:ReadSchema}> extends Schema ? Schema<{ [K in keyof S]: S[K] }> : never) * : never) * ) * ) * ) * } ReadSchemaOld */ /** * @template {any} IN * @typedef {[Extract>,Extract,Extractany>,Extract,Extract|string|number|boolean|null|(new (...args:any[])=>any)|any[]>,object>] extends [infer Schemas, infer Primitives, infer Constructors, infer Arrs, infer Obj] * ? Schema< * (Schemas extends Schema ? S : never) * | Primitives * | (Constructors extends new (...args:any[])=>any ? InstanceType : never) * | (Arrs extends any[] ? { [K in keyof Arrs]: Unwrap> }[number] : never) * | (Obj extends object ? Unwrap<(_ObjectDefToSchema<{[K in keyof Obj]:ReadSchema}> extends Schema ? Schema<{ [K in keyof S]: S[K] }> : never)> : never)> * : never * } ReadSchema */ /** * @typedef {ReadSchema<{x:42}|{y:99}|Schema|[1,2,{}]>} Q */ /** * @template IN * @param {IN} o * @return {ReadSchema} */ export const $ = o => { if ($$schema.check(o)) { return /** @type {any} */ (o) } else if ($objectAny.check(o)) { /** * @type {any} */ const o2 = {} for (const k in o) { o2[k] = $(o[k]) } return /** @type {any} */ ($object(o2)) } else if ($arrayAny.check(o)) { return /** @type {any} */ ($union(...o.map($))) } else if ($primitive.check(o)) { return /** @type {any} */ ($literal(o)) } else if ($function.check(o)) { return /** @type {any} */ ($constructedBy(/** @type {any} */ (o))) } /* c8 ignore next */ error.unexpectedCase() } /* c8 ignore start */ /** * Assert that a variable is of this specific type. * The assertion check is only performed in non-production environments. * * @type {(o:any,schema:Schema) => asserts o is T} */ export const assert = env.production ? () => {} : (o, schema) => { const err = new ValidationError() if (!schema.check(o, err)) { throw error.create(`Expected value to be of type ${schema.constructor.name}.\n${err.toString()}`) } } /* c8 ignore end */ /** * @template In * @template Out * @typedef {{ if: Schema, h: (o:In,state?:any)=>Out }} Pattern */ /** * @template {Pattern} P * @template In * @typedef {ReturnType>['h']>} PatternMatchResult */ /** * @todo move this to separate library * @template {any} [State=undefined] * @template {Pattern} [Patterns=never] */ export class PatternMatcher { /** * @param {Schema} [$state] */ constructor ($state) { /** * @type {Array} */ this.patterns = [] this.$state = $state } /** * @template P * @template R * @param {P} pattern * @param {(o:NoInfer>>,s:State)=>R} handler * @return {PatternMatcher>,R>>} */ if (pattern, handler) { // @ts-ignore this.patterns.push({ if: $(pattern), h: handler }) // @ts-ignore return this } /** * @template R * @param {(o:any,s:State)=>R} h */ else (h) { return this.if($any, h) } /** * @return {State extends undefined * ? >(o:In,state?:undefined)=>PatternMatchResult * : >(o:In,state:State)=>PatternMatchResult} */ done () { // @ts-ignore return /** @type {any} */ (o, s) => { for (let i = 0; i < this.patterns.length; i++) { const p = this.patterns[i] if (p.if.check(o)) { // @ts-ignore return p.h(o, s) } } throw error.create('Unhandled pattern') } } } /** * @template [State=undefined] * @param {State} [state] * @return {PatternMatcher>>} */ export const match = state => new PatternMatcher(/** @type {any} */ (state)) /** * Helper function to generate a (non-exhaustive) sample set from a gives schema. * * @type {(o:T,gen:prng.PRNG)=>T} */ const _random = /** @type {any} */ (match(/** @type {Schema} */ ($any)) .if($$number, (_o, gen) => prng.int53(gen, number.MIN_SAFE_INTEGER, number.MAX_SAFE_INTEGER)) .if($$string, (_o, gen) => prng.word(gen)) .if($$boolean, (_o, gen) => prng.bool(gen)) .if($$bigint, (_o, gen) => BigInt(prng.int53(gen, number.MIN_SAFE_INTEGER, number.MAX_SAFE_INTEGER))) .if($$union, (o, gen) => random(gen, prng.oneOf(gen, o.shape))) .if($$object, (o, gen) => { /** * @type {any} */ const res = {} for (const k in o.shape) { let prop = o.shape[k] if ($$optional.check(prop)) { if (prng.bool(gen)) { continue } prop = prop.shape } res[k] = _random(prop, gen) } return res }) .if($$array, (o, gen) => { const arr = [] const n = prng.int32(gen, 0, 42) for (let i = 0; i < n; i++) { arr.push(random(gen, o.shape)) } return arr }) .if($$literal, (o, gen) => { return prng.oneOf(gen, o.shape) }) .if($$null, (o, gen) => { return null }) .if($$lambda, (o, gen) => { const res = random(gen, o.res) return () => res }) .if($$any, (o, gen) => random(gen, prng.oneOf(gen, [ $number, $string, $null, $undefined, $bigint, $boolean, $array($number), $record($union('a', 'b', 'c'), $number) ]))) .if($$record, (o, gen) => { /** * @type {any} */ const res = {} const keysN = prng.int53(gen, 0, 3) for (let i = 0; i < keysN; i++) { const key = random(gen, o.shape.keys) const val = random(gen, o.shape.values) res[key] = val } return res }) .done()) /** * @template S * @param {prng.PRNG} gen * @param {S} schema * @return {Unwrap>} */ export const random = (gen, schema) => /** @type {any} */ (_random($(schema), gen)) dmonad-lib0-c7e7806/schema.test.js000066400000000000000000000463441512504553500167410ustar00rootroot00000000000000import * as t from './testing.js' import * as s from './schema.js' import * as env from './environment.js' import * as prng from './prng.js' /** * @param {t.TestCase} _tc */ export const testSchemas = _tc => { t.group('number', () => { t.assert(s.$number.validate(42)) // @ts-expect-error t.assert(!s.$number.validate(BigInt(42))) // @ts-expect-error t.assert(!s.$number.validate(undefined)) // @ts-expect-error t.assert(!s.$number.validate(new Date())) }) t.group('bigint', () => { t.assert(s.$bigint.validate(BigInt(42))) // @ts-expect-error t.assert(!s.$bigint.validate([BigInt(42)])) // @ts-expect-error t.assert(!s.$bigint.validate(undefined)) // @ts-expect-error t.assert(!s.$bigint.validate(new Date())) }) t.group('symbol', () => { t.assert(s.$symbol.validate(Symbol('random symbol'))) // @ts-expect-error t.assert(!s.$symbol.validate({})) // @ts-expect-error t.assert(!s.$symbol.validate(undefined)) // @ts-expect-error t.assert(!s.$symbol.validate(new Date())) }) t.group('literal', () => { const myliterals = s.$literal('hi', 4) myliterals.validate('hi') // @ts-expect-error t.assert(!myliterals.validate(undefined)) // @ts-expect-error t.assert(!myliterals.validate(new Date())) }) t.group('object', () => { const myobject = s.$object({ num: s.$number }) const q = /** @type {number} */ (/** @type {any} */ ({ num: 42, x: 9 })) if (myobject.check(q)) { s.$number.validate(q) myobject.validate(q) } else { // q is a number now s.$number.validate(q) } t.assert(!myobject.check(42)) t.assert(myobject.check({ num: 42, x: 9 })) // @ts-expect-error t.assert(!myobject.validate(undefined)) // @ts-expect-error t.assert(!myobject.validate(new Date())) }) t.group('record', () => { const myrecord = s.$record(s.$number, s.$string) // @ts-expect-error t.assert(!myrecord.validate({ a: 'a' })) const myrecord2 = s.$record(s.$string, s.$number) const o = { a: 42 } t.assert(myrecord2.validate(o)) }) t.group('tuple', () => { const mytuple = s.$tuple(s.$number, s.$string) t.assert(mytuple.validate([4, '5'])) // @ts-expect-error t.assert(mytuple.validate([4, '5', 6])) // @ts-expect-error t.assert(!mytuple.validate(['4', 5])) // @ts-expect-error t.assert(!mytuple.validate(undefined)) // @ts-expect-error t.assert(!mytuple.validate(new Date())) }) t.group('instance', () => { class Base { x () {} } class BetterBase extends Base { y () {} } class BetterBetterBase extends BetterBase { } const z = s.$instanceOf(Base) t.assert(z.validate(new Base())) t.assert(z.validate(new BetterBase())) t.assert(s.$instanceOf(Base, o => /** @type {any} */ (o).y != null).validate(new BetterBase())) // @ts-expect-error t.assert(!z.validate(4)) t.assert(!s.$instanceOf(BetterBetterBase).validate(new BetterBase())) // @ts-expect-error t.assert(!z.validate(undefined)) // @ts-expect-error t.assert(!z.validate(new Date())) }) t.group('string', () => { class BetterString extends String { } // @ts-expect-error t.assert(!s.$string.validate(new BetterString())) t.assert(s.$string.validate('hi')) // @ts-expect-error t.assert(!s.$string.validate(undefined)) // @ts-expect-error t.assert(!s.$string.validate(new Date())) }) t.group('array', () => { const myarray = s.$array(s.$number, s.$string) t.assert(myarray.validate([4, '5'])) t.assert(myarray.validate(['4', 5])) // @ts-expect-error t.assert(!myarray.validate(['x', new Date()])) // @ts-expect-error t.assert(!myarray.validate(undefined)) // @ts-expect-error t.assert(!myarray.validate(new Date())) { const mysimplearray = s.$array(s.$object({})) // @ts-expect-error if (env.production) t.fails(() => t.assert(mysimplearray.expect({ x: 4 }))) mysimplearray.cast([{}]) } }) t.group('union', () => { const myunion = s.$union(s.$number, s.$string) t.assert(myunion.validate(42)) t.assert(myunion.validate('str')) // @ts-expect-error t.assert(!myunion.validate(['str'])) // @ts-expect-error t.assert(!myunion.validate(undefined)) // @ts-expect-error t.assert(!myunion.validate(new Date())) // @ts-expect-error t.assert(!s.$union().validate(42)) t.assert(s.$union(s.$number).validate(42)) // @ts-expect-error t.assert(!s.$union(s.$number).validate('forty')) t.assert(/** @type {s.$Union} */ (s.$union(s.$union(s.$number), s.$string)).shape.length === 2) }) t.group('intersection', () => { const myintersectionNever = s.$intersect(s.$number, s.$string) // @ts-expect-error t.assert(!myintersectionNever.validate(42)) // @ts-expect-error t.assert(!myintersectionNever.validate('str')) const myintersection = s.$intersect(s.$object({ a: s.$number }), s.$object({ b: s.$number })) t.assert(myintersection.validate({ a: 42, b: 42 })) // @ts-expect-error t.assert(!myintersection.validate({ a: 42 })) // @ts-expect-error t.assert(!myintersection.validate({ b: 42 })) // @ts-expect-error t.assert(!myintersection.validate({ c: 42 })) t.assert(myintersection.check({ a: 42, b: 42, c: 42 })) // @ts-expect-error t.assert(!myintersectionNever.validate(['str'])) // @ts-expect-error t.assert(!myintersectionNever.validate(undefined)) // @ts-expect-error t.assert(!myintersectionNever.validate(new Date())) }) t.group('assert', () => { const x = /** @type {unknown} */ (42) // @ts-expect-error s.$number.validate(x) s.assert(x, s.$number) s.$number.validate(x) }) t.group('fails on assert/cast/ensure', () => { if (env.production) return t.info('[running in env.production] skipping fail tests because they are skipped in production') t.fails(() => { s.$number.cast('42') }) t.fails(() => { // @ts-expect-error s.$number.expect('42') }) t.fails(() => { s.assert('42', s.$number) }) }) t.group('Schema', () => { const nullVal = /** @type {unknown} */ (null) const numVal = /** @type {unknown} */ (42) const schema = s.$number.nullable // @ts-expect-error schema.validate(nullVal) // @ts-expect-error schema.validate(nullVal) // check twice to confirm that validate does not asserts // @ts-expect-error schema.validate(numVal) s.assert(nullVal, schema) s.assert(numVal, schema) schema.validate(nullVal) schema.validate(numVal) s.assert(undefined, schema.optional) }) t.group('schema.cast / schema.ensure', () => { const unknown = /** @type {unknown} */ (42) const known = s.$number.cast(unknown) s.$number.validate(known) // @ts-expect-error s.$number.validate(unknown) const f = s.$lambda(s.$number, s.$void).expect((_x) => {}) // should match a function with more parameters t.assert(s.$lambda(s.$number, s.$string, s.$void).validate(f)) // should still not match a different function // @ts-expect-error s.$lambda(s.$string, s.$void).validate(f) const x = s.$object({ f: s.$lambda(s.$string, s.$void), n: s.$number }).expect({ f: () => {}, n: 99 }) t.assert(x.n === 99) s.$lambda().cast(() => {}) }) t.group('lambda', () => { const $fun = s.$lambda(s.$number, s.$string, s.$string) t.assert($fun.validate(() => '')) t.assert($fun.validate(/** @param {number} _n */ (_n) => '')) // @ts-expect-error $fun.validate(/** @param {number} n */ (n) => n) // expected string result const $fun2 = s.$lambda(s.$number, s.$string, s.$void) t.assert($fun2.validate(() => '')) t.assert($fun2.validate(/** @param {number} n */ (n) => n + '')) t.assert($fun2.validate(/** @param {number} n */ (n) => n)) // this works now, because void is the absense of value const $fun3 = s.$lambda(s.$number, s.$undefined) // @ts-expect-error $fun3.validate(/** @param {number} n */ (n) => n) // this doesn't work, because expected the literal undefined. // @ts-expect-error t.assert(!$fun3.validate(/** @type {(a: number, b: number) => undefined} */ (_a, _b) => undefined)) // too many parameters }) t.group('never', () => { const x = 42 x.toString() if (s.$never.check(x)) { // @ts-expect-error method doesn't exist on never x.toString() } }) t.group('custom', () => { /** * @type {s.Schema} */ const $c = s.$custom(a => typeof a === 'number') t.assert($c.check(42)) t.assert(!$c.check('42')) }) } export const testSchemaExpect = () => { // Array is an edge case const q = s.$array(s.$string) const m = q.cast([]) // should be of type string[] // @ts-expect-error s.$array(s.$never).expect(m) } /** * @param {t.TestCase} _tc */ export const testObjectSchemaOptionals = _tc => { const schema = s.$object({ a: s.$number.optional, b: s.$string.optional }) t.assert(schema.validate({ })) // should work // @ts-expect-error t.assert(!schema.validate({ a: 'str' })) // should throw a type error const def = s.$union(s.$string, s.$array(s.$number)) const defOptional = def.optional const defObject = s.$object({ j: defOptional, k: def }) // @ts-expect-error t.assert(!defObject.validate({ k: undefined })) t.assert(defObject.validate({ k: [42] })) // @ts-expect-error t.assert(!defObject.validate({ k: [42], j: 42 })) t.assert(defObject.validate({ k: [42], j: 'str' })) t.assert(defObject.validate({ k: [42], j: undefined })) } /** * @param {t.TestCase} _tc */ export const testMetaSchemas = _tc => { /** * @type {s.Schema} */ const sch = s.$object({ n: s.$array(s.$number) }) t.fails(() => { s.assert(sch, s.$$number) sch.validate(42) // should work, if this wouldn't throw.. }) s.assert(sch, s.$$object) const schN = sch.shape.n s.assert(schN, s.$$array) t.assert(schN.shape) s.$$number.cast(schN.shape) t.group('meta check - shape is working', () => { const x = s.$object({ x: s.$number }) // @ts-expect-error shape shouldn't exist on $Shape t.assert(x.shape !== undefined) if (s.$$object.check(x)) { // expect that x is an object that maps to shemas t.assert(s.$object({}).validate(x.shape)) t.assert(s.$$schema.check(x.shape.x)) } }) } /** * @param {t.TestCase} _tc */ export const testStringTemplate = _tc => { // a test with number const $t = s.$stringTemplate('hi', s.$number) t.assert($t.validate('hi42')) // complex test with rgp (what you would use in css) // rgb(number,number,number) const $rgb = s.$stringTemplate('rgb(', s.$number, ',', s.$number, ',', s.$number, ')') t.assert($rgb.validate('rgb(42,42,3)')) // @ts-expect-error t.assert(!$rgb.validate('rgb(42,42,)')) // test with unions, showing that the resulting type is nicely resolved const $hi = s.$union(s.$literal('hello'), s.$literal('hi')) const $greeting = s.$stringTemplate($hi, ' ', s.$string, '!') t.assert($greeting.validate('hello world!')) t.assert($greeting.validate('hi there!')) // @ts-expect-error "moin" is not accepted" t.assert(!$greeting.validate('moin otto!')) } /** * @param {t.TestCase} _tc */ export const testSchemaExtends = _tc => { const t1 = s.$union(s.$number, s.$string) const t2 = s.$union(s.$number, s.$string, s.$null) t.assert(t2.extends(t1)) t.assert(!t1.extends(t2)) t.assert(s.$object({ a: s.$number, b: s.$number }).extends(s.$object({ a: s.$number }))) t.assert(!s.$object({ a: s.$number }).extends(s.$object({ a: s.$number, b: s.$number }))) t.assert(!s.$constructedBy(Number).extends(s.$constructedBy(Object))) } /** * @param {t.TestCase} _tc */ export const testSchemaErrors = _tc => { const x = s.$union(s.$object({ a: s.$number, b: s.$string })) try { s.assert({ a: 42, b: 43 }, x) } catch (err) { console.log(err + '') } } /** * expect * @param {t.TestCase} _tc */ export const testUnionMerging = _tc => { // should be merged into a single $union construct const $numOrStr = s.$union(s.$union(s.$union(s.$number)), s.$string) if (s.$$union.check($numOrStr)) { t.assert($numOrStr.shape.length === 2) t.assert($numOrStr.shape[0].extends(s.$number)) t.assert($numOrStr.shape[1].extends(s.$string)) t.assert($numOrStr.extends(s.$union(s.$number, s.$string))) t.assert($numOrStr.extends(s.$union(s.$number, s.$union(s.$string, s.$number)))) } else { t.fail('should be a union') } } export const testConvenienceHelper = () => { class Base { x () {} } /** * @type {s.Schema<{ x: 42|string|null, y: true, z: Base, a: { b: number|string } }>} */ const $o = s.$({ x: [/** @type {42} */(42), s.$string, null], y: /** @type {true} */ (true), z: Base, a: { b: [s.$union(s.$number, s.$string)] } }) /** * @type {s.Schema<{ x: 42|string|null, y: true, z: Base, a: { b: number|string } }>} */ const $oCpy = s.$object({ x: s.$union(s.$literal(42), s.$string).nullable, y: s.$literal(true), z: s.$constructedBy(Base), a: s.$object({ b: s.$union(s.$number, s.$string) }) }) t.assert($o.extends($oCpy)) /** * @type {s.Schema<{ x?: number }>} */ const $o2 = s.$({ x: s.$number.optional }) /** * @type {s.Schema<{ x?: number }>} */ const $o2Cpy = s.$object({ x: s.$number.optional }) t.assert($o2.extends($o2Cpy)) t.assert($o2.check({})) } export const testPatternMatcherBase = () => { const numberConverterP = s.match().if(s.$number, o => '' + o).if(s.$string, o => Number.parseInt(o)) const numberConverter = numberConverterP.done() const n = numberConverter('str') s.$number.expect(n) t.fails(() => { // @ts-expect-error s.$string.expect(n) }) const str = numberConverter(42) s.$string.expect(str) t.fails(() => { // @ts-expect-error s.$number.expect(str) }) t.fails(() => { // @ts-expect-error numberConverter({}) }) } export const testPatternMatcherWithState = () => { const numberConverterP = s.match({ cnt: s.$number }) .if(s.$number, (o, s) => { s.cnt++; return '' + o }) .if(s.$string, (o, s) => { s.cnt++; return Number.parseInt(o) }) const numberConverter = numberConverterP.done() const state = { cnt: 0 } const n = numberConverter('str', state) s.$number.expect(n) t.fails(() => { // @ts-expect-error s.$string.expect(n) }) const str = numberConverter(42, state) s.$string.expect(str) t.fails(() => { // @ts-expect-error s.$number.expect(str) }) t.assert(state.cnt === 2) t.fails(() => { // @ts-expect-error numberConverter({}, state) }) } export const testPatternMatcherBenchmark = () => { const gen = prng.create(42) const N = 10000 /** * @type {Array} */ const data = [] for (let i = 0; i < N; i++) { data.push(prng.oneOf(gen, [ () => prng.int53(gen, 0, 1000), () => prng.word(gen), () => prng.bool(gen), () => prng.oneOf(gen, [{ x: false }, { y: true }]) ])()) } t.measureTime('switch-case - count occurences (constructor checks)', () => { let numbers = 0 let strings = 0 let objects = 0 let bools = 0 for (let i = 0; i < data.length; i++) { const d = data[i] if (d.constructor === Number) { numbers++ } else if (d.constructor === String) { strings++ } else if (d.constructor === Boolean) { bools++ } else if (d instanceof Object) { objects++ } else { throw new Error('unhandled case') } } console.log({ numbers, strings, objects, bools }) }) // this is the fastest in Chrome as of december 2025 t.measureTime('switch-case - count occurences (typeof checks)', () => { let numbers = 0 let strings = 0 let objects = 0 let bools = 0 for (let i = 0; i < data.length; i++) { const d = data[i] if (typeof d === 'number') { numbers++ } else if (typeof d === 'string') { strings++ } else if (typeof d === 'boolean') { bools++ } else if (typeof d === 'object') { objects++ } else { throw new Error('unhandled case') } } console.log({ numbers, strings, objects, bools }) }) t.measureTime('switch-case - count occurences (typeof checks - optimized)', () => { let numbers = 0 let strings = 0 let objects = 0 let bools = 0 for (let i = 0; i < data.length; i++) { const d = data[i] switch (typeof d) { case 'number': numbers++ break case 'string': strings++ break case 'boolean': bools++ break case 'object': objects++ break default: throw new Error('unhandled case') } } console.log({ numbers, strings, objects, bools }) }) t.measureTime('pattern-matcher - count occurences (reduce - bad)', () => { const state = { numbers: 0, strings: 0, objects: 0, bools: 0 } const countTypes = s.match({ numbers: s.$number, strings: s.$number, objects: s.$number, bools: s.$number }) .if(s.$number, (_o, state) => { state.numbers++ }) .if(s.$string, (_o, state) => { state.strings++ }) .if(s.$boolean, (_o, state) => { state.bools++ }) .if(s.$objectAny, (_o, state) => { state.objects++ }) .done() console.log(data.reduce((s, d) => { countTypes(d, s) return s }, state)) }) t.measureTime('pattern-matcher - count occurences (for loop)', () => { const state = { numbers: 0, strings: 0, objects: 0, bools: 0 } const countTypes = s.match({ numbers: s.$number, strings: s.$number, objects: s.$number, bools: s.$number }) .if(s.$number, (_o, state) => { state.numbers++ }) .if(s.$string, (_o, state) => { state.strings++ }) .if(s.$boolean, (_o, state) => { state.bools++ }) .if(s.$objectAny, (_o, state) => { state.objects++ }) .done() for (let i = 0; i < data.length; i++) { countTypes(data[i], state) } console.log(state) }) t.measureTime('pattern-matcher - count occurences (forEach)', () => { const state = { numbers: 0, strings: 0, objects: 0, bools: 0 } const countTypes = s.match({ numbers: s.$number, strings: s.$number, objects: s.$number, bools: s.$number }) .if(s.$number, (_o, state) => { state.numbers++ }) .if(s.$string, (_o, state) => { state.strings++ }) .if(s.$boolean, (_o, state) => { state.bools++ }) .if(s.$objectAny, (_o, state) => { state.objects++ }) .done() data.forEach(d => countTypes(d, state)) console.log(state) }) } /** * @param {t.TestCase} tc */ export const testRepeatRandomFromSchema = tc => { /** * @param {string} caseName * @param {s.Schema} $s */ const testCase = (caseName, $s) => { t.group(caseName, () => { for (let i = 0; i < 10; i++) { const res = s.random(tc.prng, $s) $s.expect(res) console.info(caseName, res) } }) } testCase('object', s.$object({ number: s.$number, maybeStr: s.$string.optional })) testCase('any', s.$any) testCase('number', s.$number) testCase('array', s.$array(s.$any)) t.group('custom', () => { const $res = s.$object({ a: s.$number.optional, str: s.$string }) for (let i = 0; i < 30; i++) { const res = s.random(tc.prng, $res) $res.expect(res) console.log(res) } }) } dmonad-lib0-c7e7806/set.js000066400000000000000000000007231512504553500153050ustar00rootroot00000000000000/** * Utility module to work with sets. * * @module set */ export const create = () => new Set() /** * @template T * @param {Set} set * @return {Array} */ export const toArray = set => Array.from(set) /** * @template T * @param {Set} set * @return {T|undefined} */ export const first = set => set.values().next().value /** * @template T * @param {Iterable} entries * @return {Set} */ export const from = entries => new Set(entries) dmonad-lib0-c7e7806/set.test.js000066400000000000000000000005741512504553500162670ustar00rootroot00000000000000import * as t from './testing.js' import * as set from './set.js' /** * @param {t.TestCase} _tc */ export const testFirst = _tc => { const two = set.from(['a', 'b']) const one = set.from(['b']) const zero = set.create() t.assert(set.first(two) === 'a') t.assert(set.first(one) === 'b') t.assert(set.first(zero) === undefined) t.compare(set.toArray(one), ['b']) } dmonad-lib0-c7e7806/sort.js000066400000000000000000000041751512504553500155060ustar00rootroot00000000000000/** * Efficient sort implementations. * * 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. * * @module sort */ import * as math from './math.js' /** * @template T * @param {Array} arr * @param {number} lo * @param {number} hi * @param {function(T,T):number} compare */ export const _insertionSort = (arr, lo, hi, compare) => { for (let i = lo + 1; i <= hi; i++) { for (let j = i; j > 0 && compare(arr[j - 1], arr[j]) > 0; j--) { const tmp = arr[j] arr[j] = arr[j - 1] arr[j - 1] = tmp } } } /** * @template T * @param {Array} arr * @param {function(T,T):number} compare * @return {void} */ export const insertionSort = (arr, compare) => { _insertionSort(arr, 0, arr.length - 1, compare) } /** * @template T * @param {Array} arr * @param {number} lo * @param {number} hi * @param {function(T,T):number} compare */ const _quickSort = (arr, lo, hi, compare) => { if (hi - lo < 42) { _insertionSort(arr, lo, hi, compare) } else { const pivot = arr[math.floor((lo + hi) / 2)] let i = lo let j = hi while (true) { while (compare(pivot, arr[i]) > 0) { i++ } while (compare(arr[j], pivot) > 0) { j-- } if (i >= j) { break } // swap arr[i] with arr[j] // and increment i and j const arri = arr[i] arr[i++] = arr[j] arr[j--] = arri } _quickSort(arr, lo, j, compare) _quickSort(arr, j + 1, hi, compare) } } /** * This 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. * * @template T * @param {Array} arr * @param {function(T,T):number} compare * @return {void} */ export const quicksort = (arr, compare) => { _quickSort(arr, 0, arr.length - 1, compare) } dmonad-lib0-c7e7806/sort.test.js000066400000000000000000000113331512504553500164560ustar00rootroot00000000000000import * as prng from './prng.js' import * as t from './testing.js' import * as sort from './sort.js' /** * @template T * @param {t.TestCase} tc * @param {Array} arr * @param {function(T,T):number} compare * @param {function(T):number} getVal */ const runSortTest = (tc, arr, compare, getVal) => { const arrSort = arr const arrQuicksort = arr.slice() const arrInsertionsort = arr.slice() t.measureTime('Array.constructor.sort', () => { arrSort.sort(compare) }) if (arrInsertionsort.length <= 10000) { t.measureTime('Insertionsort', () => { sort.insertionSort(arrInsertionsort, compare) }) t.compareArrays(arrSort, arrInsertionsort, 'compare Insertionsort with expected result') } t.measureTime('Quicksort', () => { sort.quicksort(arrQuicksort, compare) }) // quickSort is not stable t.compareArrays(arrSort.map(getVal), arrQuicksort.map(getVal), 'compare Quicksort with expected result') } /** * @template T * @param {t.TestCase} tc * @param {function(number):Array} createArray * @param {function(T,T):number} compare 0 if equal, 1 if a { t.describe('sort 10 elements') runSortTest(tc, createArray(10), compare, getVal) t.describe('sort 10 elements') runSortTest(tc, createArray(10), compare, getVal) t.describe('sort 10 elements') runSortTest(tc, createArray(10), compare, getVal) t.describe('sort 50 elements') runSortTest(tc, createArray(50), compare, getVal) t.describe('sort 100 elements') runSortTest(tc, createArray(100), compare, getVal) t.describe('sort 500 elements') runSortTest(tc, createArray(500), compare, getVal) t.describe('sort 1k elements') runSortTest(tc, createArray(1000), compare, getVal) t.describe('sort 10k elements') runSortTest(tc, createArray(10000), compare, getVal) t.describe('sort 100k elements') runSortTest(tc, createArray(100000), compare, getVal) if (t.extensive) { t.describe('sort 1M elements') runSortTest(tc, createArray(1000000), compare, getVal) t.describe('sort 10M elements') runSortTest(tc, createArray(10000000), compare, getVal) } } /** * @param {t.TestCase} tc */ export const testSortUint8 = tc => { /** * @param {number} i * @return {number} */ const getVal = i => i /** * @param {number} a * @param {number} b * @return {number} */ const compare = (a, b) => a - b /** * @param {number} len * @return {Array} */ const createArray = len => Array.from(prng.uint8Array(tc.prng, len * 2)) createSortTest(tc, createArray, compare, getVal) } /** * @param {t.TestCase} tc */ export const testSortUint32 = tc => { /** * @param {number} i * @return {number} */ const getVal = i => i /** * @param {number} a * @param {number} b * @return {number} */ const compare = (a, b) => a - b /** * @param {number} len * @return {Array} */ const createArray = len => Array.from(prng.uint32Array(tc.prng, len)) createSortTest(tc, createArray, compare, getVal) } /** * @param {t.TestCase} tc */ export const testSortUint16 = tc => { /** * @param {number} i * @return {number} */ const getVal = i => i /** * @param {number} a * @param {number} b * @return {number} */ const compare = (a, b) => a - b /** * @param {number} len * @return {Array} */ const createArray = len => Array.from(prng.uint16Array(tc.prng, len)) createSortTest(tc, createArray, compare, getVal) } /** * @param {t.TestCase} tc */ export const testSortObjectUint32 = tc => { /** * @param {{index:number}} obj * @return {number} */ const getVal = obj => obj.index /** * @param {{index:number}} a * @param {{index:number}} b * @return {number} */ const compare = (a, b) => a.index - b.index /** * @param {number} len * @return {Array<{index:number}>} */ const createArray = len => Array.from(prng.uint32Array(tc.prng, len)).map(index => ({ index })) createSortTest(tc, createArray, compare, getVal) } /** * @param {t.TestCase} tc */ export const testListVsArrayPerformance = tc => { /** * @typedef {{ val: number }} Val * @typedef {{ val: Val, next: item }|null} item */ const len = 100000 t.measureTime('array creation', () => { /** * @type {Array} */ const array = new Array(len) for (let i = 0; i < len; i++) { array[i] = { val: i } } }) t.measureTime('list creation', () => { /** * @type {item} */ const listStart = { val: { val: 0 }, next: null } for (let i = 1, n = listStart; i < len; i++) { const next = { val: { val: i }, next: null } n.next = next n = next } }) } dmonad-lib0-c7e7806/statistics.js000066400000000000000000000010341512504553500167000ustar00rootroot00000000000000/** * Utility helpers for generating statistics. * * @module statistics */ import * as math from './math.js' /** * @param {Array} arr Array of values * @return {number} Returns null if the array is empty */ export const median = arr => arr.length === 0 ? NaN : (arr.length % 2 === 1 ? arr[(arr.length - 1) / 2] : (arr[math.floor((arr.length - 1) / 2)] + arr[math.ceil((arr.length - 1) / 2)]) / 2) /** * @param {Array} arr * @return {number} */ export const average = arr => arr.reduce(math.add, 0) / arr.length dmonad-lib0-c7e7806/statistics.test.js000066400000000000000000000012151512504553500176570ustar00rootroot00000000000000import * as statistics from './statistics.js' import * as t from './testing.js' import * as math from './math.js' /** * @param {t.TestCase} tc */ export const testMedian = tc => { t.assert(math.isNaN(statistics.median([])), 'median([]) = NaN') t.assert(statistics.median([1]) === 1, 'median([x]) = x') t.assert(statistics.median([1, 2, 3]) === 2, 'median([a,b,c]) = b') t.assert(statistics.median([1, 2, 3, 4]) === (2 + 3) / 2, 'median([a,b,c,d]) = (b+c)/2') t.assert(statistics.median([1, 2, 3, 4, 5]) === 3, 'median([a,b,c,d,e]) = c') t.assert(statistics.median([1, 2, 3, 4, 5, 6]) === (3 + 4) / 2, 'median([a,b,c,d,e,f]) = (c+d)/2') } dmonad-lib0-c7e7806/storage.js000066400000000000000000000033331512504553500161560ustar00rootroot00000000000000/* 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 = localStorage usePolyfill = false } } catch (e) { } /* c8 ignore stop */ /** * This is basically localStorage in browser, or a polyfill in nodejs */ /* c8 ignore next */ export const varStorage = _localStorage /** * A polyfill for `addEventListener('storage', event => {..})` that does nothing if the polyfill is being used. * * @param {function({ key: string, newValue: string, oldValue: string }): void} eventHandler * @function */ /* c8 ignore next */ export const onChange = eventHandler => usePolyfill || addEventListener('storage', /** @type {any} */ (eventHandler)) /** * A polyfill for `removeEventListener('storage', event => {..})` that does nothing if the polyfill is being used. * * @param {function({ key: string, newValue: string, oldValue: string }): void} eventHandler * @function */ /* c8 ignore next */ export const offChange = eventHandler => usePolyfill || removeEventListener('storage', /** @type {any} */ (eventHandler)) dmonad-lib0-c7e7806/storage.test.js000066400000000000000000000005371512504553500171370ustar00rootroot00000000000000import * as storage from './storage.js' import * as t from './testing.js' /** * @param {t.TestCase} tc */ export const testStorageModule = tc => { const s = storage.varStorage /** * @type {any} */ let lastEvent = null storage.onChange(event => { lastEvent = event }) s.setItem('key', 'value') t.assert(lastEvent === null) } dmonad-lib0-c7e7806/string.js000066400000000000000000000105411512504553500160170ustar00rootroot00000000000000import * as array from './array.js' /** * Utility module to work with strings. * * @module string */ export const fromCharCode = String.fromCharCode export const fromCodePoint = String.fromCodePoint /** * The largest utf16 character. * Corresponds to Uint8Array([255, 255]) or charcodeof(2x2^8) */ export const MAX_UTF16_CHARACTER = fromCharCode(65535) /** * @param {string} s * @return {string} */ const toLowerCase = s => s.toLowerCase() const trimLeftRegex = /^\s*/g /** * @param {string} s * @return {string} */ export const trimLeft = s => s.replace(trimLeftRegex, '') const fromCamelCaseRegex = /([A-Z])/g /** * @param {string} s * @param {string} separator * @return {string} */ export const fromCamelCase = (s, separator) => trimLeft(s.replace(fromCamelCaseRegex, match => `${separator}${toLowerCase(match)}`)) /** * Compute the utf8ByteLength * @param {string} str * @return {number} */ export const utf8ByteLength = str => unescape(encodeURIComponent(str)).length /** * @param {string} str * @return {Uint8Array} */ export 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 */ export const utf8TextEncoder = /** @type {TextEncoder} */ (typeof TextEncoder !== 'undefined' ? new TextEncoder() : null) /** * @param {string} str * @return {Uint8Array} */ export const _encodeUtf8Native = str => utf8TextEncoder.encode(str) /** * @param {string} str * @return {Uint8Array} */ /* c8 ignore next */ export const encodeUtf8 = utf8TextEncoder ? _encodeUtf8Native : _encodeUtf8Polyfill /** * @param {Uint8Array} buf * @return {string} */ export const _decodeUtf8Polyfill = buf => { let remainingLen = buf.length let encodedString = '' let bufPos = 0 while (remainingLen > 0) { const nextLen = remainingLen < 10000 ? remainingLen : 10000 const bytes = buf.subarray(bufPos, bufPos + nextLen) bufPos += nextLen // Starting with ES5.1 we can supply a generic array-like object as arguments encodedString += String.fromCodePoint.apply(null, /** @type {any} */ (bytes)) remainingLen -= nextLen } return decodeURIComponent(escape(encodedString)) } /* c8 ignore next */ export 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 } /* c8 ignore stop */ /** * @param {Uint8Array} buf * @return {string} */ export const _decodeUtf8Native = buf => /** @type {TextDecoder} */ (utf8TextDecoder).decode(buf) /** * @param {Uint8Array} buf * @return {string} */ /* c8 ignore next */ export const decodeUtf8 = utf8TextDecoder ? _decodeUtf8Native : _decodeUtf8Polyfill /** * @param {string} str The initial string * @param {number} index Starting position * @param {number} remove Number of characters to remove * @param {string} insert New content to insert */ export const splice = (str, index, remove, insert = '') => str.slice(0, index) + insert + str.slice(index + remove) /** * @param {string} source * @param {number} n */ export const repeat = (source, n) => array.unfold(n, () => source).join('') /** * Escape HTML characters &,<,>,'," to their respective HTML entities &,<,>,'," * * @param {string} str */ export const escapeHTML = str => str.replace(/[&<>'"]/g, r => /** @type {string} */ ({ '&': '&', '<': '<', '>': '>', "'": ''', '"': '"' }[r])) /** * Reverse of `escapeHTML` * * @param {string} str */ export const unescapeHTML = str => str.replace(/&|<|>|'|"/g, r => /** @type {string} */ ({ '&': '&', '<': '<', '>': '>', ''': "'", '"': '"' }[r])) dmonad-lib0-c7e7806/string.test.js000066400000000000000000000052761512504553500170060ustar00rootroot00000000000000import * as prng from './prng.js' import * as string from './string.js' import * as t from './testing.js' /** * @param {t.TestCase} _tc */ export const testUtilities = _tc => { t.assert(string.repeat('1', 3) === '111') t.assert(string.repeat('1', 0) === '') t.assert(string.repeat('1', 1) === '1') } /** * @param {t.TestCase} _tc */ export const testLowercaseTransformation = _tc => { t.compareStrings(string.fromCamelCase('ThisIsATest', ' '), 'this is a test') t.compareStrings(string.fromCamelCase('Testing', ' '), 'testing') t.compareStrings(string.fromCamelCase('testingThis', ' '), 'testing this') t.compareStrings(string.fromCamelCase('testYAY', ' '), 'test y a y') } /** * @param {t.TestCase} tc */ export const testRepeatStringUtf8Encoding = tc => { t.skip(!string.utf8TextDecoder) const str = prng.utf16String(tc.prng, 1000000) let nativeResult, polyfilledResult t.measureTime('TextEncoder utf8 encoding', () => { nativeResult = string._encodeUtf8Native(str) }) t.measureTime('Polyfilled utf8 encoding', () => { polyfilledResult = string._encodeUtf8Polyfill(str) }) t.compare(nativeResult, polyfilledResult, 'Encoded utf8 buffers match') } /** * @param {t.TestCase} tc */ export const testRepeatStringUtf8Decoding = tc => { t.skip(!string.utf8TextDecoder) const buf = string.encodeUtf8(prng.utf16String(tc.prng, 1000000)) let nativeResult, polyfilledResult t.measureTime('TextEncoder utf8 decoding', () => { nativeResult = string._decodeUtf8Native(buf) }) t.measureTime('Polyfilled utf8 decoding', () => { polyfilledResult = string._decodeUtf8Polyfill(buf) }) t.compare(nativeResult, polyfilledResult, 'Decoded utf8 buffers match') } /** * @param {t.TestCase} _tc */ export const testBomEncodingDecoding = _tc => { const bomStr = 'bom' t.assert(bomStr.length === 4) const polyfilledResult = string._decodeUtf8Polyfill(string._encodeUtf8Polyfill(bomStr)) t.assert(polyfilledResult.length === 4) t.assert(polyfilledResult === bomStr) if (string.utf8TextDecoder) { const nativeResult = string._decodeUtf8Native(string._encodeUtf8Native(bomStr)) t.assert(nativeResult === polyfilledResult) } } /** * @param {t.TestCase} _tc */ export const testSplice = _tc => { const initial = 'xyz' t.compareStrings(string.splice(initial, 0, 2), 'z') t.compareStrings(string.splice(initial, 0, 2, 'u'), 'uz') } /** * @param {t.TestCase} _tc */ export const testHtmlEscape = _tc => { const cases = [ { s: 'hello dmonad-lib0-c7e7806/test.js000066400000000000000000000052771512504553500155020ustar00rootroot00000000000000import { runTests } from './testing.js' import * as array from './array.test.js' import * as broadcastchannel from './broadcastchannel.test.js' import * as crypto from './crypto.test.js' import * as rabin from './hash/rabin.test.js' import * as sha256 from './hash/sha256.test.js' import * as logging from './logging.test.js' import * as string from './string.test.js' import * as encoding from './encoding.test.js' import * as diff from './diff.test.js' import * as patienceDiff from './diff/patience.test.js' import * as testing from './testing.test.js' import * as indexeddb from './indexeddb.test.js' import * as indexeddbV2 from './indexeddbV2.test.js' import * as prng from './prng.test.js' import * as log from 'lib0/logging' import * as statistics from './statistics.test.js' import * as binary from './binary.test.js' import * as random from './random.test.js' import * as promise from './promise.test.js' import * as queue from './queue.test.js' import * as map from './map.test.js' import * as eventloop from './eventloop.test.js' import * as time from './time.test.js' import * as pair from './pair.test.js' import * as object from './object.test.js' import * as observable from './observable.test.js' import * as pledge from './pledge.test.js' import * as math from './math.test.js' import * as number from './number.test.js' import * as buffer from './buffer.test.js' import * as set from './set.test.js' import * as sort from './sort.test.js' import * as url from './url.test.js' import * as metric from './metric.test.js' import * as func from './function.test.js' import * as storage from './storage.test.js' import * as list from './list.test.js' import * as cache from './cache.test.js' import * as symbol from './symbol.test.js' import * as traits from './trait/traits.test.js' import * as schema from './schema.test.js' import * as delta from './delta/delta.test.js' import * as deltaPitch from './delta/delta-pitch.test.js' // import * as deltaBinding from './delta/binding.test.js' import * as mutex from './mutex.test.js' import { isBrowser, isNode } from './environment.js' /* c8 ignore next */ if (isBrowser) { log.createVConsole(document.body) } runTests({ array, broadcastchannel, crypto, rabin, sha256, logging, string, encoding, diff, patienceDiff, testing, indexeddb, indexeddbV2, prng, statistics, binary, random, promise, queue, map, eventloop, time, pair, object, observable, pledge, math, number, buffer, set, sort, url, metric, func, storage, list, cache, symbol, traits, schema, delta, deltaPitch, // deltaBinding, mutex }).then(success => { /* c8 ignore next */ if (isNode) { process.exit(success ? 0 : 1) } }) dmonad-lib0-c7e7806/testing.js000066400000000000000000000650621512504553500161760ustar00rootroot00000000000000/** * Testing framework with support for generating tests. * * ```js * // 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) * } * }) * ``` * * ```js * // 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. * * @module testing */ import * as log from 'lib0/logging' import { simpleDiffString } from './diff.js' import * as object from './object.js' import * as string from './string.js' import * as math from './math.js' import * as random from './random.js' import * as prng from './prng.js' import * as statistics from './statistics.js' import * as array from './array.js' import * as env from './environment.js' import * as json from './json.js' import * as time from './time.js' import * as promise from './promise.js' import * as performance from 'lib0/performance' import * as equalityTrait from './trait/equality.js' export { production } from './environment.js' export const extensive = env.hasConf('extensive') /* c8 ignore next */ export const envSeed = env.hasParam('--seed') ? Number.parseInt(env.getParam('--seed', '0')) : null export class TestCase { /** * @param {string} moduleName * @param {string} testName */ constructor (moduleName, testName) { /** * @type {string} */ this.moduleName = moduleName /** * @type {string} */ this.testName = testName /** * This type can store custom information related to the TestCase * * @type {Map} */ this.meta = new Map() this._seed = null this._prng = null } resetSeed () { this._seed = null this._prng = null } /** * @type {number} */ /* c8 ignore next */ get seed () { /* c8 ignore else */ if (this._seed === null) { /* c8 ignore next */ this._seed = envSeed === null ? random.uint32() : envSeed } return this._seed } /** * A PRNG for this test case. Use only this PRNG for randomness to make the test case reproducible. * * @type {prng.PRNG} */ get prng () { /* c8 ignore else */ if (this._prng === null) { this._prng = prng.create(this.seed) } return this._prng } } export const repetitionTime = Number(env.getParam('--repetition-time', '50')) /* c8 ignore next */ const testFilter = env.hasParam('--filter') ? env.getParam('--filter', '') : null /* c8 ignore next */ const testFilterRegExp = testFilter !== null ? new RegExp(testFilter) : /.*/ const repeatTestRegex = /^(repeat|repeating)\s/ /** * @param {string} moduleName * @param {string} name * @param {function(TestCase):void|Promise} f * @param {number} i * @param {number} numberOfTests */ export const run = async (moduleName, name, f, i, numberOfTests) => { const uncamelized = string.fromCamelCase(name.slice(4), ' ') const filtered = !testFilterRegExp.test(`[${i + 1}/${numberOfTests}] ${moduleName}: ${uncamelized}`) /* c8 ignore next 3 */ if (filtered) { return true } const tc = new TestCase(moduleName, name) const repeat = repeatTestRegex.test(uncamelized) const groupArgs = [log.GREY, `[${i + 1}/${numberOfTests}] `, log.PURPLE, `${moduleName}: `, log.BLUE, uncamelized] /* c8 ignore next 5 */ if (testFilter === null) { log.groupCollapsed(...groupArgs) } else { log.group(...groupArgs) } const times = [] const start = performance.now() let lastTime = start /** * @type {any} */ let err = null performance.mark(`${name}-start`) do { try { const p = f(tc) if (promise.isPromise(p)) { await p } } catch (_err) { err = _err } const currTime = performance.now() times.push(currTime - lastTime) lastTime = currTime if (repeat && err === null && (lastTime - start) < repetitionTime) { tc.resetSeed() } else { break } } while (err === null && (lastTime - start) < repetitionTime) performance.mark(`${name}-end`) /* c8 ignore next 3 */ if (err !== null && err.constructor !== SkipError) { log.printError(err) } performance.measure(name, `${name}-start`, `${name}-end`) log.groupEnd() const duration = lastTime - start let success = true times.sort((a, b) => a - b) /* c8 ignore next 3 */ const againMessage = env.isBrowser ? ` - ${window.location.host + window.location.pathname}?filter=\\[${i + 1}/${tc._seed === null ? '' : `&seed=${tc._seed}`}` : `\nrepeat: npm run test -- --filter "\\[${i + 1}/" ${tc._seed === null ? '' : `--seed ${tc._seed}`}` const timeInfo = (repeat && err === null) ? ` - ${times.length} repetitions in ${time.humanizeDuration(duration)} (best: ${time.humanizeDuration(times[0])}, worst: ${time.humanizeDuration(array.last(times))}, median: ${time.humanizeDuration(statistics.median(times))}, average: ${time.humanizeDuration(statistics.average(times))})` : ` in ${time.humanizeDuration(duration)}` if (err !== null) { /* c8 ignore start */ if (err.constructor === SkipError) { log.print(log.GREY, log.BOLD, 'Skipped: ', log.UNBOLD, uncamelized) } else { success = false log.print(log.RED, log.BOLD, 'Failure: ', log.UNBOLD, log.UNCOLOR, uncamelized, log.GREY, timeInfo, againMessage) } /* c8 ignore stop */ } else { log.print(log.GREEN, log.BOLD, 'Success: ', log.UNBOLD, log.UNCOLOR, uncamelized, log.GREY, timeInfo, againMessage) } return success } /** * Describe what you are currently testing. The message will be logged. * * ```js * export const testMyFirstTest = tc => { * t.describe('crunching numbers', 'already crunched 4 numbers!') // the optional second argument can describe the state. * } * ``` * * @param {string} description * @param {string} info */ export const describe = (description, info = '') => log.print(log.BLUE, description, ' ', log.GREY, info) /** * Describe the state of the current computation. * ```js * export const testMyFirstTest = tc => { * t.info(already crunched 4 numbers!') // the optional second argument can describe the state. * } * ``` * * @param {string} info */ export const info = info => describe('', info) export const printDom = log.printDom export const printCanvas = log.printCanvas /** * Group outputs in a collapsible category. * * ```js * 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') * }) * } * ``` * * @param {string} description * @param {function(...any):void} f */ export const group = (description, f) => { log.group(log.BLUE, description) try { f() } finally { log.groupEnd() } } /** * Group outputs in a collapsible category. * * ```js * 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') * }) * } * ``` * * @param {string} description * @param {function(...any):Promise} f */ export const groupAsync = async (description, f) => { log.group(log.BLUE, description) try { await f() } finally { log.groupEnd() } } /** * Measure the time that it takes to calculate something. * * ```js * export const testMyFirstTest = async tc => { * t.measureTime('measurement', () => { * heavyCalculation() * }) * await t.groupAsync('async measurement', async () => { * await heavyAsyncCalculation() * }) * } * ``` * * @param {string} message * @param {function(...any):void} f * @return {number} Returns a promise that resolves the measured duration to apply f */ export const measureTime = (message, f) => { let duration const start = performance.now() try { f() } finally { duration = performance.now() - start log.print(log.PURPLE, message, log.GREY, ` ${time.humanizeDuration(duration)}`) } return duration } /** * Measure the time that it takes to calculate something. * * ```js * export const testMyFirstTest = async tc => { * t.measureTimeAsync('measurement', async () => { * await heavyCalculation() * }) * await t.groupAsync('async measurement', async () => { * await heavyAsyncCalculation() * }) * } * ``` * * @param {string} message * @param {function(...any):Promise} f * @return {Promise} Returns a promise that resolves the measured duration to apply f */ export const measureTimeAsync = async (message, f) => { let duration const start = performance.now() try { await f() } finally { duration = performance.now() - start log.print(log.PURPLE, message, log.GREY, ` ${time.humanizeDuration(duration)}`) } return duration } /** * @template T * @param {Array} as * @param {Array} bs * @param {string} [m] * @return {boolean} */ export const compareArrays = (as, bs, m = 'Arrays match') => { if (as.length !== bs.length) { fail(m) } for (let i = 0; i < as.length; i++) { if (as[i] !== bs[i]) { fail(m) } } return true } /** * @param {string} a * @param {string} b * @param {string} [m] * @throws {TestError} Throws if tests fails */ export const compareStrings = (a, b, m = 'Strings match') => { if (a !== b) { const diff = simpleDiffString(a, b) log.print(log.GREY, a.slice(0, diff.index), log.RED, a.slice(diff.index, diff.remove), log.GREEN, diff.insert, log.GREY, a.slice(diff.index + diff.remove)) fail(m) } } /** * @template K,V * @param {Object} a * @param {Object} b * @param {string} [m] * @throws {TestError} Throws if test fails */ export const compareObjects = (a, b, m = 'Objects match') => { object.equalFlat(a, b) || fail(m) } /** * @param {any} _constructor * @param {any} a * @param {any} b * @param {string} path * @throws {TestError} */ const compareValues = (_constructor, a, b, path) => { if (a !== b) { fail(`Values ${json.stringify(a)} and ${json.stringify(b)} don't match (${path})`) } return true } /** * @param {string?} message * @param {string} reason * @param {string} path * @throws {TestError} */ const _failMessage = (message, reason, path) => fail( message === null ? `${reason} ${path}` : `${message} (${reason}) ${path}` ) /** * @param {any} a * @param {any} b * @param {string} path * @param {string?} message * @param {function(any,any,any,string,any):boolean} customCompare */ const _compare = (a, b, path, message, customCompare) => { // we don't use assert here because we want to test all branches (istanbul errors if one branch is not tested) if (a == null || b == null) { return compareValues(null, a, b, path) } if (a[equalityTrait.EqualityTraitSymbol] != null) { if (a[equalityTrait.EqualityTraitSymbol](b)) { return true } else { _failMessage(message, 'Not equal by equality trait', path) } } if (a.constructor !== b.constructor) { _failMessage(message, 'Constructors don\'t match', path) } let success = 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) { _failMessage(message, 'ArrayBuffer lengths match', path) } for (let i = 0; success && i < a.length; i++) { success = success && a[i] === b[i] } break } case Set: { if (a.size !== b.size) { _failMessage(message, 'Sets have different number of attributes', path) } // @ts-ignore a.forEach(value => { if (!b.has(value)) { _failMessage(message, `b.${path} does have ${value}`, path) } }) break } case Map: { if (a.size !== b.size) { _failMessage(message, 'Maps have different number of attributes', path) } // @ts-ignore a.forEach((value, key) => { if (!b.has(key)) { _failMessage(message, `Property ${path}["${key}"] does not exist on second argument`, path) } _compare(value, b.get(key), `${path}["${key}"]`, message, customCompare) }) break } case undefined: // undefined is often set as a constructor for objects case Object: if (object.length(a) !== object.length(b)) { _failMessage(message, 'Objects have a different number of attributes', path) } object.forEach(a, (value, key) => { if (!object.hasProperty(b, key)) { _failMessage(message, `Property ${path} does not exist on second argument`, path) } _compare(value, b[key], `${path}["${key}"]`, message, customCompare) }) break case Array: if (a.length !== b.length) { _failMessage(message, 'Arrays have a different number of attributes', path) } // @ts-ignore a.forEach((value, i) => _compare(value, b[i], `${path}[${i}]`, message, customCompare)) break /* c8 ignore next 4 */ default: if (!customCompare(a.constructor, a, b, path, compareValues)) { _failMessage(message, `Values ${json.stringify(a)} and ${json.stringify(b)} don't match`, path) } } assert(success, message) return true } /** * @template T * @param {T} a * @param {T} b * @param {string?} [message] * @param {function(any,T,T,string,any):boolean} [customCompare] */ export const compare = (a, b, message = null, customCompare = compareValues) => _compare(a, b, 'obj', message, customCompare) /** * @template T * @param {T} property * @param {string?} [message] * @return {asserts property is NonNullable} * @throws {TestError} */ /* c8 ignore next */ export const assert = (property, message = null) => { property || fail(`Assertion failed${message !== null ? `: ${message}` : ''}`) } /** * @param {function(...any):Promise} f */ export const promiseRejected = async f => { try { await f() } catch (err) { return } fail('Expected promise to fail') } /** * @param {function(...any):void} f * @throws {TestError} */ export const fails = f => { try { f() } catch (_err) { log.print(log.GREEN, '⇖ This Error was expected') return } fail('Expected this to fail') } /** * @param {function(...any):Promise} f * @throws {TestError} */ export const failsAsync = async f => { try { await f() } catch (_err) { log.print(log.GREEN, '⇖ This Error was expected') return } fail('Expected this to fail') } /** * @param {Object>>} tests */ export const runTests = async tests => { /** * @param {string} testname */ const filterTest = testname => testname.startsWith('test') || testname.startsWith('benchmark') const numberOfTests = object.map(tests, mod => object.map(mod, (f, fname) => /* c8 ignore next */ f && filterTest(fname) ? 1 : 0).reduce(math.add, 0)).reduce(math.add, 0) let successfulTests = 0 let testnumber = 0 const start = performance.now() for (const modName in tests) { const mod = tests[modName] for (const fname in mod) { const f = mod[fname] /* c8 ignore else */ if (f && filterTest(fname)) { const repeatEachTest = 1 let success = true for (let i = 0; success && i < repeatEachTest; i++) { success = await run(modName, fname, f, testnumber, numberOfTests) } testnumber++ /* c8 ignore else */ if (success) { successfulTests++ } } } } const end = performance.now() log.print('') const success = successfulTests === numberOfTests /* c8 ignore start */ if (success) { log.print(log.GREEN, log.BOLD, 'All tests successful!', log.GREY, log.UNBOLD, ` in ${time.humanizeDuration(end - start)}`) log.printImgBase64(nyanCatImage, 50) } else { const failedTests = numberOfTests - successfulTests log.print(log.RED, log.BOLD, `> ${failedTests} test${failedTests > 1 ? 's' : ''} failed`) } /* c8 ignore stop */ return success } class TestError extends Error {} /** * @param {string} reason * @throws {TestError} */ export const fail = reason => { log.print(log.RED, log.BOLD, 'X ', log.UNBOLD, reason) throw new TestError('Test Failed') } class SkipError extends Error {} /** * @param {boolean} cond If true, this tests will be skipped * @throws {SkipError} */ export const skip = (cond = true) => { if (cond) { throw new SkipError('skipping..') } } // eslint-disable-next-line const nyanCatImage = 'R0lGODlhjABMAPcAAMiSE0xMTEzMzUKJzjQ0NFsoKPc7//FM/9mH/z9x0HIiIoKCgmBHN+frGSkZLdDQ0LCwsDk71g0KCUzDdrQQEOFz/8yYdelmBdTiHFxcXDU2erR/mLrTHCgoKK5szBQUFNgSCTk6ymfpCB9VZS2Bl+cGBt2N8kWm0uDcGXhZRUvGq94NCFPhDiwsLGVlZTgqIPMDA1g3aEzS5D6xAURERDtG9JmBjJsZGWs2AD1W6Hp6eswyDeJ4CFNTU1LcEoJRmTMzSd14CTg5ser2GmDzBd17/xkZGUzMvoSMDiEhIfKruCwNAJaWlvRzA8kNDXDrCfi0pe1U/+GS6SZrAB4eHpZwVhoabsx9oiYmJt/TGHFxcYyMjOid0+Zl/0rF6j09PeRr/0zU9DxO6j+z0lXtBtp8qJhMAEssLGhoaPL/GVn/AAsWJ/9/AE3Z/zs9/3cAAOlf/+aa2RIyADo85uhh/0i84WtrazQ0UyMlmDMzPwUFBe16BTMmHau0E03X+g8pMEAoS1MBAf++kkzO8pBaqSZoe9uB/zE0BUQ3Sv///4WFheuiyzo880gzNDIyNissBNqF/8RiAOF2qG5ubj0vL1z6Avl5ASsgGkgUSy8vL/8n/z4zJy8lOv96uEssV1csAN5ZCDQ0Wz1a3tbEGHLeDdYKCg4PATE7PiMVFSoqU83eHEi43gUPAOZ8reGogeKU5dBBC8faHEez2lHYF4bQFMukFtl4CzY3kkzBVJfMGZkAAMfSFf27mP0t//g4/9R6Dfsy/1DRIUnSAPRD/0fMAFQ0Q+l7rnbaD0vEntCDD6rSGtO8GNpUCU/MK07LPNEfC7RaABUWWkgtOst+71v9AfD7GfDw8P19ATtA/NJpAONgB9yL+fm6jzIxMdnNGJxht1/2A9x//9jHGOSX3+5tBP27l35+fk5OTvZ9AhYgTjo0PUhGSDs9+LZjCFf2Aw0IDwcVAA8PD5lwg9+Q7YaChC0kJP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/wtYTVAgRGF0YVhNUDw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpGNEM2MUEyMzE0QTRFMTExOUQzRkE3QTBCRDNBMjdBQyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpERjQ0NEY0QkI2MTcxMUUxOUJEQkUzNUNGQTkwRTU2MiIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpERjQ0NEY0QUI2MTcxMUUxOUJEQkUzNUNGQTkwRTU2MiIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1OEE3RTIwRjcyQTlFMTExOTQ1QkY2QTU5QzVCQjJBOSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpGNEM2MUEyMzE0QTRFMTExOUQzRkE3QTBCRDNBMjdBQyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAkKABEAIf4jUmVzaXplZCBvbiBodHRwczovL2V6Z2lmLmNvbS9yZXNpemUALAAAAACMAEwAAAj/ACMIHEiwoMGDCBMqXMiwocOHECNKnEixosWLGDNq3Mixo8ePIEOKHEmypMmTKFOqXLkxEcuXMAm6jElTZaKZNXOOvOnyps6fInECHdpRKNGjSJMqXZrSKNOnC51CnUq1qtWrWLNC9GmQq9avYMOKHUs2aFmmUs8SlcC2rdu3cNWeTEG3rt27eBnIHflBj6C/gAMLHpxCz16QElJw+7tom+PHkCOP+8utiuHDHRP/5WICgefPkIYV8RAjxudtkwVZjqCnNeaMmheZqADm8+coHn5kyPBt2udFvKrc+7A7gITXFzV77hLF9ucYGRaYo+FhWhHPUKokobFgQYbjyCsq/3fuHHr3BV88HMBeZd357+HFpxBEvnz0961b3+8OP37DtgON5xxznpl3ng5aJKiFDud5B55/Ct3TQwY93COQgLZV0AUC39ihRYMggjhJDw9CeNA9kyygxT2G6TGfcxUY8pkeH3YHgTkMNrgFBJOYs8Akl5l4Yoor3mPki6BpUsGMNS6QiA772WjNPR8CSRAjWBI0B5ZYikGQGFwyMseVYWoZppcDhSkmmVyaySWaAqk5pkBbljnQlnNYEZ05fGaAJGieVQAMjd2ZY+R+X2Rgh5FVBhmBG5BGKumklFZq6aWYZqrpppTOIQQNNPjoJ31RbGibIRXQuIExrSSY4wI66P9gToJlGHOFo374MQg2vGLjRa65etErNoMA68ew2Bi7a6+/Aitsr8UCi6yywzYb7LDR5jotsMvyau0qJJCwGw0vdrEkeTRe0UknC7hQYwYMQrmAMZ2U4WgY+Lahbxt+4Ovvvm34i68fAAscBsD9+kvwvgYDHLDACAu8sL4NFwzxvgkP3EYhhYzw52dFhOPZD5Ns0Iok6PUwyaIuTJLBBwuUIckG8RCkhhrUHKHzEUTcfLM7Ox/hjs9qBH0E0ZUE3bPPQO9cCdFGIx300EwH/bTPUfuc9M5U30zEzhN87NkwcDyXgY/oxaP22vFQIR2JBT3xBDhEUyO33FffXMndT1D/QzTfdPts9915qwEO3377DHjdfBd++N2J47y44Ij7PMN85UgBxzCeQQKJbd9wFyKI6jgqUBqoD6G66qinvvoQ1bSexutDyF4N7bLTHnvruLd+++u5v76766vb3jvxM0wxnyBQxHEued8Y8cX01Fc/fQcHZaG97A1or30DsqPgfRbDpzF+FtyPD37r4ns/fDXnp+/9+qif//74KMj/fRp9TEIDAxb4ixIWQcACFrAMFkigAhPIAAmwyHQDYYMEJ0jBClrwghjMoAY3yMEOYhAdQaCBFtBAAD244oQoTKEKV5iCbizEHjCkoCVgCENLULAJNLTHNSZ4jRzaQ4Y5tOEE+X24Qwn2MIdApKEQJUhEHvowiTBkhh7QVqT8GOmKWHwgFiWghR5AkCA+DKMYx0jGMprxjGhMYw5XMEXvGAZF5piEhQyih1CZ4wt6kIARfORFhjwDBoCEQQkIUoJAwmAFBDEkDAhSCkMOciCFDCQiB6JIgoDAkYQ0JAgSaUhLYnIgFLjH9AggkHsQYHo1oyMVptcCgUjvCx34opAWkp/L1BIhtxxILmfJy17KxJcrSQswhykWYRLzI8Y8pjKXycxfNvOZMEkmNC0izWlSpJrWlAg2s8kQnkRgJt7kpja92ZNwivOcNdkmOqOyzoyos50IeSc850nPegIzIAAh+QQJCgARACwAAAAAjABMAAAI/wAjCBxIsKDBgwgTKlzIsKHDhxAjSpxIsaLFixgzatzIsaPHjyBDihxJcmKikihTZkx0UqXLlw5ZwpxJ02DLmjhz6twJkqVMnz55Ch1KtGhCmUaTYkSqtKnJm05rMl0aVefUqlhtFryatavXr2DDHoRKkKzYs2jTqpW61exani3jun0rlCvdrhLy6t3Lt+9dlykCCx5MuDCDvyU/6BHEuLHjx5BT6EEsUkIKbowXbdvMubPncYy5VZlM+aNlxlxMIFjNGtKwIggqDGO9DbSg0aVNpxC0yEQFMKxZRwmHoEiU4AgW8cKdu+Pp1V2OI6c9bdq2cLARQGEeIV7zjM+nT//3oEfPNDiztTOXoMf7d4vhxbP+ts6cORrfIK3efq+8FnN2kPbeRPEFF918NCywgBZafLNfFffEM4k5C0wi4IARFchaBV0gqGCFDX6zQQqZZPChhRgSuBtyFRiC3DcJfqgFDTTSYOKJF6boUIGQaFLBizF+KOSQKA7EyJEEzXHkkWIQJMaSjMxBEJSMJAllk0ZCKWWWS1q5JJYCUbllBEpC6SWTEehxzz0rBqdfbL1AEsONQ9b5oQ73DOTGnnz26eefgAYq6KCEFmoooCHccosdk5yzYhQdBmfIj3N++AAEdCqoiDU62LGAOXkK5Icfg2BjKjZejDqqF6diM4iqfrT/ig2spZ6aqqqsnvqqqrLS2uqtq7a666i9qlqrqbeeQEIGN2awYhc/ilepghAssM6JaCwAQQ8ufBpqBGGE28a4bfgR7rnktnFuuH6ku24Y6Zp7brvkvpuuuuvGuy6949rrbr7kmltHIS6Yw6AWjgoyXRHErTYnPRtskMEXdLrQgzlffKHDBjZ8q4Ya1Bwh8hFEfPyxOyMf4Y7JaqR8BMuVpFyyySiPXAnLLsOc8so0p3yzyTmbHPPIK8sxyYJr9tdmcMPAwdqcG3TSyQZ2fniF1N8+8QQ4LFOjtdY/f1zJ109QwzLZXJvs9ddhqwEO2WabjHbXZLf99tdxgzy32k8Y/70gK+5UMsNu5UiB3mqQvIkA1FJLfO0CFH8ajxZXd/JtGpgPobnmmGe++RDVdJ7G50OIXg3popMeeueod37656l/vrrnm5uOOgZIfJECBpr3sZsgUMQRLXLTEJJBxPRkkETGRmSS8T1a2CCPZANlYb3oDVhvfQOio6B9FrOn8X0W2H/Pfefeaz97NeOXr/35mI+//vcouJ9MO7V03gcDFjCmxCIADGAAr1CFG2mBWQhEoA600IMLseGBEIygBCdIwQpa8IIYzKAGMcgDaGTMFSAMoQhDaAE9HOyEKOyBewZijxZG0BItbKElItiEGNrjGhC8hg3t8UIbzhCCO8ThA+Z1aMMexvCHDwxiDndoRBk+8A03Slp/1CTFKpaHiv3JS9IMssMuevGLYAyjGMdIxjJ6EYoK0oNivmCfL+RIINAD0GT0YCI8rdAgz4CBHmFQAoKUYI8wWAFBAAkDgpQCkH0cyB/3KMiBEJIgIECkHwEJgkECEpKSVKQe39CCjH0gTUbIWAsQcg8CZMw78TDlF76lowxdUSBXfONArrhC9pSnlbjMpS7rssuZzKWXPQHKL4HZEWESMyXDPKZHkqnMZjrzLnZ5pjSnSc1qWmQuzLSmQrCpzW5685vfjCY4x0nOcprznB4JCAAh+QQJCgBIACwAAAAAjABMAAAI/wCRCBxIsKDBgwgTKlzIsKHDhxAjSpxIsaLFixgzatzIsaPHjyBDihxJcmGiRCVTqsyIcqXLlzBjypxJs6bNmzgPtjR4MqfPn0CDCh1KtKjNnkaTPtyptKlToEyfShUYderTqlaNnkSJNGvTrl6dYg1bdCzZs2jTqvUpoa3bt3DjrnWZoq7du3jzMphb8oMeQYADCx5MOIUeviIlpOAGeNG2x5AjSx4HmFuVw4g/KgbMxQSCz6AhDSuCoMIw0NsoC7qcWXMKQYtMVAADGnSUcAiKRKmNYBEv1q07bv7cZTfvz9OSfw5HGgEU1vHiBdc4/Djvb3refY5y2jlrPeCnY/+sbv1zjAzmzFGZBgnS5+f3PqTvIUG8RfK1i5vPsGDBpB8egPbcF5P0l0F99jV0z4ILCoQfaBV0sV9/C7jwwzcYblAFGhQemGBDX9BAAwH3HKbHa7xVYEht51FYoYgictghgh8iZMQ95vSnBYP3oBiaJhWwyJ+LRLrooUGlwKCkkgSVsCQMKxD0JAwEgfBkCU0+GeVAUxK0wpVZLrmlQF0O9OWSTpRY4ALp0dCjILy5Vxow72hR5J0U2oGZQPb06eefgAYq6KCEFmrooYj6CQMIICgAIw0unINiFBLWZkgFetjZnzU62EEkEw/QoIN/eyLh5zWoXmPJn5akek0TrLr/Cqirq/rZaqqw2ppqrX02QWusuAKr6p++7trnDtAka8o5NKDYRZDHZUohBBkMWaEWTEBwj52TlMrGt+CGK+645JZr7rnopquuuejU9YmPtRWBGwKZ2rCBDV98IeMCPaChRb7ybCBPqVkUnMbBaTRQcMENIJwGCgtnUY3DEWfhsMILN4wwxAtPfHA1EaNwccQaH8xxwR6nAfLCIiOMMcMI9wEvaMPA8VmmV3TSCZ4UGtNJGaV+PMTQQztMNNFGH+1wNUcPkbTSCDe9tNRRH51yGlQLDfXBR8ssSDlSwNFdezdrkfPOX7jAZjzcUrGAz0ATBA44lahhtxrUzD133XdX/6I3ONTcrcbf4Aiet96B9/134nb/zbfdh8/NuBp+I3535HQbvrjdM0zxmiBQxAFtbR74u8EGC3yRSb73qPMFAR8sYIM8KdCIBORH5H4EGYITofsR7gj++xGCV/I773f7rnvwdw9f/O9E9P7742o4f7c70AtOxhEzuEADAxYApsQi5JdPvgUb9udCteyzX2EAtiMRxvxt1N+GH/PP74f9beRPP//+CwP/8Je//dkvgPzrn/8G6D8D1g+BAFyg/QiYv1XQQAtoIIAeXMHBDnqQg1VQhxZGSMISjlCDBvGDHwaBjRZiwwsqVKEXXIiNQcTQDzWg4Q1Z6EIYxnCGLrRhDP9z6MId0tCHMqShEFVIxBYasYc3PIEecrSAHZUIPDzK4hV5pAcJ6IFBCHGDGMdIxjKa8YxoTKMa18jGNqJxDlNcQAYOc49JmGMS9ziIHr6Qni+Axwg56kGpDMKIQhIkAoUs5BwIIoZEMiICBHGkGAgyB0cuciCNTGRBJElJSzLSkZtM5CQHUslECuEe+SKAQO5BgHxJxyB6oEK+WiAQI+SrA4Os0UPAEx4k8DKXAvklQXQwR2DqMiVgOeZLkqnMlTCzmdCcy1aQwJVpRjMk06zmM6/pEbNwEyTb/OZHwinOjpCznNREJzaj4k11TiSZ7XSnPHESz3lW5JnntKc+94kTFnjyUyP1/OdSBErQghr0oB0JCAAh+QQFCgAjACwAAAAAjABMAAAI/wBHCBxIsKDBgwgTKlzIsKHDhxAjSpxIsaLFixgzatzIsaPHjyBDihxJkmCikihTWjw5giVLlTBjHkz0UmBNmThz6tzJs6fPkTRn3vxJtKjRo0iTbgxqUqlTiC5tPt05dOXUnkyval2YdatXg12/ih07lmZQs2bJql27NSzbqW7fOo0rN2nViBLy6t3Lt29dmfGqCB5MuLBhBvH+pmSQQpAgKJAjS54M2XEVBopLSmjseBGCz6BDi37lWFAVPZlHbnb8SvRnSL0qIKjQK/Q2y6hTh1z9ahuYKK4rGEJgSHboV1BO697d+HOFLq4/e/j2zTmYz8lR37u3vOPq6KGnEf/68mXaNjrAEWT/QL5b943fwX+OkWGBOT3TQie/92HBggwSvCeRHgQSKFB8osExzHz12UdDddhVQYM5/gEoYET3ZDBJBveghmBoRRhHn38LaKHFDyimYIcWJFp44UP39KCFDhno0WFzocERTmgjkrhhBkCy2GKALzq03Tk6LEADFffg+NowshU3jR1okGjllf658EWRMN7zhX80NCkIeLTpISSWaC4wSW4ElQLDm28SVAKcMKxAEJ0wEAQCnSXISaedA+FJ0Ap8+gknoAIJOhChcPYpUCAdUphBc8PAEZ2ZJCZC45UQWIPpmgTZI+qopJZq6qmopqrqqqy2eioMTtz/QwMNmTRXQRGXnqnIFw0u0EOVC9zDIqgDjXrNsddYQqolyF7TxLLNltqssqMyi+yz1SJLrahNTAvttd8mS2q32pJ6ATTQfCKma10YZ+YGV1wRJIkuzAgkvPKwOQIb/Pbr778AByzwwAQXbPDBBZvxSWNSbBMOrghEAR0CZl7RSSclJlkiheawaEwnZeibxchplJxGAyOP3IDJaaCQchbVsPxyFiyjnPLKJruccswlV/MyCjW/jHPJOo/Mcxo+pwy0yTarbHIfnL2ioGvvaGExxrzaJ+wCdvT3ccgE9TzE2GOzTDbZZp/NcjVnD5G22ia3vbbccZ99dBp0iw13yWdD/10aF5BERx899CzwhQTxxHMP4hL0R08GlxQEDjiVqGG5GtRMPnnll1eiOTjUXK7G5+CInrnmoXf+eeqWf8655adPzroanqN+eeyUm7665TNMsQlnUCgh/PDCu1JFD/6ZqPzyvhJgEOxHRH8EGaITIf0R7oh+/RGiV3I99ZdbL332l2/f/fVEVH/962qYf7k76ItOxhEzuABkBhbkr//++aeQyf0ADKDzDBKGArbhgG3wQwEL6AcEtmGBBnQgBMPgQAUusIEInKADHwjBCkIQgwfUoAQ7iEALMtAPa5iEfbTQIT0YgTxGKJAMvfSFDhDoHgT4AgE6hBA/+GEQ2AgiNvy84EMfekGI2BhEEf1QAyQuEYhCJGIRjyhEJRaxiUJ8IhKlaEQkWtGHWAyiFqO4RC/UIIUl2s4H9PAlw+lrBPHQQ4UCtDU7vJEgbsijHvfIxz768Y+ADKQgB0lIQGJjDdvZjkBstJ3EHCSRRLLRHQnCiEoSJAKVrOQcCCKGTDIiApTMpBgIMgdPbnIgncxkQTw5yoGUMpOnFEgqLRnKSrZSIK/U5Ag+kLjEDaSXCQGmQHzJpWIasyV3OaYyl8nMZi7nLsl0ZkagKc1qWvOa2JxLNLPJzW6+ZZvevAhdwrkStJCTI2gZ5zknos51shOc7oynPOdJz3ra857hDAgAOw==' dmonad-lib0-c7e7806/testing.test.js000066400000000000000000000115151512504553500171460ustar00rootroot00000000000000import * as t from './testing.js' import * as math from './math.js' import * as buffer from './buffer.js' import * as map from './map.js' import * as promise from './promise.js' import * as error from './error.js' /* c8 ignore next */ export const nottestingNotTested = () => { t.assert(false, 'This test should not be executed because the name doesnt start with "test"') } export const testAssertTyping = () => { const q = Math.random() const x = q === 0.3 ? null : { a: 4 } // this should always be an object // t.assert(x.a === 4) - this will give a type error because the type is uncertain t.assert(x) t.assert(x.a === 4) // this works because x is asserted } /** * @param {t.TestCase} _tc */ export const testComparing = _tc => { t.compare({}, {}) t.compare({ a: 4 }, { a: 4 }, 'simple compare (object)') t.compare([1, 2], [1, 2], 'simple compare (array)') t.compare({ a: [1, 2] }, { a: [1, 2] }, 'simple compare nested') t.compare(new Set(['3', 1234]), new Set(['3', 1234]), 'compare Sets') const map1 = map.create() map1.set(1, 2) map1.set('x', {}) map1.set(98, 'tst') const map2 = new Map() map2.set(1, 2) map2.set('x', {}) map2.set(98, 'tst') t.compare(map1, map2, 'compare Maps') t.describe('The following errors are expected!') t.fails(() => { t.compare({ a: 4 }, { b: 5 }, 'childs are not equal') }) t.fails(() => { t.compare({ a: 4 }, { a: 5 }, 'childs are not equal') }) t.fails(() => { t.compare({ a: 4 }, null, 'childs are not equal') }) t.fails(() => { // @ts-ignore t.compare({ a: 4 }, [4], 'childs have different types') }) t.fails(() => { t.compare({ a: 4 }, { a: 4, b: 5 }, 'childs have different length (object)') }) t.fails(() => { t.compare([1], [1, 2]) // childs have different length (array) -- no message }) t.fails(() => { t.compare(buffer.createUint8ArrayFromLen(1), buffer.createUint8ArrayFromLen(2), 'Uint8Arrays have different length') }) t.fails(() => { t.compare(buffer.createUint8ArrayFromLen(1).buffer, buffer.createUint8ArrayFromLen(2).buffer, 'ArrayBuffer have different length') }) t.fails(() => { t.compareStrings('str1', 'str2', 'Strings comparison can fail') }) t.compareArrays([], [], 'Comparing empty arrays') t.fails(() => { t.compareArrays([1], [1, 2], 'Compare arrays with different length') }) t.fails(() => { t.compareArrays([1], [2]) // Compare different arrays -- no message }) t.compareObjects({ x: 1 }, { x: 1 }, 'comparing objects') t.fails(() => { t.compareObjects({}, { x: 1 }, 'compareObjects can fail') }) t.fails(() => { t.compareObjects({ x: 3 }, { x: 1 }) // Compare different objects -- no message }) t.fails(() => { t.compare({ x: undefined }, { y: 1 }, 'compare correctly handles undefined') }) t.fails(() => { t.compareObjects({ x: undefined }, { y: 1 }, 'compare correctly handles undefined') }) t.describe('Map fails') t.fails(() => { const m1 = new Map() m1.set(1, 2) const m2 = new Map() m2.set(1, 3) t.compare(m1, m2) // childs have different length (array) -- no message }) t.fails(() => { const m1 = new Map() m1.set(2, 2) const m2 = new Map() m2.set(1, 2) t.compare(m1, m2) // childs have different length (array) -- no message }) t.fails(() => { const m1 = new Map() m1.set(1, 2) const m2 = new Map() t.compare(m1, m2) // childs have different length (array) -- no message }) t.describe('Set fails') t.fails(() => { t.compare(new Set([1]), new Set([1, 2])) // childs have different length (array) -- no message }) t.fails(() => { t.compare(new Set([1]), new Set([2])) // childs have different length (array) -- no message }) t.group('test object with constructor set to `undefined`', () => { const a = Object.create(null) const b = Object.create(null) a.x = 42 b.x = 42 t.compare(a, b) }) } export const testFailing = async () => { t.fails(() => { t.fail('This fail is expected') }) await t.promiseRejected(() => promise.reject(error.create('should be rejected'))) t.fails(() => { t.fails(() => {}) }) await t.failsAsync(async () => { await t.failsAsync(async () => { }) }) await t.promiseRejected(() => t.promiseRejected(() => promise.resolve()) ) } export const testSkipping = () => { t.skip(false) t.assert(true) t.skip() /* c8 ignore next */ t.fail('should have skipped') } export const testAsync = async () => { await t.measureTimeAsync('time', () => promise.create(r => setTimeout(r))) await t.groupAsync('some description', () => promise.wait(1)) await t.promiseRejected(() => promise.reject(error.create('should be rejected'))) } export const testRepeatRepetition = () => { const arr = [] const n = 100 for (let i = 1; i <= n; i++) { arr.push(i) } t.assert(arr.reduce(math.add, 0) === (n + 1) * n / 2) } dmonad-lib0-c7e7806/time.js000066400000000000000000000023161512504553500154500ustar00rootroot00000000000000/** * Utility module to work with time. * * @module time */ import * as metric from './metric.js' import * as math from './math.js' /** * Return current time. * * @return {Date} */ export const getDate = () => new Date() /** * Return current unix time. * * @return {number} */ export 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 */ export const humanizeDuration = d => { if (d < 60000) { const p = metric.prefix(d, -1) return math.round(p.n * 100) / 100 + p.prefix + 's' } d = math.floor(d / 1000) const seconds = d % 60 const minutes = math.floor(d / 60) % 60 const hours = math.floor(d / 3600) % 24 const days = math.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' : '') } dmonad-lib0-c7e7806/time.test.js000066400000000000000000000024151512504553500164260ustar00rootroot00000000000000import * as time from './time.js' import * as t from './testing.js' import * as math from './math.js' /** * @param {t.TestCase} tc */ export const testTime = tc => { const l = time.getDate().getTime() const r = time.getUnixTime() t.assert(math.abs(l - r) < 10, 'Times generated are roughly the same') } /** * @param {t.TestCase} tc */ export const testHumanDuration = tc => { t.assert(time.humanizeDuration(10) === '10ms') t.assert(time.humanizeDuration(0.1) === '100μs') t.assert(time.humanizeDuration(61030) === '1min 1s') t.assert(time.humanizeDuration(60030) === '1min') t.assert(time.humanizeDuration(3600000) === '1h') t.assert(time.humanizeDuration(3640000) === '1h 1min') t.assert(time.humanizeDuration(3700000) === '1h 2min') t.assert(time.humanizeDuration(60 * 60 * 1000 + 29000) === '1h') t.assert(time.humanizeDuration(60 * 60 * 1000 + 31000) === '1h 1min') t.assert(time.humanizeDuration(60 * 60 * 1000 + 31000 * 3) === '1h 2min') t.assert(time.humanizeDuration(3600000 * 25) === '1d 1h') t.assert(time.humanizeDuration(3600000 * 24.6) === '1d 1h') t.assert(time.humanizeDuration(3600000 * 25.6) === '1d 2h') t.assert(time.humanizeDuration(3600000 * 24 * 400) === '400d') // test round t.assert(time.humanizeDuration(6001) === '6s') } dmonad-lib0-c7e7806/trait/000077500000000000000000000000001512504553500152755ustar00rootroot00000000000000dmonad-lib0-c7e7806/trait/equality.js000066400000000000000000000015471512504553500174770ustar00rootroot00000000000000export const EqualityTraitSymbol = Symbol('Equality') /** * @typedef {{ [EqualityTraitSymbol]:(other:EqualityTrait)=>boolean }} EqualityTrait */ /** * * Utility function to compare any two objects. * * Note that it is expected that the first parameter is more specific than the latter one. * * @example js * class X { [traits.EqualityTraitSymbol] (other) { return other === this } } * class X2 { [traits.EqualityTraitSymbol] (other) { return other === this }, x2 () { return 2 } } * // this is fine * traits.equals(new X2(), new X()) * // this is not, because the left type is less specific than the right one * traits.equals(new X(), new X2()) * * @template {EqualityTrait} T * @param {NoInfer} a * @param {T} b * @return {boolean} */ export const equals = (a, b) => a === b || !!a?.[EqualityTraitSymbol]?.(b) || false dmonad-lib0-c7e7806/trait/fingerprint.js000066400000000000000000000016731512504553500201710ustar00rootroot00000000000000import * as encoding from '../encoding.js' import * as rabin from '../hash/rabin.js' import * as buffer from '../buffer.js' export const FingerprintTraitSymbol = Symbol('Fingerprint') /** * When implementing this trait, it is recommended to write some sort of "magic number" first to * ensure that different types of objects don't have the same fingerprint. * * The recommended pracice is to generate a random u32 number as your magic number. e.g. using * `console.log(random.uint32().toString(16))` * * @typedef {{ [FingerprintTraitSymbol]:()=>string } | import('../encoding.js').AnyEncodable} Fingerprintable */ /** * @param {Fingerprintable} a * @return {string} */ export const fingerprint = a => (a != null && /** @type {any} */ (a)[FingerprintTraitSymbol]?.()) || buffer.toBase64(rabin.fingerprint(rabin.StandardIrreducible128, encoding.encode(encoder => { encoding.writeUint32(encoder, 0x8de1c475); encoding.writeAny(encoder, a) }))) dmonad-lib0-c7e7806/trait/traits.js000066400000000000000000000000771512504553500171450ustar00rootroot00000000000000export * from './equality.js' export * from './fingerprint.js' dmonad-lib0-c7e7806/trait/traits.test.js000066400000000000000000000047221512504553500201240ustar00rootroot00000000000000import * as t from '../testing.js' import * as fun from '../function.js' import * as s from '../schema.js' import * as traits from './traits.js' /** * @param {t.TestCase} _tc */ export const testEqualityTrait1 = _tc => { /** * @implements traits.EqualityTrait */ class X { get x () { return 4 } /** * @param {object} other * @return boolean */ [traits.EqualityTraitSymbol] (other) { return this === other } } /** * extension of X * @implements traits.EqualityTrait */ class X2 extends X { get x2 () { return 4 } } const x = new X() const y = new X() t.assert(!x[traits.EqualityTraitSymbol](y)) t.assert(x[traits.EqualityTraitSymbol](x)) t.assert(!traits.equals(x, y)) t.assert(traits.equals(x, x)) // left needs to be more specific t.assert(!traits.equals(x, { [traits.EqualityTraitSymbol]: (other) => other === y })) // if left is not more specific, then it should throw a type error // @ts-expect-error other object doesn't match type t.assert(!traits.equals({ [traits.EqualityTraitSymbol]: (other) => other === y }, x)) // right has a property that is missing in left // @ts-expect-error t.assert(!traits.equals(x, { [traits.EqualityTraitSymbol]: (other) => other === y, get y () { return y } })) const x2 = new X2() const x2x2 = /** @type {traits.EqualityTrait} */ (x2) // @ts-expect-error x2 typings assume that it is not constructed by X2. so this typecheck fails s.$constructedBy(X2).validate(x2x2) t.assert(x2 === x2x2) } /** * @param {t.TestCase} _tc */ export const testEqualityTrait2 = _tc => { /** * @implements traits.EqualityTrait */ class X { /** * @param {any} other * @return boolean */ [traits.EqualityTraitSymbol] (other) { return this.constructor === other.constructor } } class Y {} const x = new X() const x2 = new X() const y = new Y() t.assert(!x[traits.EqualityTraitSymbol](y)) t.assert(x[traits.EqualityTraitSymbol](x)) t.assert(x[traits.EqualityTraitSymbol](x2)) t.compare(x, x2) t.fails(() => { t.compare(x, y) }) t.assert(fun.equalityDeep(x, x)) t.assert(fun.equalityDeep(x, x2)) t.assert(!fun.equalityDeep(x, y)) } export const testFingerprintTrait = () => { const someObj = { a: 42 } const someObj2 = null const fing = { [traits.FingerprintTraitSymbol]: () => '' } t.assert(traits.fingerprint(someObj) !== traits.fingerprint(someObj2)) t.assert(traits.fingerprint(someObj) !== traits.fingerprint(fing)) } dmonad-lib0-c7e7806/tree.js000066400000000000000000000276261512504553500154640ustar00rootroot00000000000000/** * Red-black-tree implementation. * * @module tree */ // @ts-nocheck TODO: remove or refactor this file const rotate = (tree, parent, newParent, n) => { if (parent === null) { tree.root = newParent newParent._parent = null } else if (parent.left === n) { parent.left = newParent } else if (parent.right === n) { parent.right = newParent } else { throw new Error('The elements are wrongly connected!') } } /** * @template V */ class N { /** * A created node is always red! * * @param {V} val */ constructor (val) { this.val = val this.color = true this._left = null this._right = null this._parent = null } isRed () { return this.color } isBlack () { return !this.color } redden () { this.color = true; return this } blacken () { this.color = false; return this } get grandparent () { return this.parent.parent } get parent () { return this._parent } get sibling () { return (this === this.parent.left) ? this.parent.right : this.parent.left } get left () { return this._left } get right () { return this._right } set left (n) { if (n !== null) { n._parent = this } this._left = n } set right (n) { if (n !== null) { n._parent = this } this._right = n } rotateLeft (tree) { const parent = this.parent const newParent = this.right const newRight = this.right.left newParent.left = this this.right = newRight rotate(tree, parent, newParent, this) } next () { if (this.right !== null) { // search the most left node in the right tree let o = this.right while (o.left !== null) { o = o.left } return o } else { let p = this while (p.parent !== null && p !== p.parent.left) { p = p.parent } return p.parent } } prev () { if (this.left !== null) { // search the most right node in the left tree let o = this.left while (o.right !== null) { o = o.right } return o } else { let p = this while (p.parent !== null && p !== p.parent.right) { p = p.parent } return p.parent } } rotateRight (tree) { const parent = this.parent const newParent = this.left const newLeft = this.left.right newParent.right = this this.left = newLeft rotate(tree, parent, newParent, this) } getUncle () { // we can assume that grandparent exists when this is called! if (this.parent === this.parent.parent.left) { return this.parent.parent.right } else { return this.parent.parent.left } } } const isBlack = node => node !== null ? node.isBlack() : true const isRed = (node) => node !== null ? node.isRed() : false /** * This is a Red Black Tree implementation * * @template K,V */ export class Tree { constructor () { this.root = null this.length = 0 } /** * @param {K} id */ findNext (id) { const nextID = id.clone() nextID.clock += 1 return this.findWithLowerBound(nextID) } /** * @param {K} id */ findPrev (id) { const prevID = id.clone() prevID.clock -= 1 return this.findWithUpperBound(prevID) } /** * @param {K} from */ findNodeWithLowerBound (from) { let o = this.root if (o === null) { return null } else { while (true) { if (from === null || (from.lessThan(o.val._id) && o.left !== null)) { // o is included in the bound // try to find an element that is closer to the bound o = o.left } else if (from !== null && o.val._id.lessThan(from)) { // o is not within the bound, maybe one of the right elements is.. if (o.right !== null) { o = o.right } else { // there is no right element. Search for the next bigger element, // this should be within the bounds return o.next() } } else { return o } } } } /** * @param {K} to */ findNodeWithUpperBound (to) { if (to === undefined) { throw new Error('You must define from!') } let o = this.root if (o === null) { return null } else { while (true) { if ((to === null || o.val._id.lessThan(to)) && o.right !== null) { // o is included in the bound // try to find an element that is closer to the bound o = o.right } else if (to !== null && to.lessThan(o.val._id)) { // o is not within the bound, maybe one of the left elements is.. if (o.left !== null) { o = o.left } else { // there is no left element. Search for the prev smaller element, // this should be within the bounds return o.prev() } } else { return o } } } } /** * @return {V} */ findSmallestNode () { let o = this.root while (o != null && o.left != null) { o = o.left } return o } /** * @param {K} from * @return {V} */ findWithLowerBound (from) { const n = this.findNodeWithLowerBound(from) return n == null ? null : n.val } /** * @param {K} to * @return {V} */ findWithUpperBound (to) { const n = this.findNodeWithUpperBound(to) return n == null ? null : n.val } /** * @param {K} from * @param {V} from * @param {function(V):void} f */ iterate (from, to, f) { let o if (from === null) { o = this.findSmallestNode() } else { o = this.findNodeWithLowerBound(from) } while ( o !== null && ( to === null || // eslint-disable-line no-unmodified-loop-condition o.val._id.lessThan(to) || o.val._id.equals(to) ) ) { f(o.val) o = o.next() } } /** * @param {K} id * @return {V|null} */ find (id) { const n = this.findNode(id) if (n !== null) { return n.val } else { return null } } /** * @param {K} id * @return {N|null} */ findNode (id) { let o = this.root if (o === null) { return null } else { while (true) { if (o === null) { return null } if (id.lessThan(o.val._id)) { o = o.left } else if (o.val._id.lessThan(id)) { o = o.right } else { return o } } } } /** * @param {K} id */ delete (id) { let d = this.findNode(id) if (d == null) { // throw new Error('Element does not exist!') return } this.length-- if (d.left !== null && d.right !== null) { // switch d with the greates element in the left subtree. // o should have at most one child. let o = d.left // find while (o.right !== null) { o = o.right } // switch d.val = o.val d = o } // d has at most one child // let n be the node that replaces d let isFakeChild let child = d.left || d.right if (child === null) { isFakeChild = true child = new N(null) child.blacken() d.right = child } else { isFakeChild = false } if (d.parent === null) { if (!isFakeChild) { this.root = child child.blacken() child._parent = null } else { this.root = null } return } else if (d.parent.left === d) { d.parent.left = child } else if (d.parent.right === d) { d.parent.right = child } else { throw new Error('Impossible!') } if (d.isBlack()) { if (child.isRed()) { child.blacken() } else { this._fixDelete(child) } } this.root.blacken() if (isFakeChild) { if (child.parent.left === child) { child.parent.left = null } else if (child.parent.right === child) { child.parent.right = null } else { throw new Error('Impossible #3') } } } _fixDelete (n) { if (n.parent === null) { // this can only be called after the first iteration of fixDelete. return } // d was already replaced by the child // d is not the root // d and child are black let sibling = n.sibling if (isRed(sibling)) { // make sibling the grandfather n.parent.redden() sibling.blacken() if (n === n.parent.left) { n.parent.rotateLeft(this) } else if (n === n.parent.right) { n.parent.rotateRight(this) } else { throw new Error('Impossible #2') } sibling = n.sibling } // parent, sibling, and children of n are black if (n.parent.isBlack() && sibling.isBlack() && isBlack(sibling.left) && isBlack(sibling.right) ) { sibling.redden() this._fixDelete(n.parent) } else if (n.parent.isRed() && sibling.isBlack() && isBlack(sibling.left) && isBlack(sibling.right) ) { sibling.redden() n.parent.blacken() } else { if (n === n.parent.left && sibling.isBlack() && isRed(sibling.left) && isBlack(sibling.right) ) { sibling.redden() sibling.left.blacken() sibling.rotateRight(this) sibling = n.sibling } else if (n === n.parent.right && sibling.isBlack() && isRed(sibling.right) && isBlack(sibling.left) ) { sibling.redden() sibling.right.blacken() sibling.rotateLeft(this) sibling = n.sibling } sibling.color = n.parent.color n.parent.blacken() if (n === n.parent.left) { sibling.right.blacken() n.parent.rotateLeft(this) } else { sibling.left.blacken() n.parent.rotateRight(this) } } } put (v) { const node = new N(v) if (this.root !== null) { let p = this.root // p abbrev. parent while (true) { if (node.val._id.lessThan(p.val._id)) { if (p.left === null) { p.left = node break } else { p = p.left } } else if (p.val._id.lessThan(node.val._id)) { if (p.right === null) { p.right = node break } else { p = p.right } } else { p.val = node.val return p } } this._fixInsert(node) } else { this.root = node } this.length++ this.root.blacken() return node } _fixInsert (n) { if (n.parent === null) { n.blacken() return } else if (n.parent.isBlack()) { return } const uncle = n.getUncle() if (uncle !== null && uncle.isRed()) { // Note: parent: red, uncle: red n.parent.blacken() uncle.blacken() n.grandparent.redden() this._fixInsert(n.grandparent) } else { // Note: parent: red, uncle: black or null // Now we transform the tree in such a way that // either of these holds: // 1) grandparent.left.isRed // and grandparent.left.left.isRed // 2) grandparent.right.isRed // and grandparent.right.right.isRed if (n === n.parent.right && n.parent === n.grandparent.left) { n.parent.rotateLeft(this) // Since we rotated and want to use the previous // cases, we need to set n in such a way that // n.parent.isRed again n = n.left } else if (n === n.parent.left && n.parent === n.grandparent.right) { n.parent.rotateRight(this) // see above n = n.right } // Case 1) or 2) hold from here on. // Now traverse grandparent, make parent a black node // on the highest level which holds two red nodes. n.parent.blacken() n.grandparent.redden() if (n === n.parent.left) { // Case 1 n.grandparent.rotateRight(this) } else { // Case 2 n.grandparent.rotateLeft(this) } } } } dmonad-lib0-c7e7806/tree.test.js000066400000000000000000000126571512504553500164400ustar00rootroot00000000000000/* import { Tree as RedBlackTree } from 'lib0/Tree.js' import * as ID from '../utils/ID.js' import { test, proxyConsole } from 'cutest' import * as random from 'lib0/prng.js' proxyConsole() var numberOfRBTreeTests = 10000 const checkRedNodesDoNotHaveBlackChildren = (t, tree) => { let correct = true const traverse = n => { if (n == null) { return } if (n.isRed()) { if (n.left != null) { correct = correct && !n.left.isRed() } if (n.right != null) { correct = correct && !n.right.isRed() } } traverse(n.left) traverse(n.right) } traverse(tree.root) t.assert(correct, 'Red nodes do not have black children') } const checkBlackHeightOfSubTreesAreEqual = (t, tree) => { let correct = true const traverse = n => { if (n == null) { return 0 } var sub1 = traverse(n.left) var sub2 = traverse(n.right) if (sub1 !== sub2) { correct = false } if (n.isRed()) { return sub1 } else { return sub1 + 1 } } traverse(tree.root) t.assert(correct, 'Black-height of sub-trees are equal') } const checkRootNodeIsBlack = (t, tree) => { t.assert(tree.root == null || tree.root.isBlack(), 'root node is black') } test('RedBlack Tree', async function redBlackTree (t) { let tree = new RedBlackTree() tree.put({_id: ID.createID(8433, 0)}) tree.put({_id: ID.createID(12844, 0)}) tree.put({_id: ID.createID(1795, 0)}) tree.put({_id: ID.createID(30302, 0)}) tree.put({_id: ID.createID(64287)}) tree.delete(ID.createID(8433, 0)) tree.put({_id: ID.createID(28996)}) tree.delete(ID.createID(64287)) tree.put({_id: ID.createID(22721)}) checkRootNodeIsBlack(t, tree) checkBlackHeightOfSubTreesAreEqual(t, tree) checkRedNodesDoNotHaveBlackChildren(t, tree) }) test(`random tests (${numberOfRBTreeTests})`, async function randomRBTree (t) { let prng = random.createPRNG(t.getSeed() * 1000000000) let tree = new RedBlackTree() let elements = [] for (var i = 0; i < numberOfRBTreeTests; i++) { if (random.int31(prng, 0, 100) < 80) { // 80% chance to insert an element let obj = ID.createID(random.int31(prng, 0, numberOfRBTreeTests), random.int31(prng, 0, 1)) let nodeExists = tree.find(obj) if (nodeExists === null) { if (elements.some(e => e.equals(obj))) { t.assert(false, 'tree and elements contain different results') } elements.push(obj) tree.put({_id: obj}) } } else if (elements.length > 0) { // ~20% chance to delete an element var elem = random.oneOf(prng, elements) elements = elements.filter(e => { return !e.equals(elem) }) tree.delete(elem) } } checkRootNodeIsBlack(t, tree) checkBlackHeightOfSubTreesAreEqual(t, tree) checkRedNodesDoNotHaveBlackChildren(t, tree) // TEST if all nodes exist let allNodesExist = true for (let id of elements) { let node = tree.find(id) if (!node._id.equals(id)) { allNodesExist = false } } t.assert(allNodesExist, 'All inserted nodes exist') // TEST lower bound search let findAllNodesWithLowerBoundSerach = true for (let id of elements) { let node = tree.findWithLowerBound(id) if (!node._id.equals(id)) { findAllNodesWithLowerBoundSerach = false } } t.assert( findAllNodesWithLowerBoundSerach, 'Find every object with lower bound search' ) // TEST iteration (with lower bound search) let lowerBound = random.oneOf(prng, elements) let expectedResults = elements.filter((e, pos) => (lowerBound.lessThan(e) || e.equals(lowerBound)) && elements.indexOf(e) === pos ).length let actualResults = 0 tree.iterate(lowerBound, null, val => { if (val == null) { t.assert(false, 'val is undefined!') } actualResults++ }) t.assert( expectedResults === actualResults, 'Iterating over a tree with lower bound yields the right amount of results' ) expectedResults = elements.filter((e, pos) => elements.indexOf(e) === pos ).length actualResults = 0 tree.iterate(null, null, val => { if (val == null) { t.assert(false, 'val is undefined!') } actualResults++ }) t.assert( expectedResults === actualResults, 'iterating over a tree without bounds yields the right amount of results' ) let upperBound = random.oneOf(prng, elements) expectedResults = elements.filter((e, pos) => (e.lessThan(upperBound) || e.equals(upperBound)) && elements.indexOf(e) === pos ).length actualResults = 0 tree.iterate(null, upperBound, val => { if (val == null) { t.assert(false, 'val is undefined!') } actualResults++ }) t.assert( expectedResults === actualResults, 'iterating over a tree with upper bound yields the right amount of results' ) upperBound = random.oneOf(prng, elements) lowerBound = random.oneOf(prng, elements) if (upperBound.lessThan(lowerBound)) { [lowerBound, upperBound] = [upperBound, lowerBound] } expectedResults = elements.filter((e, pos) => (lowerBound.lessThan(e) || e.equals(lowerBound)) && (e.lessThan(upperBound) || e.equals(upperBound)) && elements.indexOf(e) === pos ).length actualResults = 0 tree.iterate(lowerBound, upperBound, val => { if (val == null) { t.assert(false, 'val is undefined!') } actualResults++ }) t.assert( expectedResults === actualResults, 'iterating over a tree with upper bound yields the right amount of results' ) }) */ dmonad-lib0-c7e7806/tsconfig.json000066400000000000000000000013461512504553500166650ustar00rootroot00000000000000{ "compilerOptions": { "target": "esnext", "lib": ["ESNext", "dom"], "module": "nodenext", "allowJs": true, "checkJs": true, "declaration": true, "declarationMap": true, "outDir": "./dist", "baseUrl": "./", "rootDir": "./", "emitDeclarationOnly": true, "strict": true, "noImplicitAny": true, "moduleResolution": "nodenext", "paths": { "lib0/*": ["./*"], "lib0/webcrypto": ["./webcrypto.js"], "lib0/performance": ["./performance.node.js"], "lib0/logging": ["./logging.node.js"] } }, "include": ["./*.js", "./diff/*.js", "./hash/*.js", "./crypto/*.js", "./bin/*.js", "./delta/*.js", "./trait/*.js"], "exclude": ["./dist/**/*", "./isomorphic.js"] } dmonad-lib0-c7e7806/url.js000066400000000000000000000015531512504553500153160ustar00rootroot00000000000000/** * Utility module to work with urls. * * @module url */ import * as object from './object.js' /** * Parse query parameters from an url. * * @param {string} url * @return {Object} */ export const decodeQueryParams = url => { /** * @type {Object} */ const query = {} const urlQuerySplit = url.split('?') const pairs = urlQuerySplit[urlQuerySplit.length - 1].split('&') for (let i = 0; i < pairs.length; i++) { const item = pairs[i] if (item.length > 0) { const pair = item.split('=') query[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || '') } } return query } /** * @param {Object} params * @return {string} */ export const encodeQueryParams = params => object.map(params, (val, key) => `${encodeURIComponent(key)}=${encodeURIComponent(val)}`).join('&') dmonad-lib0-c7e7806/url.test.js000066400000000000000000000016721512504553500162760ustar00rootroot00000000000000import * as t from './testing.js' import * as url from './url.js' /** * @param {Object} params */ const paramTest = params => { const out = url.decodeQueryParams(url.encodeQueryParams(params)) t.compareObjects(params, out, 'Compare params') } /** * @param {t.TestCase} tc */ export const testUrlParamQuery = tc => { paramTest({}) paramTest({ a: '4' }) paramTest({ a: 'dtrn', b: '0x0' }) t.compareObjects({ }, url.decodeQueryParams('http://localhost:8080/dtrn?')) t.compareObjects({ a: 'ay' }, url.decodeQueryParams('http://localhost:8080/dtrn?a=ay')) t.compareObjects({ a: '' }, url.decodeQueryParams('http://localhost:8080/dtrn?a=')) t.compareObjects({ a: '' }, url.decodeQueryParams('http://localhost:8080/dtrn?a')) t.compareObjects({ a: 'ay' }, url.decodeQueryParams('http://localhost:8080/dtrn?a=ay&')) t.compareObjects({ a: 'ay', b: 'bey' }, url.decodeQueryParams('http://localhost:8080/dtrn?a=ay&b=bey')) } dmonad-lib0-c7e7806/webcrypto.deno.js000066400000000000000000000003111512504553500174450ustar00rootroot00000000000000// eslint-disable-next-line export const subtle = /** @type {any} */ (crypto).subtle // eslint-disable-next-line export const getRandomValues = /** @type {any} */ (crypto).getRandomValues.bind(crypto) dmonad-lib0-c7e7806/webcrypto.js000066400000000000000000000002011512504553500165170ustar00rootroot00000000000000/* eslint-env browser */ export const subtle = crypto.subtle export const getRandomValues = crypto.getRandomValues.bind(crypto) dmonad-lib0-c7e7806/webcrypto.node.js000066400000000000000000000003031512504553500174460ustar00rootroot00000000000000import { webcrypto } from 'node:crypto' export const subtle = /** @type {any} */ (webcrypto).subtle export const getRandomValues = /** @type {any} */ (webcrypto).getRandomValues.bind(webcrypto) dmonad-lib0-c7e7806/webcrypto.react-native.js000066400000000000000000000003271512504553500211110ustar00rootroot00000000000000// @ts-ignore import webcrypto from 'isomorphic-webcrypto/src/react-native' webcrypto.ensureSecure() export const subtle = webcrypto.subtle export const getRandomValues = webcrypto.getRandomValues.bind(webcrypto) dmonad-lib0-c7e7806/websocket.js000066400000000000000000000103011512504553500164710ustar00rootroot00000000000000/* eslint-env browser */ /** * Tiny websocket connection handler. * * Implements exponential backoff reconnects, ping/pong, and a nice event system using [lib0/observable]. * * @module websocket */ import { Observable } from './observable.js' import * as time from './time.js' import * as math from './math.js' const reconnectTimeoutBase = 1200 const maxReconnectTimeout = 2500 // @todo - this should depend on awareness.outdatedTime const messageReconnectTimeout = 30000 /** * @param {WebsocketClient} wsclient */ const setupWS = (wsclient) => { if (wsclient.shouldConnect && wsclient.ws === null) { const websocket = new WebSocket(wsclient.url) const binaryType = wsclient.binaryType /** * @type {any} */ let pingTimeout = null if (binaryType) { websocket.binaryType = binaryType } wsclient.ws = websocket wsclient.connecting = true wsclient.connected = false websocket.onmessage = event => { wsclient.lastMessageReceived = time.getUnixTime() const data = event.data const message = typeof data === 'string' ? JSON.parse(data) : data if (message && message.type === 'pong') { clearTimeout(pingTimeout) pingTimeout = setTimeout(sendPing, messageReconnectTimeout / 2) } wsclient.emit('message', [message, wsclient]) } /** * @param {any} error */ const onclose = error => { if (wsclient.ws !== null) { wsclient.ws = null wsclient.connecting = false if (wsclient.connected) { wsclient.connected = false wsclient.emit('disconnect', [{ type: 'disconnect', error }, wsclient]) } else { wsclient.unsuccessfulReconnects++ } // Start with no reconnect timeout and increase timeout by // log10(wsUnsuccessfulReconnects). // The idea is to increase reconnect timeout slowly and have no reconnect // timeout at the beginning (log(1) = 0) setTimeout(setupWS, math.min(math.log10(wsclient.unsuccessfulReconnects + 1) * reconnectTimeoutBase, maxReconnectTimeout), wsclient) } clearTimeout(pingTimeout) } const sendPing = () => { if (wsclient.ws === websocket) { wsclient.send({ type: 'ping' }) } } websocket.onclose = () => onclose(null) websocket.onerror = error => onclose(error) websocket.onopen = () => { wsclient.lastMessageReceived = time.getUnixTime() wsclient.connecting = false wsclient.connected = true wsclient.unsuccessfulReconnects = 0 wsclient.emit('connect', [{ type: 'connect' }, wsclient]) // set ping pingTimeout = setTimeout(sendPing, messageReconnectTimeout / 2) } } } /** * @deprecated * @extends Observable */ export class WebsocketClient extends Observable { /** * @param {string} url * @param {object} opts * @param {'arraybuffer' | 'blob' | null} [opts.binaryType] Set `ws.binaryType` */ constructor (url, { binaryType } = {}) { super() this.url = url /** * @type {WebSocket?} */ this.ws = null this.binaryType = binaryType || null this.connected = false this.connecting = false this.unsuccessfulReconnects = 0 this.lastMessageReceived = 0 /** * Whether to connect to other peers or not * @type {boolean} */ this.shouldConnect = true this._checkInterval = setInterval(() => { if (this.connected && messageReconnectTimeout < time.getUnixTime() - this.lastMessageReceived) { // no message received in a long time - not even your own awareness // updates (which are updated every 15 seconds) /** @type {WebSocket} */ (this.ws).close() } }, messageReconnectTimeout / 2) setupWS(this) } /** * @param {any} message */ send (message) { if (this.ws) { this.ws.send(JSON.stringify(message)) } } destroy () { clearInterval(this._checkInterval) this.disconnect() super.destroy() } disconnect () { this.shouldConnect = false if (this.ws !== null) { this.ws.close() } } connect () { this.shouldConnect = true if (!this.connected && this.ws === null) { setupWS(this) } } }