├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── question.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── tests.yml ├── .gitignore ├── .mailmap ├── .metadocrc ├── .prettierignore ├── .prettierrc ├── AUTHORS ├── CHANGELOG.md ├── LICENSE ├── README.md ├── common.js ├── common.mjs ├── doc ├── footer.md └── header.md ├── lib ├── array.js ├── auth.js ├── btree.js ├── cache.js ├── callbacks.js ├── case.js ├── data.js ├── enum.js ├── events.js ├── flags.js ├── fp.js ├── fs.js ├── id.js ├── int64.js ├── iterator.js ├── math.js ├── mp.js ├── network.js ├── numeric.js ├── oop.js ├── pool.js ├── sort.js ├── stream.js ├── strings.js ├── time.js ├── uint64.js ├── unicode-categories.js ├── units.js └── utilities.js ├── package-lock.json ├── package.json ├── sequential-test └── time.js ├── test ├── array.js ├── auth.js ├── btree.js ├── cache.js ├── callbacks.js ├── data.js ├── enum.js ├── events.js ├── fixtures │ ├── callerFilepath.js │ ├── empty │ └── greetings ├── flags.js ├── fp.js ├── fs.js ├── id.js ├── int64.js ├── iterator.js ├── math.js ├── mp.js ├── network.js ├── oop.js ├── pool.js ├── sort.js ├── stream.js ├── strings.js ├── uint64.js ├── units.js └── utilities.js └── tools ├── esmodules-export-gen.js └── unicode-category-parser.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/env"] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | charset = utf-8 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [{*.js,*.mjs,*.ts,*.json,*.yml}] 11 | indent_size = 2 12 | indent_style = space 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | dist 4 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["metarhia", "plugin:prettier/recommended"], 3 | "env": { 4 | "browser": true, 5 | "es6": true, 6 | "node": true 7 | }, 8 | "parserOptions": { 9 | "ecmaVersion": "latest" 10 | }, 11 | "rules": { 12 | "no-invalid-this": "off", 13 | "no-unused-private-class-members": "off" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * -text 2 | /lib/utils/unicode-categories.js linguist-generated=true 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: tshemsedinov 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | ## Describe the bug 10 | 11 | A clear and concise description of what the bug is. 12 | 13 | ## To Reproduce 14 | 15 | Steps to reproduce the behavior: usage example or test. 16 | 17 | ## Expected behavior 18 | 19 | A clear and concise description of what you expected. 20 | 21 | ## Screenshots 22 | 23 | If applicable, add screenshots to help explain your problem. 24 | 25 | ## Desktop (please complete the following information): 26 | 27 | - OS: [e.g. Fedora 30 64-bit] 28 | - Node.js version [e.g. 14.15.1] 29 | 30 | ##Additional context 31 | 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | ## Is your feature request related to a problem? Please describe. 10 | 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | ## Describe the solution you'd like 14 | 15 | A clear and concise description of what you want to happen. 16 | 17 | ## Describe alternatives you've considered 18 | 19 | A clear and concise description of any alternative solutions or features you've considered. 20 | 21 | ## Additional context 22 | 23 | Add any other context or screenshots about the feature request here. 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Please don't open an issue to ask questions 4 | --- 5 | 6 | Issues on GitHub are intended to be related to problems and feature requests 7 | so we recommend not using this medium to ask them here grin. Thanks for 8 | understanding! 9 | 10 | If you have a question, please check out our support groups and channels for 11 | developers community: 12 | 13 | Telegram: 14 | 15 | - Channel for Metarhia community: https://t.me/metarhia 16 | - Group for Metarhia technology stack community: https://t.me/metaserverless 17 | - Group for NodeUA community: https://t.me/nodeua 18 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | - [ ] tests and linter show no problems (`npm t`) 8 | - [ ] tests are added/updated for bug fixes and new features 9 | - [ ] code is properly formatted (`npm run fmt`) 10 | - [ ] description of changes is added in CHANGELOG.md 11 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | 9 | defaults: 10 | run: 11 | shell: bash 12 | 13 | jobs: 14 | test: 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | matrix: 18 | os: 19 | - ubuntu-latest 20 | - macos-latest 21 | - windows-latest 22 | node: 23 | - 14 24 | - 16 25 | - 18 26 | - 19 27 | - 20 28 | steps: 29 | - uses: actions/checkout@v2 30 | - name: Setup Node.js ${{ matrix.node }} 31 | uses: actions/setup-node@v1 32 | with: 33 | node-version: ${{ matrix.node }} 34 | - uses: actions/cache@v2 35 | with: 36 | path: ~/.npm 37 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 38 | restore-keys: | 39 | ${{ runner.os }}-node- 40 | - if: ${{ runner.os == 'Windows' }} 41 | run: npm config set script-shell "C:\\Program Files\\git\\bin\\bash.exe" 42 | - run: npm ci 43 | - run: npm test 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .DS_Store 4 | .nyc_output 5 | coverage 6 | dist 7 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Timur Shemsedinov 2 | Dmytro Nechai 3 | Alexey Orlenko 4 | Vlad Dziuba 5 | Ivan Prodaiko 6 | Ivan Timoshenko 7 | Ivan Timoshenko 8 | Ivan Honchar 9 | Mykola Bilochub 10 | Vitaliy Semenchenko 11 | Denys Otrishko 12 | Julia Gerasymenko 13 | Nikita Machehin 14 | Diana Boloteniuk 15 | -------------------------------------------------------------------------------- /.metadocrc: -------------------------------------------------------------------------------- 1 | { 2 | "headerFile": "doc/header.md", 3 | "footerFile": "doc/footer.md", 4 | "minHeaderLevel": 2, 5 | "removeInterface": true, 6 | "files": ["common.js"], 7 | "customLinks": { 8 | "Object.fromEntries()": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries", 9 | "Buffer": "https://nodejs.org/api/buffer.html#buffer_class_buffer", 10 | "EventEmitter": "https://nodejs.org/api/events.html#events_class_eventemitter", 11 | "Writable": "https://nodejs.org/api/stream.html#stream_class_stream_writable" 12 | }, 13 | "outputFile": "README.md" 14 | } 15 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | lib/unicode-categories.js 2 | README.md 3 | coverage/ 4 | dist 5 | package.json 6 | package-lock.json 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "overrides": [ 5 | { 6 | "files": ["**/.*rc", "**/*.json"], 7 | "options": { "parser": "json" } 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Timur Shemsedinov 2 | Dmytro Nechai 3 | Alexey Orlenko 4 | Vlad Dziuba 5 | Ivan Prodaiko 6 | Ivan Timoshenko 7 | Ivan Honchar 8 | Mykola Bilochub 9 | Vitaliy Semenchenko 10 | Denys Otrishko 11 | Julia Gerasymenko 12 | Nikita Machehin 13 | Diana Boloteniuk 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to 7 | [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 8 | 9 | ## [Unreleased][unreleased] 10 | 11 | ## [2.2.2][] - 2023-07-17 12 | 13 | - Update dependencies and security issues 14 | - Fixed code style, badges and .github templates 15 | 16 | ## [2.2.1][] - 2023-05-10 17 | 18 | ### Changed 19 | 20 | - Update dependencies 21 | - Drop node.js 8, 10, 12, support 16, 18, 19, 20 to CI 22 | - Apply new code formatting from prettier and fix eslint rules 23 | 24 | ### Added 25 | 26 | - `Iterator.indices()` to simplify iterating over indices of an array. 27 | - `Iterator.last()` to return last value of this iterator or provided 28 | `defaultValue`. 29 | - `Iterator.firstNonNullable()` to find first non-nullable value or 30 | provided `defaultValue`. 31 | 32 | ### Fixed 33 | 34 | - `common.shuffle()` having non-uniform distribution. 35 | 36 | ## [2.2.0][] - 2020-07-10 37 | 38 | ### Added 39 | 40 | - A way to limit the internal buffer size of `MemoryWritable` stream. 41 | - `common.mkdirpPromise()` function. 42 | - `Iterator#apply()` and `Iterator#chainApply()` to improve iterator 43 | interaction with chained calls. 44 | - `captureMaxStack()` utility to get maximum available stack trace. 45 | - Table of contents to documentation. 46 | - Get random element from array: `sample(array)`. 47 | - ECMAScript Modules named exports support. 48 | - `Iterator#min()`, `Iterator#max()`, and `Iterator#findCompare()` to 49 | simplify consumption of iterator in common use-cases 50 | (finding minimum, maximum, or using a custom condition appropriately). 51 | - `Iterator#partition()` to allow splitting iterator values into 52 | multiple arrays. 53 | - `Iterator.zip()` - static method for zipping iterators. 54 | - `Iterator#groupBy()` to group iterator value into Map by 55 | specific keys. 56 | 57 | ### Changed 58 | 59 | - `cryptoPrefetcher()` to throw when `bufSize` is not a multiple of 60 | `valueSize`. 61 | - `MemoryWritable` internal buffer size is now limited to 8 MB by default. 62 | - Signature of `callerFilepath()` to allow passing `RegExp` as depth to be used 63 | for filtering of stack frames. 64 | - Return value of `cryptoPrefetcher()` now implements the Iterable interface. 65 | 66 | ### Fixed 67 | 68 | - `common.subst()` behavior for @.value@ variables. 69 | - `common.callerFilepath()` working incorrectly on paths with colon in them. 70 | 71 | ## [2.1.0][] - 2019-06-18 72 | 73 | ### Added 74 | 75 | - `rmRecursive()` and `rmRecursivePromise()` functions. 76 | - `Iterator#filterMap()` to enable more efficient filter+map. It will only 77 | return values from mapper function that do not match the passed 78 | filterValue (`undefined` by default). 79 | - `Iterator#skipWhile()` to enable skipping elements while predicate holds. 80 | - `MemoryWritable` stream implementation. 81 | 82 | ### Fixed 83 | 84 | - Documentation by marking some methods as private and regenerating with a newer 85 | version of `@metarhia/doc` 86 | ([changelog](https://github.com/metarhia/metadoc/blob/master/CHANGELOG.md#056---2019-05-31)). 87 | 88 | ## [2.0.0][] - 2019-04-26 89 | 90 | ### Added 91 | 92 | - This CHANGELOG.md file. 93 | - `Iterator#collectWith()` now returns the provided object. 94 | - `Iterator#toObject()` to collect iterable into an Object similar to 95 | [`Object.fromEntries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries). 96 | - `common.iterEntries()`, `common.iterKeys()`, `common.iterValues()` utility 97 | methods. 98 | 99 | ### Changed 100 | 101 | - Expose `AuthenticationStrength`'s `compliance` number property instead of 102 | `strength` string. 103 | - Replaced ES5-style classes and inheritance with ES6 classes for `Cache` and 104 | `EnhancedEmitter`. 105 | - Signature of `merger()` in `mergeObjects()` to also contain the merging key. 106 | 107 | ### Removed 108 | 109 | - Dropped support for Node.js 6. 110 | - Outdated `inherits()` method (in favor of `util.inherits()` available in 111 | Node.js). 112 | - Multiple deprecated functions: 113 | - `common.ip2int()` - replace with `common.ipToInt()` 114 | - `common.cb()` - replace with `common.once()` 115 | - `common.extractCallback()` - replace with `common.unsafeCallback()` 116 | - `common.cbUnsafe()` - replace with `common.unsafeCallback()` 117 | - `common.cbExtract()` - replace with `common.safeCallback()` 118 | - `common.crcSID()` - replace with `common.crcToken()` 119 | - `common.generateSID()` - replace with `common.generateToken()` 120 | - `common.validateSID()` - replace with `common.validateToken()` 121 | - Functions that can be replaced with `util.deprecate()` available in Node.js: 122 | - `common.deprecate()` 123 | - `common.alias()` 124 | 125 | ### Fixed 126 | 127 | - Functions `common.clone()`, `common.deleteByPath()`, and 128 | `common.mergeObjects()` throwing when used on objects without prototype. 129 | 130 | ## [1.5.0][] - 2019-04-12 131 | 132 | ### Added 133 | 134 | - `Symbol.toStringTag` property to `Iterator`. 135 | - All of the missing methods' documentation. 136 | 137 | ## [1.4.2][] - 2019-03-28 138 | 139 | ### Fixed 140 | 141 | - Issue with circular dependency on `metasync` package. 142 | 143 | ## [1.4.1][] - 2019-03-27 144 | 145 | ### Fixed 146 | 147 | - Unsuccessful attempt at fixing an issue with circular dependency on 148 | `metasync` package. 149 | 150 | ## [1.4.0][] - 2019-03-27 151 | 152 | ### Added 153 | 154 | - Password authentication test functions accounting for password topologies and 155 | popular passwords. 156 | - Recursive `rmdir` implementation `rmdirp()`. 157 | 158 | ### Fixed 159 | 160 | - `Iterator#includes()` working incorrectly for non-number values. 161 | 162 | ## [1.3.1][] - 2019-03-26 163 | 164 | ### Fixed 165 | 166 | - Browser build. 167 | 168 | ## [1.3.0][] - 2019-03-22 169 | 170 | ### Added 171 | 172 | - Recursive `mkdir` implementation `mkdirp()`. 173 | - Implementation of `pushSame()` for arrays. 174 | - Simple `Pool` implementation. 175 | 176 | ### Fixed 177 | 178 | - `duplicate()` throwing when used with objects that have no prototype. 179 | - Deprecation warnings when using `duplicate()` on `Buffer`s. 180 | 181 | ## [1.2.1][] - 2018-12-11 182 | 183 | ### Fixed 184 | 185 | - Iterating over inherited properties in `mergeObjects()`. 186 | - `duplicate()` and `clone()` regression. 187 | 188 | ## [1.2.0][] - 2018-12-07 189 | 190 | ### Added 191 | 192 | - `Iterator.range()` method. 193 | - `crcToken()`, `generateToken()`, and `validateToken()` functions. 194 | 195 | ### Deprecated 196 | 197 | - `crcSID()` function in favor of `crcToken()`. 198 | - `generateSID()` function in favor of `generateToken()`. 199 | - `validateSID()` function in favor of `validateToken()`. 200 | 201 | ### Fixed 202 | 203 | - Argument name collision in `validateHash()`. 204 | 205 | ## [1.1.1][] - 2018-11-23 206 | 207 | ### Fixed 208 | 209 | - `Int64` Postgres serialization. 210 | 211 | ## [1.1.0][] - 2018-11-23 212 | 213 | ### Added 214 | 215 | - JSON and Postgres serialization to Uint64 and Int64 via methods `toJSON()` 216 | and `toPostgres()`. 217 | - Ability to construct `Uint64` from `Int64`. 218 | 219 | ## [1.0.0][] - 2018-11-21 220 | 221 | ### Added 222 | 223 | - The first stable version of the `@metarhia/common` package. 224 | 225 | [unreleased]: https://github.com/metarhia/common/compare/v2.2.2...HEAD 226 | [2.2.2]: https://github.com/metarhia/common/compare/v2.2.1...v2.2.2 227 | [2.2.1]: https://github.com/metarhia/common/compare/v2.2.0...v2.2.1 228 | [2.2.0]: https://github.com/metarhia/common/compare/v2.1.0...v2.2.0 229 | [2.1.0]: https://github.com/metarhia/common/compare/v2.0.0...v2.1.0 230 | [2.0.0]: https://github.com/metarhia/common/compare/v1.5.0...v2.0.0 231 | [1.5.0]: https://github.com/metarhia/common/compare/v1.4.2...v1.5.0 232 | [1.4.2]: https://github.com/metarhia/common/compare/v1.4.1...v1.4.2 233 | [1.4.1]: https://github.com/metarhia/common/compare/v1.4.0...v1.4.1 234 | [1.4.0]: https://github.com/metarhia/common/compare/v1.3.1...v1.4.0 235 | [1.3.1]: https://github.com/metarhia/common/compare/v1.3.0...v1.3.1 236 | [1.3.0]: https://github.com/metarhia/common/compare/v1.2.1...v1.3.0 237 | [1.2.1]: https://github.com/metarhia/common/compare/v1.2.0...v1.2.1 238 | [1.2.0]: https://github.com/metarhia/common/compare/v1.1.1...v1.2.0 239 | [1.1.1]: https://github.com/metarhia/common/compare/v1.1.0...v1.1.1 240 | [1.1.0]: https://github.com/metarhia/common/compare/v1.0.0...v1.1.0 241 | [1.0.0]: https://github.com/metarhia/common/releases/tag/v1.0.0 242 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2023 Metarhia contributors (full list in AUTHORS file) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /common.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const submodules = [ 4 | 'array', // Arrays manipulations 5 | 'auth', // Validation of data for authentication/authorization 6 | 'btree', // B-Tree for indexes in DB 7 | 'cache', // Cache (enhanced Map) 8 | 'callbacks', // Callback utilities 9 | 'data', // Data structures manipulations 10 | 'enum', // Enumerated type 11 | 'events', // Events and emitter 12 | 'flags', // Flags data type 13 | 'fp', // Functional programming 14 | 'fs', // File System 15 | 'id', // Keys and identifiers 16 | 'int64', // Int64 17 | 'iterator', // Iterator 18 | 'math', // Math common function 19 | 'mp', // Metaprogramming 20 | 'network', // Network utilities 21 | 'oop', // Object-oriented programming 22 | 'pool', // Object pool 23 | 'sort', // Sort compare functions 24 | 'stream', // Stream utilities 25 | 'strings', // Strings utilities 26 | 'time', // Date and Time functions 27 | 'uint64', // Uint64 28 | 'units', // Units conversion 29 | 'utilities', // Common utilities 30 | ].map((path) => require('./lib/' + path)); 31 | 32 | module.exports = Object.assign({}, ...submodules); 33 | -------------------------------------------------------------------------------- /common.mjs: -------------------------------------------------------------------------------- 1 | // This is an automaticaly generated file. DO NOT MODIFY MANUALLY. 2 | import common from './common.js'; 3 | 4 | export default common; 5 | 6 | export const splitAt = common.splitAt; 7 | export const shuffle = common.shuffle; 8 | export const sample = common.sample; 9 | export const range = common.range; 10 | export const sequence = common.sequence; 11 | export const last = common.last; 12 | export const pushSame = common.pushSame; 13 | export const checkLogin = common.checkLogin; 14 | export const checkPassword = common.checkPassword; 15 | export const checkLoginPassword = common.checkLoginPassword; 16 | export const BTree = common.BTree; 17 | export const cache = common.cache; 18 | export const Cache = common.Cache; 19 | export const falseness = common.falseness; 20 | export const trueness = common.trueness; 21 | export const emptiness = common.emptiness; 22 | export const nop = common.nop; 23 | export const noop = common.noop; 24 | export const once = common.once; 25 | export const unsafeCallback = common.unsafeCallback; 26 | export const safeCallback = common.safeCallback; 27 | export const requiredCallback = common.requiredCallback; 28 | export const onceCallback = common.onceCallback; 29 | export const safeFunction = common.safeFunction; 30 | export const unsafeFunction = common.unsafeFunction; 31 | export const id = common.id; 32 | export const asyncId = common.asyncId; 33 | export const isScalar = common.isScalar; 34 | export const copy = common.copy; 35 | export const clone = common.clone; 36 | export const duplicate = common.duplicate; 37 | export const getByPath = common.getByPath; 38 | export const setByPath = common.setByPath; 39 | export const deleteByPath = common.deleteByPath; 40 | export const merge = common.merge; 41 | export const mergeObjects = common.mergeObjects; 42 | export const Enum = common.Enum; 43 | export const forwardEvents = common.forwardEvents; 44 | export const emitter = common.emitter; 45 | export const EnhancedEmitter = common.EnhancedEmitter; 46 | export const Flags = common.Flags; 47 | export const partial = common.partial; 48 | export const omap = common.omap; 49 | export const compose = common.compose; 50 | export const maybe = common.maybe; 51 | export const zip = common.zip; 52 | export const replicate = common.replicate; 53 | export const zipWith = common.zipWith; 54 | export const curryUntil = common.curryUntil; 55 | export const curryN = common.curryN; 56 | export const curryTwice = common.curryTwice; 57 | export const curry = common.curry; 58 | export const applyArgs = common.applyArgs; 59 | export const either = common.either; 60 | export const restLeft = common.restLeft; 61 | export const mkdirp = common.mkdirp; 62 | export const mkdirpPromise = common.mkdirpPromise; 63 | export const rmdirp = common.rmdirp; 64 | export const rmRecursive = common.rmRecursive; 65 | export const rmRecursivePromise = common.rmRecursivePromise; 66 | export const generateKey = common.generateKey; 67 | export const generateGUID = common.generateGUID; 68 | export const generateToken = common.generateToken; 69 | export const crcToken = common.crcToken; 70 | export const validateToken = common.validateToken; 71 | export const hash = common.hash; 72 | export const validateHash = common.validateHash; 73 | export const generateStorageKey = common.generateStorageKey; 74 | export const idToChunks = common.idToChunks; 75 | export const idToPath = common.idToPath; 76 | export const pathToId = common.pathToId; 77 | export const Int64 = common.Int64; 78 | export const Iterator = common.Iterator; 79 | export const iter = common.iter; 80 | export const iterEntries = common.iterEntries; 81 | export const iterKeys = common.iterKeys; 82 | export const iterValues = common.iterValues; 83 | export const cryptoPrefetcher = common.cryptoPrefetcher; 84 | export const random = common.random; 85 | export const cryptoRandom = common.cryptoRandom; 86 | export const methods = common.methods; 87 | export const properties = common.properties; 88 | export const ipToInt = common.ipToInt; 89 | export const localIPs = common.localIPs; 90 | export const parseHost = common.parseHost; 91 | export const override = common.override; 92 | export const mixin = common.mixin; 93 | export const Pool = common.Pool; 94 | export const sortComparePriority = common.sortComparePriority; 95 | export const sortCompareDirectories = common.sortCompareDirectories; 96 | export const sortCompareByName = common.sortCompareByName; 97 | export const MemoryWritable = common.MemoryWritable; 98 | export const subst = common.subst; 99 | export const htmlEscape = common.htmlEscape; 100 | export const fileExt = common.fileExt; 101 | export const removeExt = common.removeExt; 102 | export const spinalToCamel = common.spinalToCamel; 103 | export const escapeRegExp = common.escapeRegExp; 104 | export const newEscapedRegExp = common.newEscapedRegExp; 105 | export const addTrailingSlash = common.addTrailingSlash; 106 | export const stripTrailingSlash = common.stripTrailingSlash; 107 | export const dirname = common.dirname; 108 | export const capitalize = common.capitalize; 109 | export const between = common.between; 110 | export const removeBOM = common.removeBOM; 111 | export const arrayRegExp = common.arrayRegExp; 112 | export const section = common.section; 113 | export const rsection = common.rsection; 114 | export const split = common.split; 115 | export const rsplit = common.rsplit; 116 | export const normalizeEmail = common.normalizeEmail; 117 | export const ALPHA_UPPER = common.ALPHA_UPPER; 118 | export const ALPHA_LOWER = common.ALPHA_LOWER; 119 | export const ALPHA = common.ALPHA; 120 | export const DIGIT = common.DIGIT; 121 | export const ALPHA_DIGIT = common.ALPHA_DIGIT; 122 | export const isTimeEqual = common.isTimeEqual; 123 | export const nowDate = common.nowDate; 124 | export const nowDateTime = common.nowDateTime; 125 | export const Uint64 = common.Uint64; 126 | export const duration = common.duration; 127 | export const durationToString = common.durationToString; 128 | export const bytesToSize = common.bytesToSize; 129 | export const sizeToBytes = common.sizeToBytes; 130 | export const safe = common.safe; 131 | export const captureMaxStack = common.captureMaxStack; 132 | export const callerFilename = common.callerFilename; 133 | export const callerFilepath = common.callerFilepath; 134 | -------------------------------------------------------------------------------- /doc/footer.md: -------------------------------------------------------------------------------- 1 | ## Contributors 2 | 3 | See github for full [contributors list](https://github.com/metarhia/common/graphs/contributors) 4 | -------------------------------------------------------------------------------- /doc/header.md: -------------------------------------------------------------------------------- 1 | # Metarhia Common Library 2 | 3 | ![CI Status Badge](https://github.com/metarhia/common/workflows/Tests/badge.svg?branch=master) 4 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/57f219ad89e64c848685a93f5f2f14c2)](https://www.codacy.com/app/metarhia/common) 5 | [![NPM Version](https://badge.fury.io/js/%40metarhia%2Fcommon.svg)](https://badge.fury.io/js/%40metarhia%2Fcommon) 6 | [![NPM Downloads/Month](https://img.shields.io/npm/dm/@metarhia/common.svg)](https://www.npmjs.com/package/@metarhia/common) 7 | [![NPM Downloads](https://img.shields.io/npm/dt/@metarhia/common.svg)](https://www.npmjs.com/package/@metarhia/common) 8 | 9 | Namespace: `api.common` in [Impress Application Server](https://github.com/metarhia/Impress) 10 | 11 | ## Installation 12 | 13 | ```bash 14 | $ npm install @metarhia/common 15 | ``` 16 | 17 | ## API 18 | -------------------------------------------------------------------------------- /lib/array.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Split array into two parts 4 | // index - , index defining end of first part and start of second 5 | // array - , to be split 6 | // Returns: , tuple with two parts of the array 7 | const splitAt = (index, array) => { 8 | const part1 = array.slice(0, index); 9 | const part2 = array.slice(index, array.length); 10 | return [part1, part2]; 11 | }; 12 | 13 | // Shuffle an array 14 | // arr - 15 | // Returns: 16 | const shuffle = (arr) => { 17 | // Based on the algorithm described here: 18 | // https://en.wikipedia.org/wiki/Fisher-Yates_shuffle 19 | for (let i = arr.length - 1; i > 0; i--) { 20 | const j = Math.floor(Math.random() * (i + 1)); 21 | [arr[i], arr[j]] = [arr[j], arr[i]]; 22 | } 23 | return arr; 24 | }; 25 | 26 | // Random element from array 27 | // arr - 28 | // Returns: 29 | const sample = (arr) => arr[Math.floor(Math.random() * arr.length)]; 30 | 31 | // Generate int array from given range 32 | // from - , range start 33 | // to - , range end 34 | // Returns: 35 | // 36 | // Example: range(1, 5) 37 | // Result: [1, 2, 3, 4, 5] 38 | const range = (from, to) => { 39 | if (to < from) return []; 40 | const len = to - from + 1; 41 | const range = new Array(len); 42 | for (let i = from; i <= to; i++) { 43 | range[i - from] = i; 44 | } 45 | return range; 46 | }; 47 | 48 | // Generate int array from sequence syntax 49 | // Signature: seq[, max] 50 | // seq - 51 | // max - , (optional), max 52 | // Returns: 53 | // 54 | // Example: list: sequence([81, 82, 83]) 55 | // Result: [81, 82, 83] 56 | // Example: range from..to: sequence([81,,83]) = [81, 82, 83] 57 | // Result: [81, 82, 83] 58 | // Example: range from..count: sequence([81, [3]]) = [81, 82, 83] 59 | // Result: [81, 82, 83] 60 | // Example: range from..max-to: sequence([81, [-2]], 5) = [81, 82, 83] 61 | // Result: [81, 82, 83] 62 | const sequence = (seq, max) => { 63 | const from = seq[0]; 64 | let to = seq[1]; 65 | let res = seq; 66 | if (Array.isArray(to)) { 67 | const count = to[0] < 0 ? max + to[0] : to[0]; 68 | res = range(from, from + count - 1); 69 | } else if (!to) { 70 | to = seq[2]; 71 | res = range(from, to); 72 | } 73 | return res; 74 | }; 75 | 76 | // Get last element of array 77 | // arr - 78 | // Returns: , element 79 | const last = (arr) => arr[arr.length - 1]; 80 | 81 | // Push single value multiple times 82 | // arr - 83 | // n - 84 | // value - 85 | // Returns: , new value of arr.length 86 | const pushSame = (arr, n, value) => { 87 | if (n <= 0) return arr.length; 88 | const from = arr.length; 89 | arr.length += n; 90 | for (let i = from; i < arr.length; i++) { 91 | arr[i] = value; 92 | } 93 | return arr.length; 94 | }; 95 | 96 | module.exports = { 97 | splitAt, 98 | shuffle, 99 | sample, 100 | range, 101 | sequence, 102 | last, 103 | pushSame, 104 | }; 105 | -------------------------------------------------------------------------------- /lib/auth.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { iter } = require('./iterator'); 4 | const unicodeCategories = require('./unicode-categories'); 5 | 6 | const NUMBERS_RANGE = [[49, 57]]; 7 | const SPECIAL_CHARS_RANGE = [ 8 | [32, 47], 9 | [58, 64], 10 | [91, 96], 11 | [123, 126], 12 | ]; 13 | // https://www.owasp.org/index.php/Password_special_characters 14 | const UTF16_SINGLE_UNIT = 1 << 16; 15 | 16 | const unicodeRangesIncludes = (ranges, codePoint) => { 17 | let left = 0; 18 | let right = ranges.length - 1; 19 | while (left <= right) { 20 | const mid = (left + right) >>> 1; 21 | const value = ranges[mid]; 22 | if (typeof value === 'number') { 23 | if (codePoint > value) left = mid + 1; 24 | else if (codePoint < value) right = mid - 1; 25 | else return true; 26 | } else if (codePoint > value[1]) { 27 | left = mid + 1; 28 | } else if (codePoint < value[0]) { 29 | right = mid - 1; 30 | } else { 31 | return true; 32 | } 33 | } 34 | return false; 35 | }; 36 | 37 | const stringIncludesChars = (str, ranges, charsNumber) => { 38 | let number = 0; 39 | for (let index = 0; index <= str.length - (charsNumber - number); index++) { 40 | const codePoint = str.codePointAt(index); 41 | if (codePoint >= UTF16_SINGLE_UNIT) index++; 42 | if (unicodeRangesIncludes(ranges, codePoint) && ++number === charsNumber) { 43 | return true; 44 | } 45 | } 46 | return false; 47 | }; 48 | 49 | const isNotTopology = (str, topologies) => { 50 | let topology = ''; 51 | 52 | for (let index = 0; index < str.length; index++) { 53 | const codePoint = str.codePointAt(index); 54 | if (codePoint >= UTF16_SINGLE_UNIT) index++; 55 | 56 | if (unicodeRangesIncludes(unicodeCategories.Lu, codePoint)) { 57 | topology += 'u'; 58 | } else if (unicodeRangesIncludes(unicodeCategories.Ll, codePoint)) { 59 | topology += 'l'; 60 | } else if (unicodeRangesIncludes(NUMBERS_RANGE, codePoint)) { 61 | topology += 'd'; 62 | } else { 63 | topology += 's'; 64 | } 65 | } 66 | 67 | return !iter(topologies).includes(topology); 68 | }; 69 | 70 | const passwordTests = { 71 | MIN_LENGTH: { 72 | test: (password, options) => password.length >= options.minLength, 73 | hint: (options) => ({ name: 'MIN_LENGTH', minLength: options.minLength }), 74 | options: { minLength: 10 }, 75 | }, 76 | MAX_LENGTH: { 77 | test: (password, options) => password.length <= options.maxLength, 78 | hint: (options) => ({ name: 'MAX_LENGTH', maxLength: options.maxLength }), 79 | options: { maxLength: 128 }, 80 | }, 81 | MIN_PASSPHRASE_LENGTH: { 82 | test: (password, options) => password.length >= options.minLength, 83 | hint: (options) => ({ 84 | name: 'MIN_PASSPHRASE_LENGTH', 85 | minLength: options.minLength, 86 | }), 87 | options: { minLength: 20 }, 88 | }, 89 | MAX_REPEATED_CHARS: { 90 | test: (password, options) => { 91 | const regexp = new RegExp(`(.)\\1{${options.number},}`); 92 | return !regexp.test(password); 93 | }, 94 | hint: (options) => ({ name: 'MAX_REPEATED_CHARS', number: options.number }), 95 | options: { number: 2 }, 96 | }, 97 | MIN_LOWERCASE_CHARS: { 98 | test: (password, option) => 99 | stringIncludesChars(password, unicodeCategories.Ll, option.number), 100 | hint: (options) => ({ 101 | name: 'MIN_LOWERCASE_CHARS', 102 | number: options.number, 103 | }), 104 | options: { number: 1 }, 105 | }, 106 | MIN_UPPERCASE_CHARS: { 107 | test: (password, options) => 108 | stringIncludesChars(password, unicodeCategories.Lu, options.number), 109 | hint: (options) => ({ 110 | name: 'MIN_UPPERCASE_CHARS', 111 | number: options.number, 112 | }), 113 | options: { number: 1 }, 114 | }, 115 | MIN_NUMBERS: { 116 | test: (password, options) => 117 | stringIncludesChars(password, NUMBERS_RANGE, options.number), 118 | hint: (options) => ({ name: 'MIN_NUMBERS', number: options.number }), 119 | options: { number: 1 }, 120 | }, 121 | MIN_SPECIAL_CHARS: { 122 | test: (password, options) => 123 | stringIncludesChars(password, SPECIAL_CHARS_RANGE, options.number), 124 | hint: (options) => ({ name: 'MIN_SPECIAL_CHARS', number: options.number }), 125 | options: { number: 1 }, 126 | }, 127 | FOUND_TOPOLOGY: { 128 | test: (password, options) => isNotTopology(password, options.topologies), 129 | hint: () => ({ name: 'FOUND_TOPOLOGY' }), 130 | }, 131 | POPULAR_PASSWORD: { 132 | test: (password, options) => 133 | !iter(options.popularPasswords).includes(password), 134 | hint: () => ({ name: 'POPULAR_PASSWORD' }), 135 | }, 136 | }; 137 | 138 | const loginTests = { 139 | MIN_LENGTH: { 140 | test: (login, options) => login.length >= options.minLength, 141 | hint: (options) => ({ name: 'MIN_LENGTH', minLength: options.minLength }), 142 | options: { minLength: 6 }, 143 | }, 144 | MAX_LENGTH: { 145 | test: (login, options) => login.length <= options.maxLength, 146 | hint: (options) => ({ name: 'MAX_LENGTH', maxLength: options.maxLength }), 147 | options: { maxLength: 50 }, 148 | }, 149 | IS_EMAIL: { 150 | test: (login) => { 151 | const EMAIL_REGEXP = new RegExp( 152 | "^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(" + 153 | '?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$', 154 | ); 155 | const MAX_DOMAIN_LENGTH = 255; 156 | const MAX_LOCAL_PART_LENGTH = 64; 157 | 158 | if (login.includes('@')) { 159 | const [localPart, domain] = login.split('@'); 160 | return ( 161 | domain.length <= MAX_DOMAIN_LENGTH && 162 | localPart.length <= MAX_LOCAL_PART_LENGTH && 163 | EMAIL_REGEXP.test(login) 164 | ); 165 | } 166 | 167 | return false; 168 | }, 169 | hint: () => ({ name: 'IS_EMAIL' }), 170 | }, 171 | }; 172 | 173 | const loginPasswordTests = { 174 | LOGIN_INCLUDES_PASSWORD: { 175 | test: (login, password) => !login.includes(password), 176 | hint: () => ({ name: 'LOGIN_INCLUDES_PASSWORD' }), 177 | }, 178 | PASSWORD_INCLUDES_LOGIN: { 179 | test: (login, password) => !password.includes(login), 180 | hint: () => ({ name: 'PASSWORD_INCLUDES_LOGIN' }), 181 | }, 182 | }; 183 | 184 | class AuthenticationStrength { 185 | // AuthenticationStrength constructor 186 | // valid - 187 | // hints - 188 | // required - 189 | // optional - 190 | // compliance - , ratio of passed optional tests 191 | // to all optional tests 192 | constructor(valid, hints, compliance) { 193 | this.valid = valid; 194 | this.hints = hints; 195 | this.compliance = compliance; 196 | } 197 | } 198 | 199 | // Function that checks the arguments on a test suite 200 | // Signature: tests, required, optional, ...testArgs 201 | // tests - , of password/login tests 202 | // required - , required tests configs 203 | // optional - , optional tests configs 204 | // testArgs - , [password] / [login] / [login, password] 205 | // Returns: 206 | const makeTest = (tests, required, optional, ...testArgs) => { 207 | const test = (testsConfig) => { 208 | const testsHints = []; 209 | testsConfig.forEach((testConfig) => { 210 | const [testName, userOptions] = 211 | typeof testConfig === 'string' 212 | ? [testConfig, {}] 213 | : [testConfig.name, testConfig]; 214 | const { test, hint, options = {} } = tests[testName]; 215 | const testOptions = { ...options, ...userOptions }; 216 | if (!test(...testArgs, testOptions)) testsHints.push(hint(testOptions)); 217 | }); 218 | return testsHints; 219 | }; 220 | 221 | const requiredHints = test(required); 222 | const optionalHints = test(optional); 223 | 224 | const valid = !requiredHints.length; 225 | const compliance = optional.length 226 | ? 1 - optionalHints.length / optional.length 227 | : 1; 228 | const hints = { required: requiredHints, optional: optionalHints }; 229 | 230 | return new AuthenticationStrength(valid, hints, compliance); 231 | }; 232 | 233 | // Function that tests the login 234 | // Signature: login, required[, optional] 235 | // login - , login to test 236 | // required - , required tests configs 237 | // optional - , optional tests configs, defalult: `[]` 238 | // Returns: 239 | const checkLogin = (login, required, optional = []) => { 240 | if (!required) { 241 | required = ['MIN_LENGTH', 'MAX_LENGTH']; 242 | } 243 | return makeTest(loginTests, required, optional, login); 244 | }; 245 | 246 | // Function that tests the password 247 | // Signature: password, required[, optional] 248 | // password - , password to test 249 | // required - , required tests configs 250 | // optional - , optional tests configs, default: `[]` 251 | // Returns: 252 | const checkPassword = (password, required, optional = []) => { 253 | if (!required) { 254 | required = ['MIN_LENGTH', 'MAX_LENGTH']; 255 | optional = [ 256 | 'MIN_NUMBERS', 257 | 'MIN_SPECIAL_CHARS', 258 | 'MIN_UPPERCASE_CHARS', 259 | 'MIN_LOWERCASE_CHARS', 260 | ]; 261 | } 262 | return makeTest(passwordTests, required, optional, password); 263 | }; 264 | 265 | // Function that tests the login with password 266 | // Signature: login, password, required[, optional] 267 | // login - , login to test 268 | // password - , password to test 269 | // required - , required tests configs 270 | // optional - , optional tests configs, default: `[]` 271 | // Returns: 272 | const checkLoginPassword = (login, password, required, optional = []) => { 273 | if (!required) { 274 | required = ['PASSWORD_INCLUDES_LOGIN', 'LOGIN_INCLUDES_PASSWORD']; 275 | } 276 | return makeTest(loginPasswordTests, required, optional, login, password); 277 | }; 278 | 279 | module.exports = { 280 | checkLogin, 281 | checkPassword, 282 | checkLoginPassword, 283 | }; 284 | -------------------------------------------------------------------------------- /lib/btree.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const DEFAULT_DEGREE = 6; // min degree of b-tree. 4 | // All vertices except the root have [degree ... 2 * degree] child nodes 5 | // And [degree - 1 ... 2 * degree - 1] + 1 "empty" elements 6 | 7 | class Element { 8 | constructor(key, data, child = null) { 9 | this.key = key; 10 | this.data = data; 11 | this.child = child; 12 | } 13 | } 14 | 15 | const empty = (child) => new Element(undefined, undefined, child); 16 | 17 | const splitNode = (parent, index) => { 18 | const node = parent[index].child; 19 | const len = node.length; 20 | const newLeftNode = node.splice(0, len / 2 - 1); // First half 21 | const mid = node.shift(); 22 | newLeftNode.push(empty(mid.child)); 23 | mid.child = newLeftNode; 24 | parent.splice(index, 0, mid); 25 | return parent; 26 | }; 27 | 28 | const isLeaf = (node) => !node[0].child; 29 | 30 | const binarySearch = (node, key) => { 31 | let start = 0; 32 | let end = node.length - 1; 33 | while (start <= end) { 34 | const i = (start + end) >> 1; 35 | const itemKey = node[i].key; 36 | if (key > itemKey) start = i + 1; 37 | else if (itemKey === undefined || key < itemKey) end = i - 1; 38 | else return [true, i]; 39 | } 40 | return [false, start]; 41 | }; 42 | 43 | function* inorderTraversal(start, finish, currNode) { 44 | const startIndex = start === undefined ? 0 : binarySearch(currNode, start)[1]; 45 | const finishIndex = 46 | finish === undefined 47 | ? currNode.length - 1 48 | : binarySearch(currNode, finish)[1]; 49 | if (isLeaf(currNode)) { 50 | for (let currIndex = startIndex; currIndex < finishIndex; currIndex++) { 51 | yield currNode[currIndex].data; 52 | } 53 | } else { 54 | for (let currIndex = startIndex; currIndex < finishIndex; currIndex++) { 55 | const currElement = currNode[currIndex]; 56 | yield* inorderTraversal(start, finish, currElement.child); 57 | yield currElement.data; 58 | } 59 | yield* inorderTraversal(start, finish, currNode[finishIndex].child); 60 | } 61 | } 62 | 63 | const joinNodes = (parent, firstNodeIndex, secondNodeIndex) => { 64 | const firstNode = parent[firstNodeIndex].child; 65 | const secondNode = parent[secondNodeIndex].child; 66 | const mid = parent.splice(firstNodeIndex, 1)[0]; 67 | mid.child = firstNode.pop().child; 68 | secondNode.splice(0, 0, ...firstNode, mid); 69 | return secondNode; 70 | }; 71 | 72 | const growChild = (parent, childIndex) => { 73 | const parentElement = parent[childIndex]; 74 | const node = parentElement.child; 75 | const minDegree = node.length; 76 | if (childIndex > 0) { 77 | // If we have left neighbor 78 | const leftNeighbor = parent[childIndex - 1].child; 79 | if (leftNeighbor.length > minDegree) { 80 | const extractedElement = leftNeighbor.splice( 81 | leftNeighbor.length - 2, 82 | 1, 83 | )[0]; 84 | const insertedElement = new Element( 85 | parent[childIndex - 1].key, 86 | parent[childIndex - 1].data, 87 | leftNeighbor[leftNeighbor.length - 1].child, 88 | ); 89 | leftNeighbor[leftNeighbor.length - 1].child = extractedElement.child; 90 | extractedElement.child = leftNeighbor; 91 | parent.splice(childIndex - 1, 1, extractedElement); 92 | node.unshift(insertedElement); 93 | return node; 94 | } 95 | } 96 | if (childIndex < parent.length - 1) { 97 | // If we have right neighbor 98 | // parent.length - 1 means that we now on the rightmost element 99 | const rightNeighbor = parent[childIndex + 1].child; 100 | if (rightNeighbor.length > minDegree) { 101 | const extractedElement = rightNeighbor.shift(); 102 | const insertedElement = new Element( 103 | parentElement.key, 104 | parentElement.data, 105 | node[node.length - 1].child, 106 | ); 107 | node[node.length - 1].child = extractedElement.child; 108 | extractedElement.child = node; 109 | parent.splice(childIndex, 1, extractedElement); 110 | node.splice(node.length - 1, 0, insertedElement); 111 | return node; 112 | } 113 | return joinNodes(parent, childIndex, childIndex + 1); 114 | } 115 | return joinNodes(parent, childIndex - 1, childIndex); 116 | }; 117 | 118 | // Get the minimal upper, or the maximum lover node for some given node 119 | // node - some given node 120 | // minDegree - value of bTree.minDegree for tree 121 | // upper - boolean: true - upperLimit, false - lowerLimit 122 | // Result: upperLimit or lowerLimit node 123 | const extractLimit = (node, minDegree, upper) => { 124 | let currNode = node; 125 | while (!isLeaf(currNode)) { 126 | const index = upper ? 0 : currNode.length - 1; 127 | let nextNode = currNode[index].child; 128 | if (nextNode.length === minDegree) { 129 | nextNode = growChild(currNode, index); 130 | } 131 | currNode = nextNode; 132 | } 133 | const index = upper ? 0 : currNode.length - 2; 134 | return currNode.splice(index, 1)[0]; 135 | }; 136 | 137 | const deleteElement = (node, elementIndex, minDegree) => { 138 | const element = node[elementIndex]; 139 | const deletedData = element.data; 140 | const leftChild = element.child; 141 | const rightChild = node[elementIndex + 1].child; 142 | if (isLeaf(node)) { 143 | return node.splice(elementIndex, 1)[0].data; 144 | } 145 | if (leftChild.length > minDegree) { 146 | const lowerLimit = extractLimit(leftChild, minDegree, false); 147 | element.key = lowerLimit.key; 148 | element.data = lowerLimit.data; 149 | } else if (rightChild.length > minDegree) { 150 | const upperLimit = extractLimit(rightChild, minDegree, true); 151 | element.key = upperLimit.key; 152 | element.data = upperLimit.data; 153 | } else { 154 | joinNodes(node, elementIndex, elementIndex + 1); 155 | rightChild.splice(minDegree - 1, 1); 156 | } 157 | return deletedData; 158 | }; 159 | 160 | class BTree { 161 | constructor(degree = DEFAULT_DEGREE) { 162 | this.root = [empty()]; 163 | this.minDegree = degree; 164 | } 165 | 166 | get(key) { 167 | let currNode = this.root; 168 | while (currNode) { 169 | const [found, i] = binarySearch(currNode, key); 170 | if (found) { 171 | return currNode[i].data; 172 | } 173 | currNode = currNode[i].child; 174 | } 175 | const result = undefined; 176 | return result; 177 | } 178 | 179 | set(key, data) { 180 | const newElement = new Element(key, data); 181 | if (this.root.length === 1) { 182 | this.root.unshift(newElement); 183 | return this; 184 | } 185 | if (this.root.length === this.minDegree * 2) { 186 | this.root = [empty(this.root)]; 187 | splitNode(this.root, 0); 188 | } 189 | let currNode = this.root; 190 | while (true) { 191 | const [found, nextNodeIndex] = binarySearch(currNode, key); 192 | if (found) { 193 | currNode[nextNodeIndex].data = data; 194 | return this; 195 | } 196 | if (isLeaf(currNode)) { 197 | currNode.splice(nextNodeIndex, 0, newElement); 198 | return this; 199 | } 200 | let nextNode = currNode[nextNodeIndex].child; 201 | if (nextNode.length === this.minDegree * 2) { 202 | splitNode(currNode, nextNodeIndex); 203 | const element = currNode[nextNodeIndex]; 204 | if (element.key === key) { 205 | element.data = data; 206 | return this; 207 | } 208 | if (element.key > key) { 209 | nextNode = element.child; 210 | } 211 | } 212 | currNode = nextNode; 213 | } 214 | } 215 | 216 | iterator(start, finish) { 217 | return inorderTraversal(start, finish, this.root); 218 | } 219 | 220 | remove(key) { 221 | let currNode = this.root; 222 | while (currNode) { 223 | const [found, index] = binarySearch(currNode, key); 224 | if (found) { 225 | const deletedData = deleteElement(currNode, index, this.minDegree); 226 | if (this.root.length === 1 && this.root[0].child) { 227 | this.root = this.root[0].child; 228 | } 229 | return deletedData; 230 | } else { 231 | let nextNode = currNode[index].child; 232 | if (nextNode && nextNode.length === this.minDegree) { 233 | nextNode = growChild(currNode, index); 234 | } 235 | currNode = nextNode; 236 | } 237 | } 238 | if (this.root.length === 1 && this.root[0].child) { 239 | this.root = this.root[0].child; 240 | } 241 | const result = undefined; 242 | return result; 243 | } 244 | } 245 | 246 | module.exports = { 247 | BTree, 248 | }; 249 | -------------------------------------------------------------------------------- /lib/cache.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const dataSize = (data) => (data && data.length ? data.length : 0); 4 | 5 | class Cache extends Map { 6 | constructor() { 7 | super(); 8 | this.allocated = 0; 9 | } 10 | 11 | // Add key-value pair to cache 12 | // key - , key 13 | // val - , associated value 14 | add(key, val) { 15 | if (this.has(key)) { 16 | const prev = this.get(key); 17 | this.allocated -= dataSize(prev); 18 | } 19 | this.allocated += dataSize(val); 20 | this.set(key, val); 21 | } 22 | 23 | // Delete cache element 24 | // key - , key 25 | del(key) { 26 | if (this.has(key)) { 27 | const val = this.get(key); 28 | this.allocated -= dataSize(val); 29 | } 30 | this.delete(key); 31 | } 32 | 33 | // Clear cache elements that start with prefix 34 | // Signature: prefix[, fn] 35 | // prefix - , to compare with beginning of the key 36 | // fn - , (optional) 37 | // key - , key 38 | // val - , associative value to be called on each key 39 | clr(prefix, fn) { 40 | this.forEach((val, key) => { 41 | if (key.startsWith(prefix)) { 42 | this.allocated -= dataSize(val); 43 | this.delete(key); 44 | if (fn) fn(key, val); 45 | } 46 | }); 47 | } 48 | } 49 | 50 | // Create Cache, enhanced Map 51 | // Returns: 52 | const cache = () => new Cache(); 53 | 54 | module.exports = { 55 | cache, 56 | Cache, 57 | }; 58 | -------------------------------------------------------------------------------- /lib/callbacks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { last } = require('./array'); 4 | 5 | // Empty function 6 | // Returns: , always `false` 7 | const falseness = () => false; 8 | 9 | // Empty function 10 | // Returns: , always `true` 11 | const trueness = () => true; 12 | 13 | // Empty function 14 | const emptiness = () => {}; 15 | 16 | // Empty asynchronous callback-last single-argument function 17 | // callback - , callback to be called with (null) 18 | const nop = (callback) => { 19 | callback(null); 20 | }; 21 | 22 | // Empty asynchronous callback-last double-argument function 23 | // empty - , incoming value to be ignored 24 | // callback - , callback to be called with (null, null) 25 | const noop = (empty, callback) => { 26 | callback(null, null); 27 | }; 28 | 29 | // Wrap function: call once, not null 30 | // Signature: [fn] 31 | // fn - , (optional) 32 | // Returns: , function(...args) wrapped callback 33 | // args - 34 | const once = (fn) => { 35 | if (!fn) return emptiness; 36 | let finished = false; 37 | const wrap = (...args) => { 38 | if (finished) return; 39 | finished = true; 40 | fn(...args); 41 | }; 42 | return wrap; 43 | }; 44 | 45 | // Extract callback function 46 | // It's unsafe: may return null, allows multiple calls 47 | // args - , arguments 48 | // Returns: | , callback if any 49 | const unsafeCallback = (args) => { 50 | const callback = last(args); 51 | if (typeof callback === 'function') return args.pop(); 52 | return null; 53 | }; 54 | 55 | // Extract callback 56 | // args - , arguments 57 | // Returns: , callback or common.emptiness if there is no callback 58 | const safeCallback = (args) => { 59 | const callback = last(args); 60 | if (typeof callback === 'function') return args.pop(); 61 | return emptiness; 62 | }; 63 | 64 | // Extract callback 65 | // args - , arguments 66 | // Returns: , extracted callback 67 | // Throws: , if there is no callback 68 | const requiredCallback = (args) => { 69 | const callback = last(args); 70 | if (typeof callback === 'function') return args.pop(); 71 | throw new TypeError('No callback provided'); 72 | }; 73 | 74 | // Extract callback and make it safe 75 | // Wrap callback with once() 76 | // args - , arguments 77 | // Returns: , callback or common.emptiness if there is no callback 78 | const onceCallback = (args) => { 79 | const callback = last(args); 80 | if (typeof callback === 'function') return once(args.pop()); 81 | return emptiness; 82 | }; 83 | 84 | // Check function 85 | // fn - 86 | // Returns: | , function or null if fn is not a function 87 | const unsafeFunction = (fn) => (typeof fn === 'function' ? fn : null); 88 | 89 | // Check function and make it safe 90 | // fn - 91 | // Returns: , function or `common.emptiness` if fn is not a function 92 | const safeFunction = (fn) => (typeof fn === 'function' ? fn : emptiness); 93 | 94 | // Identity function 95 | // x - , incoming value which will be returned 96 | // Returns: , incoming value 97 | const id = (x) => x; 98 | 99 | // Async identity function 100 | // x - , incoming value which will be returned into the callback 101 | // callback - , callback to be called with first argument 102 | // err - 103 | // data - 104 | const asyncId = (x, callback) => { 105 | process.nextTick(callback, null, x); 106 | }; 107 | 108 | module.exports = { 109 | falseness, 110 | trueness, 111 | emptiness, 112 | nop, 113 | noop, 114 | 115 | once, 116 | unsafeCallback, 117 | safeCallback, 118 | 119 | requiredCallback, 120 | onceCallback, 121 | 122 | safeFunction, 123 | unsafeFunction, 124 | 125 | id, 126 | asyncId, 127 | }; 128 | -------------------------------------------------------------------------------- /lib/case.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = {}; 4 | -------------------------------------------------------------------------------- /lib/data.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const SCALAR_TYPES = ['boolean', 'number', 'string', 'undefined']; 4 | 5 | // Check if value is scalar 6 | // value - 7 | // Returns: 8 | const isScalar = (value) => SCALAR_TYPES.includes(typeof value); 9 | 10 | // Copy dataset (copy objects to new array) 11 | // ds - , source dataset to be copied 12 | // Returns: 13 | const copy = (ds) => ds.slice(); 14 | 15 | // Clone object or array 16 | // obj - | 17 | // Returns: | 18 | const clone = (val) => { 19 | if (typeof val !== 'object' || val === null) { 20 | return val; 21 | } 22 | const objOrArray = Array.isArray(val) ? new Array(val.length) : {}; 23 | for (const key in val) { 24 | if (Object.prototype.hasOwnProperty.call(val, key)) { 25 | objOrArray[key] = clone(val[key]); 26 | } 27 | } 28 | return objOrArray; 29 | }; 30 | 31 | const duplicateWithReferences = (val, references) => { 32 | if (typeof val !== 'object' || val === null) { 33 | return val; 34 | } 35 | let objOrArray; 36 | if (Array.isArray(val)) { 37 | objOrArray = new Array(val.length); 38 | } else if (Buffer.isBuffer(val)) { 39 | objOrArray = Buffer.from(val); 40 | } else if (!val.constructor) { 41 | objOrArray = Object.create(null); 42 | } else if (val.constructor.name !== 'Object') { 43 | objOrArray = new val.constructor(val.toString()); 44 | } else { 45 | objOrArray = {}; 46 | } 47 | references.set(val, objOrArray); 48 | for (const key in val) { 49 | if (!Object.prototype.hasOwnProperty.call(val, key)) { 50 | continue; 51 | } 52 | const reference = references.get(val[key]); 53 | if (reference !== undefined) { 54 | objOrArray[key] = reference; 55 | } else { 56 | objOrArray[key] = duplicateWithReferences(val[key], references); 57 | } 58 | } 59 | return objOrArray; 60 | }; 61 | 62 | // Duplicate object or array (properly handles prototype and circular links) 63 | // obj - | 64 | // Returns: | 65 | const duplicate = (val) => duplicateWithReferences(val, new Map()); 66 | 67 | // Read property by dot-separated path 68 | // data - 69 | // dataPath - , dot-separated path 70 | // Returns: , value 71 | const getByPath = (data, dataPath) => { 72 | const path = dataPath.split('.'); 73 | let obj = data; 74 | for (let i = 0; i < path.length; i++) { 75 | const prop = path[i]; 76 | const next = obj[prop]; 77 | if (next === undefined || next === null) return next; 78 | obj = next; 79 | } 80 | return obj; 81 | }; 82 | 83 | // Set property by dot-separated path 84 | // data - 85 | // dataPath - , dot-separated path 86 | // value - , new value 87 | const setByPath = (data, dataPath, value) => { 88 | const path = dataPath.split('.'); 89 | const len = path.length; 90 | let obj = data; 91 | let i = 0; 92 | let next, prop; 93 | for (;;) { 94 | if (typeof obj !== 'object') return false; 95 | prop = path[i]; 96 | if (i === len - 1) { 97 | obj[prop] = value; 98 | return true; 99 | } 100 | next = obj[prop]; 101 | if (next === undefined || next === null) { 102 | next = {}; 103 | obj[prop] = next; 104 | } 105 | obj = next; 106 | i++; 107 | } 108 | }; 109 | 110 | // Delete property by dot-separated path 111 | // data - 112 | // dataPath - , dot-separated path 113 | // Returns: 114 | const deleteByPath = (data, dataPath) => { 115 | const path = dataPath.split('.'); 116 | let obj = data; 117 | const len = path.length; 118 | for (let i = 0; i < len; i++) { 119 | const prop = path[i]; 120 | const next = obj[prop]; 121 | if (i === len - 1) { 122 | if (Object.prototype.hasOwnProperty.call(obj, prop)) { 123 | delete obj[prop]; 124 | return true; 125 | } 126 | } else { 127 | if (next === undefined || next === null) return false; 128 | obj = next; 129 | } 130 | } 131 | return false; 132 | }; 133 | 134 | // Distinctly merge multiple arrays 135 | // Signature: ...args 136 | // args - , arrays with elements to be merged 137 | // Returns: 138 | const merge = (...args) => { 139 | const unique = new Set(); 140 | const ilen = args.length; 141 | for (let i = 0; i < ilen; i++) { 142 | const arr = args[i]; 143 | for (let j = 0; j < arr.length; j++) { 144 | unique.add(arr[j]); 145 | } 146 | } 147 | return [...unique]; 148 | }; 149 | 150 | // Merge multiple objects with merger 151 | // Signature: merger, ...objs 152 | // merger - 153 | // key - current merging key 154 | // ...values - values under key 155 | // objs - , objects to be merged 156 | // Returns: 157 | const mergeObjects = (merger, ...objs) => { 158 | const keys = new Set(); 159 | for (const obj of objs) { 160 | for (const key in obj) { 161 | if (Object.prototype.hasOwnProperty.call(obj, key)) keys.add(key); 162 | } 163 | } 164 | 165 | const result = {}; 166 | for (const key of keys) { 167 | const args = new Array(objs.length); 168 | for (let i = 0; i < objs.length; ++i) { 169 | args[i] = objs[i][key]; 170 | } 171 | result[key] = merger(key, ...args); 172 | } 173 | return result; 174 | }; 175 | 176 | module.exports = { 177 | isScalar, 178 | copy, 179 | clone, 180 | duplicate, 181 | getByPath, 182 | setByPath, 183 | deleteByPath, 184 | merge, 185 | mergeObjects, 186 | }; 187 | -------------------------------------------------------------------------------- /lib/enum.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const NaE = Symbol('NotAnEnum'); 4 | 5 | // Static properties: 6 | // NaE Not an Enum 7 | class Enum { 8 | static from(...args) { 9 | let values = args[0]; 10 | if (typeof values !== 'object') { 11 | values = args; 12 | } 13 | const enumValues = new Map(); 14 | const EnumClass = class extends Enum { 15 | static from(val) { 16 | return enumValues.get(val) || Enum.NaE; 17 | } 18 | static get values() { 19 | return values; 20 | } 21 | static has(value) { 22 | return enumValues.has(value); 23 | } 24 | static key(value) { 25 | const e = enumValues.get(value); 26 | return e ? e.index : undefined; 27 | } 28 | [Symbol.toPrimitive]() { 29 | return this.index; 30 | } 31 | }; 32 | const withData = !Array.isArray(values); 33 | let i = 0; 34 | for (const key in values) { 35 | const e = new EnumClass(); 36 | e.index = i++; 37 | if (withData) { 38 | e.value = key; 39 | e.data = values[key]; 40 | } else { 41 | e.value = values[key]; 42 | } 43 | enumValues.set(e.value, e); 44 | } 45 | return EnumClass; 46 | } 47 | } 48 | 49 | Object.defineProperty(Enum, 'NaE', { 50 | configurable: false, 51 | enumerable: false, 52 | writable: false, 53 | value: NaE, 54 | }); 55 | 56 | module.exports = { Enum }; 57 | -------------------------------------------------------------------------------- /lib/events.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { EventEmitter } = require('events'); 4 | 5 | // Forward an event from one EventEmitter to another 6 | // Signature: from, to, event[, newEvent] 7 | // from - , to listen for event 8 | // to - , to emit event on 9 | // event - , event name 10 | // newEvent - , (optional), default: `event`, forwarded event name 11 | const forwardEvent = (from, to, event, newEvent = event) => { 12 | if (event === '*') { 13 | from.on(event, (eventName, ...args) => { 14 | to.emit(eventName, ...args); 15 | }); 16 | } else { 17 | from.on(event, (...args) => { 18 | to.emit(newEvent, ...args); 19 | }); 20 | } 21 | }; 22 | 23 | // Forward events from one EventEmitter to another 24 | // Signature: from, to[, events] 25 | // from - , to listen for event 26 | // to - , to emit event on 27 | // events - | | , (optional), events names 28 | // 29 | // Example: forwardEvents(from, to); 30 | // Example: forwardEvents(from, to, 'eventName'); 31 | // Example: forwardEvents(from, to, { eventName: 'newEventName' }); 32 | // Example: forwardEvents(from, to, ['eventName1', 'eventName2']); 33 | const forwardEvents = (from, to, events) => { 34 | if (!events) { 35 | forwardEvent(from, to, '*'); 36 | return; 37 | } 38 | if (typeof events === 'string') { 39 | forwardEvent(from, to, events); 40 | return; 41 | } 42 | if (Array.isArray(events)) { 43 | for (const event of events) { 44 | forwardEvent(from, to, event); 45 | } 46 | return; 47 | } 48 | for (const event in events) { 49 | forwardEvent(from, to, event, events[event]); 50 | } 51 | }; 52 | 53 | class EnhancedEmitter extends EventEmitter { 54 | // Call listener with provided arguments 55 | // Signature: ...args 56 | // args - , arguments to be passed 57 | emit(...args) { 58 | super.emit('*', ...args); 59 | super.emit(...args); 60 | } 61 | 62 | // Forward events from one EventEmitter to another 63 | // to - , to emit event on 64 | // events - | | , events names 65 | forward(to, events) { 66 | forwardEvents(this, to, events); 67 | } 68 | } 69 | 70 | // Create EnhancedEmitter, enhanced EventEmitter 71 | // with wildcard and forward method 72 | // Returns: 73 | const emitter = () => new EnhancedEmitter(); 74 | 75 | module.exports = { 76 | forwardEvents, 77 | emitter, 78 | EnhancedEmitter, 79 | }; 80 | -------------------------------------------------------------------------------- /lib/flags.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { Uint64 } = require('./uint64'); 4 | 5 | const masks = []; 6 | const masksNot = []; 7 | for (let i = 0; i < 64; i++) { 8 | const mask = new Uint64(1).shiftLeft(i); 9 | masks.push(mask); 10 | masksNot.push(Uint64.not(mask)); 11 | } 12 | 13 | const getErrorMsg = (key) => `Flags instance does not have key ${key}`; 14 | 15 | class Flags { 16 | static from(...args) { 17 | if (args.length > 64) { 18 | throw new TypeError('Flags does not support more than 64 values'); 19 | } 20 | const values = new Map(args.map((v, i) => [v, i])); 21 | 22 | class FlagsClass { 23 | static from(...args) { 24 | return new FlagsClass(...args); 25 | } 26 | 27 | constructor(...args) { 28 | if (args[0] instanceof Uint64) { 29 | this.value = new Uint64(args[0]); 30 | } else { 31 | this.value = new Uint64(0); 32 | args.forEach((arg) => this.set(arg)); 33 | } 34 | } 35 | 36 | static has(key) { 37 | return values.has(key); 38 | } 39 | 40 | get(key) { 41 | const value = values.get(key); 42 | if (value === undefined) { 43 | throw new TypeError(getErrorMsg(key)); 44 | } 45 | return (Uint64.shiftRight(this.value, value).toUint32() & 0x1) === 1; 46 | } 47 | 48 | set(key) { 49 | const value = values.get(key); 50 | if (value === undefined) { 51 | throw new TypeError(getErrorMsg(key)); 52 | } 53 | this.value.or(masks[value]); 54 | } 55 | 56 | unset(key) { 57 | const value = values.get(key); 58 | if (value === undefined) { 59 | throw new TypeError(getErrorMsg(key)); 60 | } 61 | this.value.and(masksNot[value]); 62 | } 63 | 64 | toggle(key) { 65 | const value = values.get(key); 66 | if (value === undefined) { 67 | throw new TypeError(getErrorMsg(key)); 68 | } 69 | this.value.xor(masks[value]); 70 | } 71 | 72 | toString() { 73 | const str = this.value.toString(2); 74 | return '0'.repeat(values.size - str.length) + str; 75 | } 76 | 77 | toNumber() { 78 | return new Uint64(this.value); 79 | } 80 | 81 | [Symbol.toPrimitive]() { 82 | return this.value.toUint32(); 83 | } 84 | } 85 | return FlagsClass; 86 | } 87 | } 88 | 89 | module.exports = { Flags }; 90 | -------------------------------------------------------------------------------- /lib/fp.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { safeCallback } = require('./callbacks'); 4 | 5 | // Partially apply arguments to function 6 | // Signature: fn, ...args 7 | // fn - 8 | // args - , arguments to be applied 9 | // Returns: , function(...rest) 10 | // rest - , arguments 11 | const partial = 12 | (fn, ...args) => 13 | (...rest) => 14 | fn(...args.concat(rest)); 15 | 16 | // Map object fields with provided function 17 | // mapFn - , to apply to every field value 18 | // obj - , which fields used for mapping 19 | // Returns: , with same reference but with transformed fields 20 | const omap = (mapFn, obj) => { 21 | for (const key in obj) { 22 | obj[key] = mapFn(obj[key]); 23 | } 24 | return obj; 25 | }; 26 | 27 | // Compose multiple functions into one 28 | // Signature: ...fns 29 | // fns - , functions to be composed 30 | // Returns: , function(...args), composed 31 | // args - , arguments to be passed to the first function 32 | const compose = 33 | (...fns) => 34 | (...args) => { 35 | if (fns.length === 0) return args[0]; 36 | 37 | let res = fns[0](...args); 38 | for (let i = 1; i < fns.length; i++) { 39 | res = fns[i](res); 40 | } 41 | return res; 42 | }; 43 | 44 | // Apply given function to value or default value 45 | // Signature: fn, defVal[, value] 46 | // fn - 47 | // defVal - , default value 48 | // value - , (optional), value 49 | // Returns: , result of `fn` or `defVal` 50 | const maybe = (fn, defVal, value) => 51 | value !== undefined && value !== null ? fn(value) : defVal; 52 | 53 | // Zip several arrays into one 54 | // Signature: ...arrays 55 | // arrays - , arrays to be zipped 56 | // Returns: , length is minimal of input arrays length, 57 | // element with index i of resulting array is array with 58 | // elements with index i from input array 59 | const zip = (...arrays) => { 60 | if (arrays.length === 0) return arrays; 61 | 62 | let minLen = arrays[0].length; 63 | for (let i = 1; i < arrays.length; i++) { 64 | minLen = Math.min(arrays[i].length, minLen); 65 | } 66 | 67 | const res = new Array(minLen); 68 | for (let i = 0; i < res.length; i++) { 69 | res[i] = new Array(arrays.length); 70 | for (let j = 0; j < res[i].length; j++) { 71 | res[i][j] = arrays[j][i]; 72 | } 73 | } 74 | return res; 75 | }; 76 | 77 | // Create array of replicated values 78 | // count - , new array length 79 | // elem - , value to replicate 80 | // Returns: , replicated 81 | const replicate = (count, elem) => Array.from({ length: count }, () => elem); 82 | 83 | // Zip arrays using specific function 84 | // Signature: fn, ...arrays 85 | // fn - , for zipping elements with index i 86 | // arrays - , arrays to be zipped 87 | // Returns: , zipped, element with index i of resulting array is result 88 | // of fn called with arguments from arrays 89 | const zipWith = (fn, ...arrays) => zip(...arrays).map((args) => fn(...args)); 90 | 91 | // Curry function until the condition is met 92 | // Signature: condition, fn, ...args 93 | // condition - , returns: 94 | // argsI - , arguments for i-th currying 95 | // argsParts - , of args given for currying 96 | // from first to i-th currying 97 | // fn - , to be curried 98 | // args - , arguments for fn 99 | // Returns: , function(...args), curried 100 | // args - , arguments 101 | const curryUntil = (condition, fn, ...args) => { 102 | const argsParts = []; 103 | 104 | const curryMore = (...argsI) => { 105 | argsParts.push(argsI); 106 | if (condition(argsI, argsParts)) { 107 | const allArgs = [].concat(...argsParts); 108 | return fn(...allArgs); 109 | } 110 | return curryMore; 111 | }; 112 | 113 | return curryMore(...args); 114 | }; 115 | 116 | // Curry function with given arguments 117 | // Signature: fn, ...param 118 | // fn - , to be curried 119 | // param - , arguments to the function 120 | // Returns: , function(...args), curried 121 | const curry = (fn, ...param) => { 122 | const curried = (...args) => 123 | fn.length > args.length ? curry(fn.bind(null, ...args)) : fn(...args); 124 | return param.length ? curried(...param) : curried; 125 | }; 126 | 127 | // Curry fn count times, first curry uses args for first currying 128 | // Signature: fn, count, ...args 129 | // fn - , to be curried 130 | // count - , of times function should be curried 131 | // args - , arguments for first currying 132 | // Returns: , curried given times count 133 | const curryN = (fn, count, ...args) => { 134 | let i = -1; 135 | const condition = () => (i++, i === count); 136 | return curryUntil(condition, fn, ...args); 137 | }; 138 | 139 | // Curry function curry with fn 140 | // fn - , to be curried 141 | // Returns: , to pass arguments that returns curried fn 142 | const curryTwice = (fn) => curry(curry, fn); 143 | 144 | // Apply arguments 145 | // Signature: ...args 146 | // args - , arguments to save in closure 147 | // Returns: , returns: , result of `fn(...args)` 148 | // fn - , to be applied saved arguments 149 | const applyArgs = 150 | (...args) => 151 | (fn) => 152 | fn(...args); 153 | 154 | // Get first not errored result of fn 155 | // fn - , to be called 156 | // Returns: , function(...args), returns: , result of `fn(arg)`, 157 | // where `arg` - first valid element of `args` 158 | // args - , arguments to iterate 159 | // Throws: , if `fn` throws it 160 | const either = 161 | (fn) => 162 | (...args) => { 163 | let lastError; 164 | for (const arg of args) { 165 | try { 166 | return fn(arg); 167 | } catch (error) { 168 | lastError = error; 169 | } 170 | } 171 | throw lastError; 172 | }; 173 | 174 | // Rest left, transform function 175 | // fn - , function(args, ...namedArgs, callback) 176 | // args - , rest of spreadArgs created by excluding namedArgs 177 | // namedArgs - , first values of spreadArgs, 178 | // length is based upon interface of fn 179 | // callback - , callback, last argument of spreadArgs 180 | // Returns: , function(...spreadArgs) 181 | // spreadArgs - , arguments to be added 182 | const restLeft = 183 | (fn) => 184 | (...spreadArgs) => { 185 | const callback = safeCallback(spreadArgs); 186 | const namedArgsCount = fn.length - 2; 187 | const namedArgs = spreadArgs.slice(0, namedArgsCount); 188 | const args = spreadArgs.slice(namedArgsCount); 189 | fn(args, ...namedArgs, callback); 190 | }; 191 | 192 | module.exports = { 193 | partial, 194 | omap, 195 | compose, 196 | maybe, 197 | zip, 198 | replicate, 199 | zipWith, 200 | curryUntil, 201 | curryN, 202 | curryTwice, 203 | curry, 204 | applyArgs, 205 | either, 206 | restLeft, 207 | }; 208 | -------------------------------------------------------------------------------- /lib/fs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const pathModule = require('path'); 5 | const util = require('util'); 6 | const { iter } = require('./iterator'); 7 | const MKDIRP_DEFAULT_MODE = 0o777; 8 | 9 | const mkdir = (dir, mode, cb) => { 10 | fs.access(dir, fs.constants.F_OK, (err) => { 11 | if (err && err.code === 'ENOENT') { 12 | mkdir(pathModule.dirname(dir), mode, (err) => { 13 | if (err) cb(err); 14 | else fs.mkdir(dir, mode, cb); 15 | }); 16 | } else { 17 | cb(err); 18 | } 19 | }); 20 | }; 21 | 22 | const recursivelyListDirs = (dir) => { 23 | const list = [dir]; 24 | let nextDir = dir; 25 | const root = pathModule.parse(dir).root || '.'; 26 | while ((nextDir = pathModule.dirname(nextDir)) !== root) list.push(nextDir); 27 | return list; 28 | }; 29 | 30 | const rmdirp = (dir, cb) => { 31 | const dirs = recursivelyListDirs(dir); 32 | let i = 0; 33 | rmNextDir(); 34 | function rmNextDir() { 35 | fs.rmdir(dirs[i], (err) => { 36 | if (err) { 37 | cb(err); 38 | return; 39 | } 40 | if (++i === dirs.length) { 41 | cb(); 42 | } else { 43 | rmNextDir(); 44 | } 45 | }); 46 | } 47 | }; 48 | 49 | let mkdirp; 50 | const version = process.versions.node.split('.').map((el) => parseInt(el)); 51 | if (version[0] < 10 || (version[0] === 10 && version[1] <= 11)) { 52 | mkdirp = (dir, mode, cb) => { 53 | if (typeof mode === 'function') { 54 | cb = mode; 55 | mode = MKDIRP_DEFAULT_MODE; 56 | } 57 | mkdir(dir, mode, cb); 58 | }; 59 | } else { 60 | mkdirp = (dir, mode, cb) => { 61 | typeof mode === 'function' 62 | ? fs.mkdir(dir, { recursive: true }, mode) 63 | : fs.mkdir(dir, { recursive: true, mode }, cb); 64 | }; 65 | } 66 | 67 | const isNotDirectoryError = (err) => 68 | err.code === 'ENOTDIR' || 69 | (process.platform === 'win32' && err.code === 'ENOENT'); 70 | 71 | // Recursively remove directory 72 | // path path to a file or directory to be removed 73 | // callback callback 74 | const rmRecursive = (path, callback) => { 75 | fs.readdir(path, (err, files) => { 76 | if (err) { 77 | if (isNotDirectoryError(err)) fs.unlink(path, callback); 78 | else callback(err); 79 | return; 80 | } 81 | if (files.length === 0) { 82 | fs.rmdir(path, callback); 83 | return; 84 | } 85 | 86 | let errored = false; 87 | let counter = files.length; 88 | const cb = (err) => { 89 | if (errored) return; 90 | if (err) { 91 | errored = true; 92 | callback(err); 93 | } else if (--counter === 0) { 94 | fs.rmdir(path, callback); 95 | } 96 | }; 97 | files.forEach((f) => rmRecursive(pathModule.join(path, f), cb)); 98 | }); 99 | }; 100 | 101 | // TODO(SemenchenkoVitaliy): remove and use `fs.promises` instead when 102 | // Node.js 8 is dropped 103 | let fsPromises; 104 | if (fs.promises) { 105 | fsPromises = fs.promises; 106 | } else { 107 | fsPromises = iter([ 108 | 'readdir', 109 | 'rmdir', 110 | 'unlink', 111 | 'mkdir', 112 | 'access', 113 | ]).collectWith({}, (obj, name) => { 114 | obj[name] = util.promisify(fs[name]); 115 | }); 116 | } 117 | 118 | // Recursively remove directory 119 | // path path to a file or directory to be removed 120 | // Returns: 121 | const rmRecursivePromise = async (path) => 122 | fsPromises.readdir(path).then( 123 | async (files) => { 124 | await Promise.all( 125 | files.map((f) => rmRecursivePromise(pathModule.join(path, f))), 126 | ); 127 | return fsPromises.rmdir(path); 128 | }, 129 | (err) => { 130 | if (isNotDirectoryError(err)) return fsPromises.unlink(path); 131 | throw err; 132 | }, 133 | ); 134 | 135 | const mkdirPromise = async (dir, mode) => 136 | fsPromises.access(dir).then( 137 | () => Promise.resolve(), 138 | async () => { 139 | await mkdirPromise(pathModule.dirname(dir), mode); 140 | return fsPromises.mkdir(dir, mode); 141 | }, 142 | ); 143 | 144 | let mkdirpPromise; 145 | if (fs.promises) { 146 | mkdirpPromise = (dir, mode = MKDIRP_DEFAULT_MODE) => 147 | fs.promises.mkdir(dir, { recursive: true, mode }); 148 | } else { 149 | mkdirpPromise = (dir, mode = MKDIRP_DEFAULT_MODE) => mkdirPromise(dir, mode); 150 | } 151 | 152 | module.exports = { 153 | mkdirp, 154 | mkdirpPromise, 155 | rmdirp, 156 | rmRecursive, 157 | rmRecursivePromise, 158 | }; 159 | -------------------------------------------------------------------------------- /lib/id.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const crypto = require('crypto'); 4 | 5 | const math = require('./math'); 6 | const { ALPHA_DIGIT } = require('./strings'); 7 | 8 | // Generate random key 9 | // length - , key length 10 | // possible - , with possible characters 11 | // Returns: , key 12 | const generateKey = (length, possible) => { 13 | const base = possible.length; 14 | let key = ''; 15 | for (let i = 0; i < length; i++) { 16 | const index = Math.floor(math.cryptoRandom() * base); 17 | key += possible[index]; 18 | } 19 | return key; 20 | }; 21 | 22 | // Generate file storage key 23 | // Returns: , [folder1, folder2, code] 24 | const generateStorageKey = () => { 25 | const folder1 = generateKey(2, ALPHA_DIGIT); 26 | const folder2 = generateKey(2, ALPHA_DIGIT); 27 | const code = generateKey(8, ALPHA_DIGIT); 28 | return [folder1, folder2, code]; 29 | }; 30 | 31 | const guidPrefetcher = math.cryptoPrefetcher(4096, 16); 32 | 33 | // Generate an RFC4122-compliant GUID (UUID v4) 34 | // Returns: , GUID 35 | const generateGUID = () => { 36 | const bytes = guidPrefetcher.next(); 37 | 38 | bytes[6] &= 0x0f; 39 | bytes[6] |= 0x40; 40 | bytes[8] &= 0x3f; 41 | bytes[8] |= 0x80; 42 | 43 | return [ 44 | bytes.toString('hex', 0, 4), 45 | bytes.toString('hex', 4, 6), 46 | bytes.toString('hex', 6, 8), 47 | bytes.toString('hex', 8, 10), 48 | bytes.toString('hex', 10, 16), 49 | ].join('-'); 50 | }; 51 | 52 | // Calculate Token crc 53 | // secret 54 | // key 55 | // Returns: , crc 56 | const crcToken = (secret, key) => 57 | crypto 58 | .createHash('md5') 59 | .update(key + secret) 60 | .digest('hex') 61 | .substring(0, 4); 62 | 63 | // Generate random Token 64 | // secret 65 | // characters 66 | // length 67 | // Returns: , token 68 | const generateToken = (secret, characters, length) => { 69 | const key = generateKey(length - 4, characters); 70 | return key + crcToken(secret, key); 71 | }; 72 | 73 | // Validate Token 74 | // secret 75 | // token 76 | // Returns: 77 | const validateToken = (secret, token) => { 78 | if (!token) return false; 79 | const len = token.length; 80 | const crc = token.slice(len - 4); 81 | const key = token.slice(0, -4); 82 | return crcToken(secret, key) === crc; 83 | }; 84 | 85 | // Calculate hash with salt 86 | // password - 87 | // salt - 88 | // Returns: , hash 89 | const hash = (password, salt) => 90 | crypto.createHmac('sha512', salt).update(password).digest('hex'); 91 | 92 | // Validate hash 93 | // hashValue - 94 | // password - 95 | // salt - 96 | // Returns: 97 | const validateHash = (hashValue, password, salt) => 98 | hash(password, salt) === hashValue; 99 | 100 | // Convert id to array of hex strings 101 | // id - 102 | // Returns: , minimal length is 2 103 | // which contains hex strings with length of 4 104 | const idToChunks = (id) => { 105 | let hex = id.toString(16); 106 | const remainder = hex.length % 4; 107 | if (remainder !== 0) { 108 | hex = hex.padStart(hex.length + 4 - remainder, '0'); 109 | } 110 | let count = hex.length / 4; 111 | if (count === 1) { 112 | hex = '0000' + hex; 113 | count++; 114 | } 115 | const chunks = new Array(count); 116 | for (let i = 0; i < count; i++) { 117 | const chunk = hex.substr((i + 1) * -4, 4); 118 | chunks[i] = chunk; 119 | } 120 | return chunks; 121 | }; 122 | 123 | // Convert id to file path 124 | // id - 125 | // Returns: 126 | const idToPath = (id) => { 127 | const chunks = idToChunks(id); 128 | const path = chunks.join('/'); 129 | return path; 130 | }; 131 | 132 | // Convert file path to id 133 | // path - 134 | // Returns: 135 | const pathToId = (path) => { 136 | const chunks = path.split('/'); 137 | let hex = '0x'; 138 | for (let i = chunks.length - 1; i >= 0; i--) { 139 | hex += chunks[i]; 140 | } 141 | return parseInt(hex, 16); 142 | }; 143 | 144 | module.exports = { 145 | generateKey, 146 | generateGUID, 147 | generateToken, 148 | crcToken, 149 | validateToken, 150 | hash, 151 | validateHash, 152 | generateStorageKey, 153 | idToChunks, 154 | idToPath, 155 | pathToId, 156 | }; 157 | -------------------------------------------------------------------------------- /lib/math.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const crypto = require('crypto'); 4 | 5 | class CryptoRandomPrefetcher { 6 | constructor(bufSize, valueSize) { 7 | if (bufSize % valueSize !== 0) { 8 | throw new RangeError('buffer size must be a multiple of value size'); 9 | } 10 | this.buf = crypto.randomBytes(bufSize); 11 | this.pos = 0; 12 | this.vsz = valueSize; 13 | } 14 | 15 | // Return Buffer with next `valueSize` random bytes. 16 | next() { 17 | if (this.pos === this.buf.length) { 18 | this.pos = 0; 19 | crypto.randomFillSync(this.buf); 20 | } 21 | const end = this.pos + this.vsz; 22 | const buf = this.buf.slice(this.pos, end); 23 | this.pos = end; 24 | return buf; 25 | } 26 | 27 | [Symbol.iterator]() { 28 | return { 29 | [Symbol.iterator]() { 30 | return this; 31 | }, 32 | next: () => ({ value: this.next(), done: false }), 33 | }; 34 | } 35 | } 36 | 37 | // Create prefetcher to use when crypto.randomBytes is required to generate 38 | // multiple same-size values. `bufSize` must be a multiple of `valueSize` for 39 | // this to work. 40 | // bufSize - , size in bytes of the buffer to preallocate 41 | // valueSize - , size in bytes of the produced chunks 42 | const cryptoPrefetcher = (bufSize, valueSize) => 43 | new CryptoRandomPrefetcher(bufSize, valueSize); 44 | 45 | // Generate random integer value in given range 46 | // min - , range start 47 | // max - , range end 48 | // Returns: 49 | const random = (min, max) => { 50 | if (max === undefined) { 51 | max = min; 52 | min = 0; 53 | } 54 | return min + Math.floor(Math.random() * (max - min + 1)); 55 | }; 56 | 57 | const randPrefetcher = cryptoPrefetcher(4096, 4); 58 | const UINT32_MAX = 0xffffffff; 59 | 60 | // Generate random number in the range from 0 inclusive up to 61 | // but not including 1 (same as Math.random), 62 | // using crypto-secure number generator. 63 | // Returns: 64 | const cryptoRandom = () => 65 | randPrefetcher.next().readUInt32LE(0, true) / (UINT32_MAX + 1); 66 | 67 | module.exports = { 68 | cryptoPrefetcher, 69 | random, 70 | cryptoRandom, 71 | }; 72 | -------------------------------------------------------------------------------- /lib/mp.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // List method names 4 | // iface - , to be introspected 5 | // Returns: , method names 6 | const methods = (iface) => { 7 | const names = []; 8 | for (const name in iface) { 9 | if (typeof iface[name] === 'function') { 10 | names.push(name); 11 | } 12 | } 13 | return names; 14 | }; 15 | 16 | // List property names 17 | // iface - , to be introspected 18 | // Returns: , property names 19 | const properties = (iface) => { 20 | const names = []; 21 | for (const name in iface) { 22 | if (typeof iface[name] !== 'function') { 23 | names.push(name); 24 | } 25 | } 26 | return names; 27 | }; 28 | 29 | module.exports = { 30 | methods, 31 | properties, 32 | }; 33 | -------------------------------------------------------------------------------- /lib/network.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const os = require('os'); 4 | 5 | // Convert IP string to number 6 | // Signature: [ip] 7 | // ip - , (optional), default: '127.0.0.1', IP address 8 | // Returns: 9 | const ipToInt = (ip = '127.0.0.1') => 10 | ip.split('.').reduce((res, item) => (res << 8) + +item, 0); 11 | 12 | let LOCAL_IPS_CACHE; 13 | 14 | // Get local network interfaces 15 | // Returns: 16 | const localIPs = () => { 17 | if (LOCAL_IPS_CACHE) return LOCAL_IPS_CACHE; 18 | const ips = []; 19 | const ifHash = os.networkInterfaces(); 20 | for (const ifName in ifHash) { 21 | const ifItem = ifHash[ifName]; 22 | for (let i = 0; i < ifItem.length; i++) { 23 | const protocol = ifItem[i]; 24 | if (protocol.family === 'IPv4') { 25 | ips.push(protocol.address); 26 | } 27 | } 28 | } 29 | LOCAL_IPS_CACHE = ips; 30 | return ips; 31 | }; 32 | 33 | // Parse host string 34 | // host - , host or empty string, may contain `:port` 35 | // Returns: , host without port but not empty 36 | const parseHost = (host) => { 37 | if (!host) { 38 | return 'no-host-name-in-http-headers'; 39 | } 40 | const portOffset = host.indexOf(':'); 41 | if (portOffset > -1) host = host.substr(0, portOffset); 42 | return host; 43 | }; 44 | 45 | module.exports = { 46 | ipToInt, 47 | localIPs, 48 | parseHost, 49 | }; 50 | -------------------------------------------------------------------------------- /lib/numeric.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class Numeric {} 4 | 5 | module.exports = { Numeric }; 6 | -------------------------------------------------------------------------------- /lib/oop.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Override method: save old to `fn.inherited` 4 | // Previous function will be accessible by obj.fnName.inherited 5 | // obj - , containing method to override 6 | // fn - , name will be used to find method 7 | const override = (obj, fn) => { 8 | fn.inherited = obj[fn.name]; 9 | obj[fn.name] = fn; 10 | }; 11 | 12 | // Mixin for ES6 classes without overriding existing methods 13 | // target - , mixin to target 14 | // source - , source methods 15 | const mixin = (target, source) => { 16 | const methods = Object.getOwnPropertyNames(source); 17 | const mix = {}; 18 | for (const method of methods) { 19 | if (!target[method]) { 20 | mix[method] = source[method]; 21 | } 22 | } 23 | Object.assign(target, mix); 24 | }; 25 | 26 | module.exports = { 27 | override, 28 | mixin, 29 | }; 30 | -------------------------------------------------------------------------------- /lib/pool.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const storage = Symbol('storage'); 4 | 5 | class Pool { 6 | constructor(factory = null) { 7 | this.factory = factory; 8 | this[storage] = []; 9 | } 10 | 11 | put(value) { 12 | this[storage].push(value); 13 | } 14 | 15 | get() { 16 | if (this[storage].length === 0) { 17 | return typeof this.factory === 'function' ? this.factory() : null; 18 | } 19 | return this[storage].pop(); 20 | } 21 | } 22 | 23 | module.exports = { Pool }; 24 | -------------------------------------------------------------------------------- /lib/sort.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Compare for array.sort with priority 4 | // priority - , with priority 5 | // s1 - , to compare 6 | // s2 - , to compare 7 | // Returns: 8 | // 9 | // Example: files.sort(common.sortComparePriority) 10 | const sortComparePriority = (priority, s1, s2) => { 11 | let a = priority.indexOf(s1); 12 | let b = priority.indexOf(s2); 13 | if (a === -1) a = Infinity; 14 | if (b === -1) b = Infinity; 15 | if (a < b) return -1; 16 | if (a > b) return 1; 17 | return 0; 18 | }; 19 | 20 | // Compare for array.sort, directories first 21 | // a - , to compare 22 | // b - , to compare 23 | // Returns: 24 | // 25 | // Example: files.sort(sortCompareDirectories); 26 | const sortCompareDirectories = (a, b) => { 27 | let s1 = a.name; 28 | let s2 = b.name; 29 | if (s1.charAt(0) !== '/') s1 = '0' + s1; 30 | if (s2.charAt(0) !== '/') s2 = '0' + s2; 31 | if (s1 < s2) return -1; 32 | if (s1 > s2) return 1; 33 | return 0; 34 | }; 35 | 36 | // Compare for array.sort 37 | // a - , { name } to compare 38 | // b - , { name } to compare 39 | // Returns: 40 | // 41 | // Example: files.sort(sortCompareByName) 42 | const sortCompareByName = (a, b) => { 43 | const s1 = a.name; 44 | const s2 = b.name; 45 | if (s1 < s2) return -1; 46 | if (s1 > s2) return 1; 47 | return 0; 48 | }; 49 | 50 | module.exports = { 51 | sortComparePriority, 52 | sortCompareDirectories, 53 | sortCompareByName, 54 | }; 55 | -------------------------------------------------------------------------------- /lib/stream.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const units = require('./units'); 4 | 5 | const stream = require('stream'); 6 | 7 | const buffer = Symbol('buffer'); 8 | const storageLeft = Symbol('storageLeft'); 9 | 10 | const SIZE_LIMIT = 8 * 1000 * 1000; // 8 MB 11 | 12 | class MemoryWritable extends stream.Writable { 13 | // Signature: [sizeLimit] 14 | // sizeLimit | limit of the internal buffer size specified 15 | // as number in bytes or as string in format supported by 16 | // `common.bytesToSize()`. Defaults to 8 MB 17 | constructor(sizeLimit = SIZE_LIMIT) { 18 | super(); 19 | 20 | this[buffer] = []; 21 | this[storageLeft] = units.sizeToBytes(sizeLimit); 22 | this.finished = false; 23 | this.once('finish', () => { 24 | this.finished = true; 25 | }); 26 | } 27 | 28 | // #private 29 | _write(chunk, encoding, callback) { 30 | this[storageLeft] -= chunk.length; 31 | if (this[storageLeft] < 0) { 32 | callback( 33 | new RangeError(`size limit exceeded by ${-this[storageLeft]} bytes`), 34 | ); 35 | return; 36 | } 37 | this[buffer].push(chunk); 38 | callback(); 39 | } 40 | 41 | // #private 42 | _final(callback) { 43 | this[buffer] = Buffer.concat(this[buffer]); 44 | callback(); 45 | } 46 | 47 | // Return a Promise that will be resolved with all the written data once it 48 | // becomes available. 49 | // Signature: [encoding] 50 | // encoding - encoding to convert the resulting data to, must be a 51 | // valid encoding 52 | // Returns: 53 | async getData(encoding) { 54 | return new Promise((resolve, reject) => { 55 | const finishCallback = () => { 56 | this.removeListener('error', reject); 57 | let buf = Buffer.from(this[buffer]); 58 | if (encoding) { 59 | try { 60 | buf = buf.toString(encoding); 61 | } catch (e) { 62 | reject(e); 63 | return; 64 | } 65 | } 66 | resolve(buf); 67 | }; 68 | if (this.finished) { 69 | finishCallback(); 70 | } else { 71 | this.once('error', reject); 72 | this.once('finish', finishCallback); 73 | } 74 | }); 75 | } 76 | } 77 | 78 | module.exports = { 79 | MemoryWritable, 80 | }; 81 | -------------------------------------------------------------------------------- /lib/strings.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | const { getByPath } = require('./data'); 6 | const { nowDateTime } = require('./time'); 7 | 8 | const HTML_ESCAPE_REGEXP = new RegExp('[&<>"\'/]', 'g'); 9 | 10 | const HTML_ESCAPE_CHARS = { 11 | '&': '&', 12 | '<': '<', 13 | '>': '>', 14 | '"': '"', 15 | "'": ''', 16 | }; 17 | 18 | const ALPHA_UPPER = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; 19 | const ALPHA_LOWER = 'abcdefghijklmnopqrstuvwxyz'; 20 | const ALPHA = ALPHA_UPPER + ALPHA_LOWER; 21 | const DIGIT = '0123456789'; 22 | const ALPHA_DIGIT = ALPHA + DIGIT; 23 | 24 | // Escape html characters 25 | // content - , to escape 26 | // Returns: 27 | // 28 | // Example: htmlEscape('5>=5') = '5<=5' 29 | const htmlEscape = (content) => 30 | content.replace(HTML_ESCAPE_REGEXP, (char) => HTML_ESCAPE_CHARS[char]); 31 | 32 | // Substitute variables 33 | // tpl - , template body 34 | // data - , hash, data structure to visualize 35 | // dataPath - , current position in data structure 36 | // escapeHtml - , escape html special characters if true 37 | // Returns: 38 | const subst = (tpl, data, dataPath, escapeHtml) => { 39 | let start = 0; 40 | let end = tpl.indexOf('@'); 41 | if (end === -1) return tpl; 42 | 43 | const defaultData = getByPath(data, dataPath); 44 | let result = ''; 45 | while (end !== -1) { 46 | result += tpl.substring(start, end); 47 | start = end + 1; 48 | end = tpl.indexOf('@', start); 49 | if (end === -1) { 50 | start--; 51 | break; 52 | } 53 | 54 | const hasDot = tpl.charAt(start) === '.'; 55 | const key = tpl.slice(hasDot ? start + 1 : start, end); 56 | let value = getByPath(hasDot ? defaultData : data, key); 57 | if (hasDot && value === undefined && key === 'value') { 58 | value = defaultData; 59 | } 60 | 61 | if (value === null) { 62 | value = '[null]'; 63 | } else if (value === undefined) { 64 | value = '[undefined]'; 65 | } else if (typeof value === 'object') { 66 | const parentName = value.constructor.name; 67 | if (parentName === 'Date') { 68 | value = nowDateTime(value); 69 | } else if (parentName === 'Array') { 70 | value = '[array]'; 71 | } else { 72 | value = '[object]'; 73 | } 74 | } 75 | result += escapeHtml ? htmlEscape(value) : value; 76 | start = end + 1; 77 | end = tpl.indexOf('@', start); 78 | } 79 | if (start < tpl.length) { 80 | result += tpl.substring(start); 81 | } 82 | return result; 83 | }; 84 | 85 | // Extract file extension in lower case without dot 86 | // fileName - , file name 87 | // Returns: 88 | // 89 | // Example: fileExt('/dir/file.txt') 90 | // Result: 'txt' 91 | const fileExt = (fileName) => 92 | path.extname(fileName).replace('.', '').toLowerCase(); 93 | 94 | // Remove file extension from file name 95 | // fileName - , file name 96 | // Returns: 97 | // 98 | // Example: fileExt('file.txt') 99 | // Result: 'file' 100 | const removeExt = (fileName) => fileName.substr(0, fileName.lastIndexOf('.')); 101 | 102 | const CAPITALIZE_REGEXP = /\w+/g; 103 | 104 | // Capitalize string 105 | // s - 106 | // Returns: 107 | const capitalize = (s) => 108 | s.replace( 109 | CAPITALIZE_REGEXP, 110 | (word) => word.charAt(0).toUpperCase() + word.substr(1).toLowerCase(), 111 | ); 112 | 113 | const UNDERLINE_REGEXP = /_/g; 114 | 115 | // Convert spinal case to camel case 116 | // name - 117 | // Returns: 118 | const spinalToCamel = (name) => 119 | name 120 | .replace(UNDERLINE_REGEXP, '-') 121 | .split('-') 122 | .map((part, i) => (i > 0 ? capitalize(part) : part)) 123 | .join(''); 124 | 125 | const ESCAPE_REGEXP_SPECIALS = [ 126 | // order matters for these 127 | '-', 128 | '[', 129 | ']', 130 | // order doesn't matter for any of these 131 | '/', 132 | '{', 133 | '}', 134 | '(', 135 | ')', 136 | '*', 137 | '+', 138 | '?', 139 | '.', 140 | '\\', 141 | '^', 142 | '$', 143 | '|', 144 | ]; 145 | 146 | const ESCAPE_REGEXP = new RegExp( 147 | '[' + ESCAPE_REGEXP_SPECIALS.join('\\') + ']', 148 | 'g', 149 | ); 150 | 151 | // Escape regular expression control characters 152 | // s - 153 | // Returns: 154 | // 155 | // Example: escapeRegExp('/path/to/res?search=this.that') 156 | const escapeRegExp = (s) => s.replace(ESCAPE_REGEXP, '\\$&'); 157 | 158 | // Generate escaped regular expression 159 | // s - 160 | // Returns: 161 | const newEscapedRegExp = (s) => new RegExp(escapeRegExp(s), 'g'); 162 | 163 | // Add trailing slash at the end if there isn't one 164 | // s - 165 | // Returns: 166 | const addTrailingSlash = (s) => s + (s.endsWith('/') ? '' : '/'); 167 | 168 | // Remove trailing slash from string 169 | // s - 170 | // Returns: 171 | const stripTrailingSlash = (s) => 172 | s.endsWith('/') ? s.substr(0, s.length - 1) : s; 173 | 174 | // Get directory name with trailing slash from path 175 | // filePath - 176 | // Returns: 177 | const dirname = (filePath) => { 178 | let dir = path.dirname(filePath); 179 | if (dir !== '/') dir += '/'; 180 | return dir; 181 | }; 182 | 183 | // Extract substring between prefix and suffix 184 | // s - , source 185 | // prefix - , before needed fragment 186 | // suffix - , after needed fragment 187 | // Returns: 188 | const between = (s, prefix, suffix) => { 189 | let i = s.indexOf(prefix); 190 | if (i === -1) return ''; 191 | s = s.substring(i + prefix.length); 192 | if (suffix) { 193 | i = s.indexOf(suffix); 194 | if (i === -1) return ''; 195 | s = s.substring(0, i); 196 | } 197 | return s; 198 | }; 199 | 200 | const BOM_REGEXP = /^[\uBBBF\uFEFF]*/; 201 | 202 | // Remove UTF-8 BOM 203 | // s - , possibly starts with BOM 204 | // Returns: 205 | const removeBOM = (s) => 206 | typeof s === 'string' ? s.replace(BOM_REGEXP, '') : s; 207 | 208 | const ITEM_ESCAPE_REGEXP = /\\\*/g; 209 | 210 | // Generate RegExp from array with '*' wildcards 211 | // items - 212 | // Returns: 213 | // 214 | // Example: ['/css/*', '/index.html'] 215 | const arrayRegExp = (items) => { 216 | if (!items || items.length === 0) return null; 217 | items = items.map((item) => 218 | escapeRegExp(item).replace(ITEM_ESCAPE_REGEXP, '.*'), 219 | ); 220 | const ex = items.length === 1 ? items[0] : '((' + items.join(')|(') + '))'; 221 | return new RegExp('^' + ex + '$'); 222 | }; 223 | 224 | // Split string by the first occurrence of separator 225 | // s - 226 | // separator - , or char 227 | // Returns: 228 | // 229 | // Example: rsection('All you need is JavaScript', 'is') 230 | // Result: ['All you need ', ' JavaScript'] 231 | const section = (s, separator) => { 232 | const i = s.indexOf(separator); 233 | if (i < 0) return [s, '']; 234 | return [s.slice(0, i), s.slice(i + separator.length)]; 235 | }; 236 | 237 | // Split string by the last occurrence of separator 238 | // s - 239 | // separator - , or char 240 | // Returns: 241 | // 242 | // Example: rsection('All you need is JavaScript', 'a') 243 | // Result: ['All you need is Jav', 'Script'] 244 | const rsection = (s, separator) => { 245 | const i = s.lastIndexOf(separator); 246 | if (i < 0) return [s, '']; 247 | return [s.slice(0, i), s.slice(i + separator.length)]; 248 | }; 249 | 250 | // Split string by multiple occurrence of separator 251 | // Signature: s[, separator[, limit]] 252 | // s - 253 | // separator - , (optional), default: ',' 254 | // limit - , (optional), default: `-1`, max length of result array 255 | // Returns: 256 | // 257 | // Example: split('a,b,c,d') 258 | // Result: ['a', 'b', 'c', 'd'] 259 | // Example: split('a,b,c,d', ',', 2) 260 | // Result: ['a', 'b'] 261 | const split = (s, separator = ',', limit = -1) => s.split(separator, limit); 262 | 263 | // Split string by multiple occurrences of separator 264 | // Signature: s[, separator[, limit]] 265 | // s - 266 | // separator - , (optional), default: ',' 267 | // limit - , (optional), default: `-1`, max length of result array 268 | // Returns: 269 | // 270 | // Example: split('a,b,c,d', ',', 2) 271 | // Result: ['c', 'd'] 272 | const rsplit = (s, separator = ',', limit = -1) => { 273 | const result = []; 274 | if (limit === -1) limit = Number.MAX_VALUE; 275 | let count = 0; 276 | while (limit > count) { 277 | const i = s.lastIndexOf(separator); 278 | if (i < 0) { 279 | result.unshift(s); 280 | return result; 281 | } 282 | result.unshift(s.slice(i + separator.length)); 283 | s = s.slice(0, i); 284 | count++; 285 | } 286 | return result; 287 | }; 288 | 289 | // Normalize email address according to OWASP recommendations 290 | // email - , email address to normalize 291 | // Returns: , normalized email address 292 | const normalizeEmail = (email) => { 293 | const at = email.lastIndexOf('@'); 294 | const domain = email.slice(at).toLowerCase(); 295 | return email.slice(0, at) + domain; 296 | }; 297 | 298 | module.exports = { 299 | subst, 300 | htmlEscape, 301 | fileExt, 302 | removeExt, 303 | spinalToCamel, 304 | escapeRegExp, 305 | newEscapedRegExp, 306 | addTrailingSlash, 307 | stripTrailingSlash, 308 | dirname, 309 | capitalize, 310 | between, 311 | removeBOM, 312 | arrayRegExp, 313 | section, 314 | rsection, 315 | split, 316 | rsplit, 317 | normalizeEmail, 318 | ALPHA_UPPER, 319 | ALPHA_LOWER, 320 | ALPHA, 321 | DIGIT, 322 | ALPHA_DIGIT, 323 | }; 324 | -------------------------------------------------------------------------------- /lib/time.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Compare time1 and time2 4 | // time1 - , time or milliseconds 5 | // time2 - , time or milliseconds 6 | // Returns: 7 | // 8 | // Example: isTimeEqual(sinceTime, buffer.stats.mtime) 9 | const isTimeEqual = (time1, time2) => 10 | new Date(time1).getTime() === new Date(time2).getTime(); 11 | 12 | const pad2 = (n) => (n < 10 ? '0' + n : '' + n); 13 | 14 | // Get current date in YYYY-MM-DD format 15 | // Signature: [date] 16 | // date - , (optional), default: `new Date()` 17 | // Returns: 18 | const nowDate = (date) => { 19 | if (!date) date = new Date(); 20 | return ( 21 | date.getUTCFullYear() + 22 | '-' + 23 | pad2(date.getUTCMonth() + 1) + 24 | '-' + 25 | pad2(date.getUTCDate()) 26 | ); 27 | }; 28 | 29 | // Get current date in YYYY-MM-DD hh:mm format 30 | // Signature: [date] 31 | // date - , (optional), default: `new Date()` 32 | // Returns: 33 | const nowDateTime = (date) => { 34 | if (!date) date = new Date(); 35 | return ( 36 | date.getUTCFullYear() + 37 | '-' + 38 | pad2(date.getUTCMonth() + 1) + 39 | '-' + 40 | pad2(date.getUTCDate()) + 41 | ' ' + 42 | pad2(date.getUTCHours()) + 43 | ':' + 44 | pad2(date.getUTCMinutes()) 45 | ); 46 | }; 47 | 48 | module.exports = { 49 | isTimeEqual, 50 | nowDate, 51 | nowDateTime, 52 | }; 53 | -------------------------------------------------------------------------------- /lib/uint64.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // The value is assigned at the end of the file to avoid the circular reference. 4 | let Int64 = null; 5 | 6 | const UINT32_MAX = 0xffffffff; 7 | 8 | const charToNum = { 9 | 0: 0, 10 | 1: 1, 11 | 2: 2, 12 | 3: 3, 13 | 4: 4, 14 | 5: 5, 15 | 6: 6, 16 | 7: 7, 17 | 8: 8, 18 | 9: 9, 19 | a: 10, 20 | A: 10, 21 | b: 11, 22 | B: 11, 23 | c: 12, 24 | C: 12, 25 | d: 13, 26 | D: 13, 27 | e: 14, 28 | E: 14, 29 | f: 15, 30 | F: 15, 31 | }; 32 | 33 | const radixStr = { 34 | '0x': 16, 35 | '0o': 8, 36 | '0b': 2, 37 | }; 38 | 39 | class Uint64 { 40 | constructor(value) { 41 | this.value = new Uint32Array(2); 42 | if (value instanceof Uint64 || value instanceof Int64) { 43 | this.value[0] = value.value[0]; 44 | this.value[1] = value.value[1]; 45 | return; 46 | } 47 | const numValue = Number(value); 48 | if (Number.isNaN(numValue) || numValue <= 0) { 49 | return; 50 | } 51 | if (numValue <= UINT32_MAX) { 52 | this.value[0] = numValue; 53 | } else if (numValue <= Number.MAX_SAFE_INTEGER) { 54 | this.value[0] = numValue % (UINT32_MAX + 1); 55 | this.value[1] = Math.floor(numValue / (UINT32_MAX + 1)); 56 | } else if (typeof value === 'string') { 57 | const radix = radixStr[value.slice(0, 2)] || 10; 58 | const uintRadix = new Uint64(radix); 59 | let res = new Uint64(); 60 | for (let i = radix !== 10 ? 2 : 0; i < value.length; i++) { 61 | const digit = charToNum[value[i]]; 62 | const uintDigit = new Uint64(digit); 63 | res = Uint64.mult(res, uintRadix).add(uintDigit); 64 | } 65 | this.value[0] = res.value[0]; 66 | this.value[1] = res.value[1]; 67 | } 68 | } 69 | 70 | toUint32() { 71 | return this.value[0]; 72 | } 73 | 74 | static add(a, b) { 75 | return new Uint64(a).add(b); 76 | } 77 | 78 | add(b) { 79 | const tmp = this.value[0] + b.value[0]; 80 | this.value[0] = tmp; 81 | this.value[1] += b.value[1] + Math.floor(tmp / (UINT32_MAX + 1)); 82 | return this; 83 | } 84 | 85 | static sub(a, b) { 86 | return new Uint64(a).sub(b); 87 | } 88 | 89 | sub(b) { 90 | if (b.value[0] > this.value[0]) { 91 | this.value[1]--; 92 | this.value[0] = ~this.value[0] + 1; 93 | } 94 | this.value[0] -= b.value[0]; 95 | this.value[1] -= b.value[1]; 96 | return this; 97 | } 98 | 99 | static mult(a, b) { 100 | const result = new Uint64(); 101 | let value = b.value[0]; 102 | let bitIndex = 0; 103 | while (value) { 104 | if (value & 1) { 105 | result.add(Uint64.shiftLeft(a, bitIndex)); 106 | } 107 | bitIndex++; 108 | value >>>= 1; 109 | } 110 | value = b.value[1]; 111 | bitIndex = 32; 112 | while (value) { 113 | if (value & 1) { 114 | result.add(Uint64.shiftLeft(a, bitIndex)); 115 | } 116 | bitIndex++; 117 | value >>>= 1; 118 | } 119 | return result; 120 | } 121 | 122 | static cmp(a, b) { 123 | if (a.value[1] > b.value[1]) { 124 | return 1; 125 | } else if (a.value[1] < b.value[1]) { 126 | return -1; 127 | } else if (a.value[0] === b.value[0]) { 128 | return 0; 129 | } 130 | return a.value[0] > b.value[0] ? 1 : -1; 131 | } 132 | 133 | // #private 134 | static _division(n, d) { 135 | const zero = new Uint64(); 136 | const one = new Uint64(1); 137 | if (Uint64.cmp(d, zero) === 0) { 138 | throw new RangeError('Uint64: division by zero'); 139 | } 140 | const cmp = Uint64.cmp(d, n); 141 | if (cmp > 0) return [zero, n]; 142 | else if (cmp === 0) return [one, zero]; 143 | 144 | const q = new Uint64(); 145 | const r = new Uint64(); 146 | for (let i = 63; i >= 0; i--) { 147 | r.shiftLeft(1); 148 | const valIndex = i >>> 5; 149 | const nval = n.value[valIndex]; 150 | const ii = i < 32 ? i : i - 32; 151 | r.value[0] = (r.value[0] & ~1) | ((nval & (1 << ii)) >>> ii); 152 | if (Uint64.cmp(r, d) >= 0) { 153 | r.sub(d); 154 | q.value[valIndex] |= 1 << ii; 155 | } 156 | } 157 | return [q, r]; 158 | } 159 | 160 | static div(a, b) { 161 | return Uint64._division(a, b)[0]; 162 | } 163 | 164 | static mod(a, b) { 165 | return Uint64._division(a, b)[1]; 166 | } 167 | 168 | static and(a, b) { 169 | return new Uint64(a).and(b); 170 | } 171 | 172 | and(b) { 173 | this.value[0] &= b.value[0]; 174 | this.value[1] &= b.value[1]; 175 | return this; 176 | } 177 | 178 | static or(a, b) { 179 | return new Uint64(a).or(b); 180 | } 181 | 182 | or(b) { 183 | this.value[0] |= b.value[0]; 184 | this.value[1] |= b.value[1]; 185 | return this; 186 | } 187 | 188 | static not(a) { 189 | return new Uint64(a).not(); 190 | } 191 | 192 | not() { 193 | this.value[0] = ~this.value[0]; 194 | this.value[1] = ~this.value[1]; 195 | return this; 196 | } 197 | 198 | static xor(a, b) { 199 | return new Uint64(a).xor(b); 200 | } 201 | 202 | xor(b) { 203 | this.value[0] ^= b.value[0]; 204 | this.value[1] ^= b.value[1]; 205 | return this; 206 | } 207 | 208 | static shiftRight(a, b) { 209 | return new Uint64(a).shiftRight(b); 210 | } 211 | 212 | shiftRight(b) { 213 | b %= 64; 214 | if (b >= 32) { 215 | this.value[0] = this.value[1] >>> (b - 32); 216 | this.value[1] = 0; 217 | return this; 218 | } 219 | const mask = (1 << b) - 1; 220 | const tr = (this.value[1] & mask) << (32 - b); 221 | this.value[1] >>>= b; 222 | this.value[0] = (this.value[0] >>> b) | tr; 223 | return this; 224 | } 225 | 226 | static shiftLeft(a, b) { 227 | return new Uint64(a).shiftLeft(b); 228 | } 229 | 230 | shiftLeft(b) { 231 | b %= 64; 232 | if (b >= 32) { 233 | this.value[1] = this.value[0] << (b - 32); 234 | this.value[0] = 0; 235 | return this; 236 | } 237 | const mask = ((1 << b) - 1) << (32 - b); 238 | const tr = (this.value[0] & mask) >>> (32 - b); 239 | this.value[0] <<= b; 240 | this.value[1] = (this.value[1] << b) | tr; 241 | return this; 242 | } 243 | 244 | inc() { 245 | return this.add(new Uint64(1)); 246 | } 247 | 248 | dec() { 249 | return this.sub(new Uint64(1)); 250 | } 251 | 252 | toString(radix = 10) { 253 | if (radix < 2 || radix > 36) { 254 | throw new RangeError( 255 | 'toString() radix argument must be between 2 and 36', 256 | ); 257 | } 258 | const digitStr = '0123456789abcdefghijklmnopqrstuvwxyz'; 259 | let result = ''; 260 | if (radix === 2 || radix === 16) { 261 | let value = this.value[0]; 262 | while (value !== 0) { 263 | result = digitStr[value % radix] + result; 264 | value = Math.floor(value / radix); 265 | } 266 | value = this.value[1]; 267 | if (value !== 0) { 268 | const pad = 269 | radix === 2 ? '00000000000000000000000000000000' : '00000000'; 270 | result = (pad + result).slice(-pad.length); 271 | } 272 | while (value !== 0) { 273 | result = digitStr[value % radix] + result; 274 | value = Math.floor(value / radix); 275 | } 276 | if (result.length === 0) { 277 | result = '0'; 278 | } 279 | return result; 280 | } 281 | 282 | const zero = new Uint64(); 283 | let value = new Uint64(this); 284 | const uintRadix = new Uint64(radix); 285 | while (Uint64.cmp(value, zero) !== 0) { 286 | const digit = Uint64.mod(value, uintRadix).value[0]; 287 | result = digitStr[digit] + result; 288 | value = Uint64.div(value, uintRadix); 289 | } 290 | if (result.length === 0) { 291 | result = '0'; 292 | } 293 | return result; 294 | } 295 | 296 | toJSON() { 297 | return this.toString(); 298 | } 299 | 300 | toPostgres() { 301 | return new Int64(this); 302 | } 303 | } 304 | 305 | module.exports = { Uint64 }; 306 | 307 | ({ Int64 } = require('./int64')); 308 | -------------------------------------------------------------------------------- /lib/unicode-categories.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2018 Metarhia contributors. Use of this source code is 2 | // governed by the MIT license that can be found in the LICENSE file. 3 | // 4 | // 5 | // This file contains data derived from the Unicode Data Files. 6 | // The following license applies to this data: 7 | // 8 | // COPYRIGHT AND PERMISSION NOTICE 9 | // 10 | // Copyright © 1991-2018 Unicode, Inc. All rights reserved. 11 | // Distributed under the Terms of Use in http://www.unicode.org/copyright.html. 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining 14 | // a copy of the Unicode data files and any associated documentation 15 | // (the "Data Files") or Unicode software and any associated documentation 16 | // (the "Software") to deal in the Data Files or Software 17 | // without restriction, including without limitation the rights to use, 18 | // copy, modify, merge, publish, distribute, and/or sell copies of 19 | // the Data Files or Software, and to permit persons to whom the Data Files 20 | // or Software are furnished to do so, provided that either 21 | // (a) this copyright and permission notice appear with all copies 22 | // of the Data Files or Software, or 23 | // (b) this copyright and permission notice appear in associated 24 | // Documentation. 25 | // 26 | // THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF 27 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 28 | // WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 29 | // NONINFRINGEMENT OF THIRD PARTY RIGHTS. 30 | // IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS 31 | // NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL 32 | // DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, 33 | // DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 34 | // TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 35 | // PERFORMANCE OF THE DATA FILES OR SOFTWARE. 36 | // 37 | // Except as contained in this notice, the name of a copyright holder 38 | // shall not be used in advertising or otherwise to promote the sale, 39 | // use or other dealings in these Data Files or Software without prior 40 | // written authorization of the copyright holder. 41 | // 42 | // 43 | // This file is automatically generated by tools/unicode-category-parcer.js. 44 | // Unicode version 11.0.0. 45 | // 46 | // Do not edit this file manually! 47 | /* eslint-disable */ 48 | module.exports = {"Lu":[[65,90],[192,214],[216,222],256,258,260,262,264,266,268,270,272,274,276,278,280,282,284,286,288,290,292,294,296,298,300,302,304,306,308,310,313,315,317,319,321,323,325,327,330,332,334,336,338,340,342,344,346,348,350,352,354,356,358,360,362,364,366,368,370,372,374,[376,377],379,381,[385,386],388,[390,391],[393,395],[398,401],[403,404],[406,408],[412,413],[415,416],418,420,[422,423],425,428,[430,431],[433,435],437,[439,440],444,452,455,458,461,463,465,467,469,471,473,475,478,480,482,484,486,488,490,492,494,497,500,[502,504],506,508,510,512,514,516,518,520,522,524,526,528,530,532,534,536,538,540,542,544,546,548,550,552,554,556,558,560,562,[570,571],[573,574],577,[579,582],584,586,588,590,880,882,886,895,902,[904,911],[913,939],975,[978,980],984,986,988,990,992,994,996,998,1000,1002,1004,1006,1012,1015,[1017,1018],[1021,1071],1120,1122,1124,1126,1128,1130,1132,1134,1136,1138,1140,1142,1144,1146,1148,1150,1152,1162,1164,1166,1168,1170,1172,1174,1176,1178,1180,1182,1184,1186,1188,1190,1192,1194,1196,1198,1200,1202,1204,1206,1208,1210,1212,1214,[1216,1217],1219,1221,1223,1225,1227,1229,1232,1234,1236,1238,1240,1242,1244,1246,1248,1250,1252,1254,1256,1258,1260,1262,1264,1266,1268,1270,1272,1274,1276,1278,1280,1282,1284,1286,1288,1290,1292,1294,1296,1298,1300,1302,1304,1306,1308,1310,1312,1314,1316,1318,1320,1322,1324,1326,[1329,1366],[4256,4301],[5024,5109],[7312,7359],7680,7682,7684,7686,7688,7690,7692,7694,7696,7698,7700,7702,7704,7706,7708,7710,7712,7714,7716,7718,7720,7722,7724,7726,7728,7730,7732,7734,7736,7738,7740,7742,7744,7746,7748,7750,7752,7754,7756,7758,7760,7762,7764,7766,7768,7770,7772,7774,7776,7778,7780,7782,7784,7786,7788,7790,7792,7794,7796,7798,7800,7802,7804,7806,7808,7810,7812,7814,7816,7818,7820,7822,7824,7826,7828,7838,7840,7842,7844,7846,7848,7850,7852,7854,7856,7858,7860,7862,7864,7866,7868,7870,7872,7874,7876,7878,7880,7882,7884,7886,7888,7890,7892,7894,7896,7898,7900,7902,7904,7906,7908,7910,7912,7914,7916,7918,7920,7922,7924,7926,7928,7930,7932,7934,[7944,7951],[7960,7965],[7976,7983],[7992,7999],[8008,8013],[8025,8031],[8040,8047],[8120,8123],[8136,8139],[8152,8155],[8168,8172],[8184,8187],8450,8455,[8459,8461],[8464,8466],8469,[8473,8477],8484,8486,8488,[8490,8493],[8496,8499],[8510,8511],8517,8579,[11264,11310],11360,[11362,11364],11367,11369,11371,[11373,11376],11378,11381,[11390,11392],11394,11396,11398,11400,11402,11404,11406,11408,11410,11412,11414,11416,11418,11420,11422,11424,11426,11428,11430,11432,11434,11436,11438,11440,11442,11444,11446,11448,11450,11452,11454,11456,11458,11460,11462,11464,11466,11468,11470,11472,11474,11476,11478,11480,11482,11484,11486,11488,11490,11499,11501,11506,42560,42562,42564,42566,42568,42570,42572,42574,42576,42578,42580,42582,42584,42586,42588,42590,42592,42594,42596,42598,42600,42602,42604,42624,42626,42628,42630,42632,42634,42636,42638,42640,42642,42644,42646,42648,42650,42786,42788,42790,42792,42794,42796,42798,42802,42804,42806,42808,42810,42812,42814,42816,42818,42820,42822,42824,42826,42828,42830,42832,42834,42836,42838,42840,42842,42844,42846,42848,42850,42852,42854,42856,42858,42860,42862,42873,42875,[42877,42878],42880,42882,42884,42886,42891,42893,42896,42898,42902,42904,42906,42908,42910,42912,42914,42916,42918,42920,[42922,42926],[42928,42932],42934,42936,[65313,65338],[66560,66599],[66736,66771],[68736,68786],[71840,71871],[93760,93791],[119808,119833],[119860,119885],[119912,119937],[119964,119989],[120016,120041],[120068,120092],[120120,120144],[120172,120197],[120224,120249],[120276,120301],[120328,120353],[120380,120405],[120432,120457],[120488,120512],[120546,120570],[120604,120628],[120662,120686],[120720,120744],120778,[125184,125217]],"Ll":[[97,122],181,[223,246],[248,255],257,259,261,263,265,267,269,271,273,275,277,279,281,283,285,287,289,291,293,295,297,299,301,303,305,307,309,[311,312],314,316,318,320,322,324,326,[328,329],331,333,335,337,339,341,343,345,347,349,351,353,355,357,359,361,363,365,367,369,371,373,375,378,380,[382,384],387,389,392,[396,397],402,405,[409,411],414,417,419,421,424,[426,427],429,432,436,438,[441,442],[445,447],454,457,460,462,464,466,468,470,472,474,[476,477],479,481,483,485,487,489,491,493,[495,496],499,501,505,507,509,511,513,515,517,519,521,523,525,527,529,531,533,535,537,539,541,543,545,547,549,551,553,555,557,559,561,[563,569],572,[575,576],578,583,585,587,589,[591,659],[661,687],881,883,887,[891,893],912,[940,974],[976,977],[981,983],985,987,989,991,993,995,997,999,1001,1003,1005,[1007,1011],1013,1016,[1019,1020],[1072,1119],1121,1123,1125,1127,1129,1131,1133,1135,1137,1139,1141,1143,1145,1147,1149,1151,1153,1163,1165,1167,1169,1171,1173,1175,1177,1179,1181,1183,1185,1187,1189,1191,1193,1195,1197,1199,1201,1203,1205,1207,1209,1211,1213,1215,1218,1220,1222,1224,1226,1228,[1230,1231],1233,1235,1237,1239,1241,1243,1245,1247,1249,1251,1253,1255,1257,1259,1261,1263,1265,1267,1269,1271,1273,1275,1277,1279,1281,1283,1285,1287,1289,1291,1293,1295,1297,1299,1301,1303,1305,1307,1309,1311,1313,1315,1317,1319,1321,1323,1325,1327,[1376,1416],[4304,4346],[4349,4351],[5112,5117],[7296,7304],[7424,7467],[7531,7543],[7545,7578],7681,7683,7685,7687,7689,7691,7693,7695,7697,7699,7701,7703,7705,7707,7709,7711,7713,7715,7717,7719,7721,7723,7725,7727,7729,7731,7733,7735,7737,7739,7741,7743,7745,7747,7749,7751,7753,7755,7757,7759,7761,7763,7765,7767,7769,7771,7773,7775,7777,7779,7781,7783,7785,7787,7789,7791,7793,7795,7797,7799,7801,7803,7805,7807,7809,7811,7813,7815,7817,7819,7821,7823,7825,7827,[7829,7837],7839,7841,7843,7845,7847,7849,7851,7853,7855,7857,7859,7861,7863,7865,7867,7869,7871,7873,7875,7877,7879,7881,7883,7885,7887,7889,7891,7893,7895,7897,7899,7901,7903,7905,7907,7909,7911,7913,7915,7917,7919,7921,7923,7925,7927,7929,7931,7933,[7935,7943],[7952,7957],[7968,7975],[7984,7991],[8000,8005],[8016,8023],[8032,8039],[8048,8071],[8080,8087],[8096,8103],[8112,8119],8126,[8130,8135],[8144,8151],[8160,8167],[8178,8183],8458,[8462,8463],8467,8495,8500,8505,[8508,8509],[8518,8521],8526,8580,[11312,11358],11361,[11365,11366],11368,11370,11372,11377,[11379,11380],[11382,11387],11393,11395,11397,11399,11401,11403,11405,11407,11409,11411,11413,11415,11417,11419,11421,11423,11425,11427,11429,11431,11433,11435,11437,11439,11441,11443,11445,11447,11449,11451,11453,11455,11457,11459,11461,11463,11465,11467,11469,11471,11473,11475,11477,11479,11481,11483,11485,11487,11489,[11491,11492],11500,11502,11507,[11520,11565],42561,42563,42565,42567,42569,42571,42573,42575,42577,42579,42581,42583,42585,42587,42589,42591,42593,42595,42597,42599,42601,42603,42605,42625,42627,42629,42631,42633,42635,42637,42639,42641,42643,42645,42647,42649,42651,42787,42789,42791,42793,42795,42797,[42799,42801],42803,42805,42807,42809,42811,42813,42815,42817,42819,42821,42823,42825,42827,42829,42831,42833,42835,42837,42839,42841,42843,42845,42847,42849,42851,42853,42855,42857,42859,42861,42863,[42865,42872],42874,42876,42879,42881,42883,42885,42887,42892,42894,42897,[42899,42901],42903,42905,42907,42909,42911,42913,42915,42917,42919,42921,42927,42933,42935,42937,43002,[43824,43866],[43872,43967],[64256,64279],[65345,65370],[66600,66639],[66776,66811],[68800,68850],[71872,71903],[93792,93823],[119834,119859],[119886,119911],[119938,119963],[119990,120015],[120042,120067],[120094,120119],[120146,120171],[120198,120223],[120250,120275],[120302,120327],[120354,120379],[120406,120431],[120458,120485],[120514,120538],[120540,120545],[120572,120596],[120598,120603],[120630,120654],[120656,120661],[120688,120712],[120714,120719],[120746,120770],[120772,120777],120779,[125218,125251]]} 49 | -------------------------------------------------------------------------------- /lib/units.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const DURATION_UNITS = { 4 | d: 86400, // days 5 | h: 3600, // hours 6 | m: 60, // minutes 7 | s: 1, // seconds 8 | }; 9 | 10 | // Parse duration to seconds 11 | // s - , duration syntax 12 | // Returns: , milliseconds 13 | // 14 | // Example: duration('1d 10h 7m 13s') 15 | const duration = (s) => { 16 | if (typeof s === 'number') return s; 17 | if (typeof s !== 'string') return 0; 18 | let result = 0; 19 | const parts = s.split(' '); 20 | for (const part of parts) { 21 | const unit = part.slice(-1); 22 | const value = parseInt(part.slice(0, -1)); 23 | const mult = DURATION_UNITS[unit]; 24 | if (!isNaN(value) && mult) result += value * mult; 25 | } 26 | return result * 1000; 27 | }; 28 | 29 | const UNITS_MAX_DURATION = { 30 | s: 60, // seconds 31 | m: 60, // minutes 32 | h: 24, // hours 33 | }; 34 | 35 | // Convert integer duration to string 36 | // n - , duration 37 | // Returns: 38 | const durationToString = (n) => { 39 | if (typeof n !== 'number' || !n) return '0s'; 40 | n = Math.floor(n / 1000); 41 | const parts = []; 42 | for (const unit in UNITS_MAX_DURATION) { 43 | const mult = UNITS_MAX_DURATION[unit]; 44 | const remainder = n % mult; 45 | if (remainder) parts.push(remainder + unit); 46 | n = Math.floor(n / mult); 47 | } 48 | if (n) parts.push(n + 'd'); 49 | return parts.reverse().join(' '); 50 | }; 51 | 52 | const SIZE_UNITS = ['', ' Kb', ' Mb', ' Gb', ' Tb', ' Pb', ' Eb', ' Zb', ' Yb']; 53 | 54 | // Convert integer to string, representing data size in Kb, Mb, Gb, and Tb 55 | // bytes - , size 56 | // Returns: 57 | const bytesToSize = (bytes) => { 58 | if (bytes === 0) return '0'; 59 | const exp = Math.floor(Math.log(bytes) / Math.log(1000)); 60 | const size = bytes / 1000 ** exp; 61 | const short = Math.round(size, 2); 62 | const unit = SIZE_UNITS[exp]; 63 | return short + unit; 64 | }; 65 | 66 | const UNIT_SIZES = { 67 | yb: 24, // yottabyte 68 | zb: 21, // zettabyte 69 | eb: 18, // exabyte 70 | pb: 15, // petabyte 71 | tb: 12, // terabyte 72 | gb: 9, // gigabyte 73 | mb: 6, // megabyte 74 | kb: 3, // kilobyte 75 | }; 76 | 77 | // Convert string with data size to integer 78 | // size - , size 79 | // Returns: 80 | const sizeToBytes = (size) => { 81 | if (typeof size === 'number') return size; 82 | const [num, unit] = size.toLowerCase().split(' '); 83 | const exp = UNIT_SIZES[unit]; 84 | const value = parseInt(num, 10); 85 | if (!exp) return value; 86 | return value * 10 ** exp; 87 | }; 88 | 89 | module.exports = { 90 | duration, 91 | durationToString, 92 | bytesToSize, 93 | sizeToBytes, 94 | }; 95 | -------------------------------------------------------------------------------- /lib/utilities.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { basename } = require('path'); 4 | 5 | // Make function raise-safe 6 | // fn - 7 | // Returns: , function(...args), wrapped with try/catch interception 8 | // args - , arguments to be passed to wrapped function 9 | const safe = 10 | (fn) => 11 | (...args) => { 12 | try { 13 | return [null, fn(...args)]; 14 | } catch (err) { 15 | return [err, null]; 16 | } 17 | }; 18 | 19 | const captureMaxStack = () => { 20 | const oldLimit = Error.stackTraceLimit; 21 | Error.stackTraceLimit = Infinity; 22 | const stack = new Error().stack; 23 | Error.stackTraceLimit = oldLimit; 24 | return stack; 25 | }; 26 | 27 | const nodeModuleMatch = /internal[/\\]modules[/\\](cjs|esm)[/\\]/; 28 | 29 | // Try to detect the filepath of a caller of this function. 30 | // Signature: depth = 0, stack = null 31 | // depth | initial stack slice or filter regular expression, 32 | // 0 by default. 33 | // stack stack string, optional 34 | const callerFilepath = (depth = 0, stack = null) => { 35 | if (!stack) { 36 | // remove first 'Error' line, captureMaxStack and this function 37 | stack = captureMaxStack().split('\n').slice(3); 38 | } else { 39 | // remove first 'Error' line and this function 40 | stack = stack.split('\n').slice(2); 41 | } 42 | 43 | const filters = [nodeModuleMatch]; 44 | if (typeof depth === 'number') { 45 | if (depth > stack.length - 1) depth = stack.length - 1; 46 | if (depth > 0) stack = stack.slice(depth); 47 | } else { 48 | filters.push(depth); 49 | } 50 | 51 | const testFilters = (frame) => filters.some((f) => f.test(frame)); 52 | let frame = null; 53 | do { 54 | frame = stack.shift(); 55 | } while (frame && testFilters(frame)); 56 | 57 | if (frame) { 58 | const start = frame.indexOf('(') + 1; 59 | const lastColon = frame.lastIndexOf(':'); 60 | const end = frame.lastIndexOf(':', lastColon - 1); 61 | return frame.substring(start, end); 62 | } 63 | return ''; 64 | }; 65 | 66 | const callerFilename = (depth = 0, stack = null) => 67 | basename( 68 | callerFilepath(typeof depth === 'number' ? depth + 1 : depth, stack), 69 | ); 70 | 71 | module.exports = { 72 | safe, 73 | captureMaxStack, 74 | callerFilename, 75 | callerFilepath, 76 | }; 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@metarhia/common", 3 | "version": "2.2.2", 4 | "author": "Timur Shemsedinov ", 5 | "description": "Metarhia Common Library", 6 | "license": "MIT", 7 | "keywords": [ 8 | "metarhia", 9 | "common", 10 | "impress", 11 | "jstp", 12 | "globalstorege", 13 | "metasync" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/metarhia/common" 18 | }, 19 | "main": "common.js", 20 | "exports": { 21 | ".": { 22 | "require": "./common.js", 23 | "import": "./common.mjs" 24 | }, 25 | "./lib/": { 26 | "require": "./lib/" 27 | } 28 | }, 29 | "browser": { 30 | "common.js": "./dist/common.js", 31 | "dist/lib/fs.js": false, 32 | "dist/lib/network.js": false, 33 | "dist/lib/stream.js": false 34 | }, 35 | "readmeFilename": "README.md", 36 | "files": [ 37 | "common.mjs", 38 | "lib/", 39 | "dist/" 40 | ], 41 | "scripts": { 42 | "test": "npm run -s lint && metatests test/ && for test in ./sequential-test/*.js; do metatests \"$test\"; done", 43 | "lint": "eslint . && prettier -c \"**/*.js\" \"**/*.json\" \"**/*.md\" \".*rc\" \"**/*.yml\"", 44 | "fmt": "prettier --write \"**/*.js\" \"**/*.json\" \"**/*.md\" \".*rc\" \"**/*.yml\"", 45 | "doc": "metadoc", 46 | "build": "babel common.js -d dist && babel lib -d dist/lib && node tools/esmodules-export-gen.js", 47 | "prepublish": "npm run -s build" 48 | }, 49 | "engines": { 50 | "node": ">=12.0.0" 51 | }, 52 | "devDependencies": { 53 | "@babel/cli": "^7.22.9", 54 | "@babel/core": "^7.22.9", 55 | "@babel/preset-env": "^7.22.9", 56 | "@metarhia/doc": "^0.7.0", 57 | "eslint": "^8.45.0", 58 | "eslint-config-metarhia": "^8.2.0", 59 | "eslint-config-prettier": "^8.8.0", 60 | "eslint-plugin-import": "^2.27.5", 61 | "eslint-plugin-prettier": "^5.0.0", 62 | "metatests": "^0.8.2", 63 | "prettier": "^3.0.0" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /sequential-test/time.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metatests = require('metatests'); 4 | const common = require('..'); 5 | 6 | const DefaultDate = Date; 7 | const dateNowValue = 1561975200000; // 2019-07-01T10:00:00.000Z 8 | // eslint-disable-next-line no-global-assign 9 | Date = function (...args) { 10 | if (!new.target) { 11 | return DefaultDate.apply(DefaultDate, args); 12 | } 13 | if (args.length !== 0) { 14 | return new DefaultDate(...args); 15 | } 16 | return new DefaultDate(dateNowValue); 17 | }; 18 | Date.now = function now() { 19 | return dateNowValue; 20 | }; 21 | 22 | metatests.case( 23 | 'Common / date & time', 24 | { common }, 25 | { 26 | 'common.isTimeEqual': [ 27 | ['2014-01-01', '2014-01-01', true], 28 | ['2014-01-01', '2014-01-02', false], 29 | ['1234-12-12', '1234-12-12', true], 30 | ['1234-12-12', '4321-12-21', false], 31 | ['December 17, 1995 03:24:00', '1995-12-17T03:24:00', true], 32 | ], 33 | 'common.nowDate': [ 34 | [new Date('2014-12-12 12:30:15.150'), '2014-12-12'], 35 | [new Date('2014-12-12 12:30:15'), '2014-12-12'], 36 | [new Date('2014-12-12 12:30'), '2014-12-12'], 37 | [new Date('2014-12-12'), '2014-12-12'], 38 | [new Date('1995-12-17T03:24:00Z'), '1995-12-17'], 39 | ['2019-07-01'], 40 | ], 41 | 'common.nowDateTime': [ 42 | [new Date('2014-12-12 12:30:15.150Z'), '2014-12-12 12:30'], 43 | [new Date('2014-12-12 12:30:15Z'), '2014-12-12 12:30'], 44 | [new Date('2014-12-12 12:30Z'), '2014-12-12 12:30'], 45 | [new Date('2014-12-12Z'), '2014-12-12 00:00'], 46 | [new Date('1995-12-17T03:24:00Z'), '1995-12-17 03:24'], 47 | ['2019-07-01 10:00'], 48 | ], 49 | }, 50 | ); 51 | -------------------------------------------------------------------------------- /test/array.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metatests = require('metatests'); 4 | const common = require('..'); 5 | 6 | metatests.case( 7 | 'Common / array', 8 | { common }, 9 | { 10 | 'common.splitAt': [ 11 | [0, [1, 2, 3, 4, 5], [[], [1, 2, 3, 4, 5]]], 12 | [1, [1, 2, 3, 4, 5], [[1], [2, 3, 4, 5]]], 13 | [ 14 | 2, 15 | [1, 2, 3, 4, 5], 16 | [ 17 | [1, 2], 18 | [3, 4, 5], 19 | ], 20 | ], 21 | [ 22 | 3, 23 | [1, 2, 3, 4, 5], 24 | [ 25 | [1, 2, 3], 26 | [4, 5], 27 | ], 28 | ], 29 | [4, [1, 2, 3, 4, 5], [[1, 2, 3, 4], [5]]], 30 | [5, [1, 2, 3, 4, 5], [[1, 2, 3, 4, 5], []]], 31 | ], 32 | 'common.last': [ 33 | [[5], 5], 34 | [[1, 2, 3, 4, 5], 5], 35 | [[true, true, false], false], 36 | [[false, true, true], true], 37 | [[], undefined], 38 | ], 39 | 'common.range': [ 40 | [1, 5, [1, 2, 3, 4, 5]], 41 | [5, 1, []], 42 | [1, 0, []], 43 | [1, 1, [1]], 44 | [8, 9, [8, 9]], 45 | ], 46 | 'common.sequence': [ 47 | [ 48 | [80, 81, 82], 49 | [80, 81, 82], 50 | ], 51 | [ 52 | // eslint-disable-next-line no-sparse-arrays 53 | [40, , 45], 54 | [40, 41, 42, 43, 44, 45], 55 | ], 56 | [ 57 | [40, [6]], 58 | [40, 41, 42, 43, 44, 45], 59 | ], 60 | [[40, [-3]], 6, [40, 41, 42]], 61 | ], 62 | 'common.shuffle': [ 63 | [[1, 2, 3], (result) => JSON.stringify(result.sort()) === '[1,2,3]'], 64 | [['a', 'b'], (result) => JSON.stringify(result.sort()) === '["a","b"]'], 65 | [[1, 'a', 3], (result) => JSON.stringify(result.sort()) === '[1,3,"a"]'], 66 | [[], (result) => JSON.stringify(result.sort()) === '[]'], 67 | ], 68 | 'common.sample': [ 69 | [[1, 2, 3], (result) => [1, 2, 3].includes(result)], 70 | [['a', 'b', 'c'], (result) => ['a', 'b', 'c'].includes(result)], 71 | ], 72 | }, 73 | ); 74 | 75 | metatests.test('array / pushSame', (test) => { 76 | const array = [1, 2, 3]; 77 | test.strictSame(common.pushSame(array, 0, 1), 3); 78 | test.strictSame(array, [1, 2, 3]); 79 | test.strictSame(common.pushSame(array, 5, 0), 8); 80 | test.strictSame(array, [1, 2, 3, 0, 0, 0, 0, 0]); 81 | test.end(); 82 | }); 83 | 84 | metatests.test('array / shuffle uniform distribution', (test) => { 85 | const N = 1e7; 86 | const dist = { 87 | abc: 0, 88 | acb: 0, 89 | bac: 0, 90 | bca: 0, 91 | cab: 0, 92 | cba: 0, 93 | }; 94 | for (let i = 0; i < N; i++) { 95 | const arr = ['a', 'b', 'c']; 96 | const key = common.shuffle(arr).join(''); 97 | dist[key]++; 98 | } 99 | test.assert( 100 | Object.values(dist) 101 | .map((c) => Math.round((c / N) * 100)) 102 | .every((c, i, arr) => c === arr[0]), 103 | ); 104 | test.end(); 105 | }); 106 | -------------------------------------------------------------------------------- /test/btree.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { BTree } = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | metatests.test('basic set', (test) => { 7 | const bTree = new BTree(2); 8 | 9 | bTree.set(40, { city: 'Abu Dhabi' }); 10 | 11 | test.strictSame(bTree.root.length, 2); 12 | test.strictSame(bTree.root[0].key, 40); 13 | test.strictSame(bTree.root[0].data, { city: 'Abu Dhabi' }); 14 | test.strictSame(bTree.root[0].child, null); 15 | 16 | bTree.set(30, { city: 'Ankara' }); 17 | bTree.set(50, { city: 'Jerusalem' }); 18 | test.strictSame(bTree.root.length, 4); 19 | test.strictSame(bTree.root[0].key, 30); 20 | test.strictSame(bTree.root[0].data, { city: 'Ankara' }); 21 | test.strictSame(bTree.root[1].key, 40); 22 | test.strictSame(bTree.root[2].key, 50); 23 | test.strictSame(bTree.root[3].key, undefined); // Special empty element 24 | test.end(); 25 | }); 26 | 27 | metatests.test('set with spliting root', (test) => { 28 | const bTree = new BTree(2); 29 | bTree 30 | .set(40, { city: 'Abu Dhabi' }) 31 | .set(100, { city: 'Ankara' }) 32 | .set(20, { city: 'Jerusalem' }) 33 | .set(0, { city: 'Seoul' }); 34 | // 40 35 | // / \ 36 | // 0 20 100 37 | test.strictSame(bTree.root[0].key, 40); 38 | test.strictSame(bTree.root[1].key, undefined); 39 | test.strictSame(bTree.root[0].child[0].key, 0); 40 | test.strictSame(bTree.root[0].child[0].data, { city: 'Seoul' }); 41 | test.strictSame(bTree.root[0].child[1].key, 20); 42 | test.strictSame(bTree.root[0].child[2].key, undefined); 43 | test.strictSame(bTree.root[1].child[0].key, 100); 44 | test.strictSame(bTree.root[1].child[1].key, undefined); 45 | test.end(); 46 | }); 47 | 48 | metatests.test('Insert key, which already are in', (test) => { 49 | const bTree = new BTree(2); 50 | bTree 51 | .set('first', { data: 'first set' }) 52 | .set('second', { data: 'second set' }) 53 | .set('third', { data: 'third set' }) 54 | .set('fouth', { data: 'fouth set' }) 55 | .set('fouth') 56 | .set('fifth', { data: 'fifth set' }) 57 | .set('first'); 58 | 59 | let result = bTree.get('first'); 60 | test.strictSame(result, undefined); 61 | 62 | result = bTree.get('fouth'); 63 | test.strictSame(result, undefined); 64 | test.end(); 65 | }); 66 | 67 | metatests.test('set with spliting internal node', (test) => { 68 | const bTree = new BTree(3); 69 | 70 | bTree 71 | .set(20, { city: 'Abu Dhabi' }) 72 | .set(50, { city: 'Ankara' }) 73 | .set(100, { city: 'Jerusalem' }) 74 | .set(150, { city: 'Seoul' }) 75 | .set(200, { city: 'Kiev' }) 76 | .set(0, { city: 'Luxembourg' }) 77 | .set(160, { city: 'Minsk' }) 78 | .set(180, { city: 'Oslo' }) 79 | .set(170, { city: 'Stockholm' }); 80 | // 100 81 | // / \ 82 | // 0 20 50 150 160 170 180 200 83 | bTree.set(190, { city: 'Washington' }); // splitting left node 84 | // 100 170 85 | // / \ \ 86 | // 0 20 50 150 160 180 190 200 87 | 88 | test.strictSame(bTree.root.length, 3); 89 | test.strictSame(bTree.root[0].key, 100); 90 | test.strictSame(bTree.root[1].key, 170); 91 | test.strictSame(bTree.root[0].child[0].key, 0); 92 | test.strictSame(bTree.root[0].child[2].key, 50); 93 | test.strictSame(bTree.root[1].child[1].key, 160); 94 | test.strictSame(bTree.root[2].child[1].key, 190); 95 | test.end(); 96 | }); 97 | 98 | metatests.test('get / number keys', (test) => { 99 | const bTree = new BTree(3); 100 | 101 | bTree 102 | .set(20, { city: 'Abu Dhabi' }) 103 | .set(50, { city: 'Ankara' }) 104 | .set(100, { city: 'Jerusalem' }) 105 | .set(150, { city: 'Seoul' }) 106 | .set(200, { city: 'Kiev' }) 107 | .set(0, { city: 'Luxembourg' }) 108 | .set(160, { city: 'Minsk' }) 109 | .set(180, { city: 'Oslo' }) 110 | .set(170, { city: 'Stockholm' }); 111 | 112 | test.strictSame(bTree.get(20), { city: 'Abu Dhabi' }); 113 | test.strictSame(bTree.get(50), { city: 'Ankara' }); 114 | test.strictSame(bTree.get(100), { city: 'Jerusalem' }); 115 | test.strictSame(bTree.get(150), { city: 'Seoul' }); 116 | test.strictSame(bTree.get(0), { city: 'Luxembourg' }); 117 | test.strictSame(bTree.get(170), { city: 'Stockholm' }); 118 | test.end(); 119 | }); 120 | 121 | metatests.test('get / string keys', (test) => { 122 | const bTree = new BTree(3); 123 | 124 | bTree 125 | .set('Abu Dhabi', { country: 'UAE' }) 126 | .set('Ankara', { country: 'Turkey' }) 127 | .set('Jerusalem', { country: 'Israel' }) 128 | .set('Seoul', { country: 'South Korea' }) 129 | .set('Kiev', { country: 'Ukraine' }) 130 | .set('Luxembourg', { country: 'Luxembourg' }) 131 | .set('Minsk', { country: 'Belarus' }) 132 | .set('Oslo', { country: 'Norway' }) 133 | .set('Stockholm', { country: 'Sweden' }); 134 | 135 | test.strictSame(bTree.get('Abu Dhabi'), { country: 'UAE' }); 136 | test.strictSame(bTree.get('Ankara'), { country: 'Turkey' }); 137 | test.strictSame(bTree.get('Kiev'), { country: 'Ukraine' }); 138 | test.strictSame(bTree.get('Minsk'), { country: 'Belarus' }); 139 | test.strictSame(bTree.get('Oslo'), { country: 'Norway' }); 140 | test.strictSame(bTree.get('Stockholm'), { country: 'Sweden' }); 141 | test.end(); 142 | }); 143 | 144 | metatests.test('iterator / numer key', (test) => { 145 | const bTree = new BTree(2); 146 | 147 | bTree 148 | .set(40, 'G') 149 | .set(30, 'F') 150 | .set(72, 'M') 151 | .set(24, 'E') 152 | .set(20, 'D') 153 | .set(18, 'B') 154 | .set(50, 'I') 155 | .set(60, 'L') 156 | .set(90, 'N') 157 | .set(45, 'H') 158 | .set(55, 'K') 159 | .set(52, 'J') 160 | .set(100, 'O') 161 | .set(120, 'P') 162 | .set(130, 'Q') 163 | .set(140, 'R') 164 | .set(19, 'C') 165 | .set(-20, 'A'); 166 | 167 | let res = [...bTree.iterator()].join(''); // All nodes 168 | test.strictSame(res, 'ABCDEFGHIJKLMNOPQR'); 169 | 170 | res = [...bTree.iterator(undefined, 10000)].join(''); // All nodes 171 | test.strictSame(res, 'ABCDEFGHIJKLMNOPQR'); 172 | 173 | res = [...bTree.iterator(-10000)].join(''); // All nodes 174 | test.strictSame(res, 'ABCDEFGHIJKLMNOPQR'); 175 | 176 | res = [...bTree.iterator(100, 0)].join(''); // Empty 177 | test.strictSame(res, ''); 178 | 179 | res = [...bTree.iterator(-200, -100)].join(''); // Empty 180 | test.strictSame(res, ''); 181 | 182 | res = [...bTree.iterator(20, 50)].join(''); 183 | test.strictSame(res, 'DEFGH'); 184 | 185 | res = [...bTree.iterator(100, 1000)].join(''); 186 | test.strictSame(res, 'OPQR'); 187 | 188 | res = [...bTree.iterator(10, 99)].join(''); 189 | test.strictSame(res, 'BCDEFGHIJKLMN'); 190 | 191 | res = [...bTree.iterator(10)].join(''); 192 | test.strictSame(res, 'BCDEFGHIJKLMNOPQR'); 193 | 194 | res = [...bTree.iterator(126, 128)].join(''); 195 | test.strictSame(res, ''); 196 | test.end(); 197 | }); 198 | 199 | metatests.test('iterator / string keys', (test) => { 200 | const bTree = new BTree(); 201 | 202 | bTree 203 | .set('Abu Dhabi', 1) 204 | .set('Ankara', 2) 205 | .set('Jerusalem', 3) 206 | .set('Seoul', 8) 207 | .set('Kiev', 4) 208 | .set('Luxembourg', 5) 209 | .set('Minsk', 6) 210 | .set('Oslo', 7) 211 | .set('Stockholm', 9); 212 | 213 | let res = [...bTree.iterator('A', 'C')]; 214 | test.strictSame(res, [1, 2]); 215 | 216 | res = [...bTree.iterator('Abu Dhabi', 'C')]; 217 | test.strictSame(res, [1, 2]); 218 | 219 | res = [...bTree.iterator('D', 'Z')]; 220 | test.strictSame(res, [3, 4, 5, 6, 7, 8, 9]); 221 | 222 | res = [...bTree.iterator()]; 223 | test.strictSame(res, [1, 2, 3, 4, 5, 6, 7, 8, 9]); 224 | 225 | res = [...bTree.iterator('Oslo')]; 226 | test.strictSame(res, [7, 8, 9]); 227 | test.end(); 228 | }); 229 | 230 | metatests.test('remove', (test) => { 231 | const bTree = new BTree(2); 232 | bTree 233 | .set(40, { city: 'Abu Dhabi' }) 234 | .set(30, { city: 'Ankara' }) 235 | .set(80, { city: 'Jerusalem' }) 236 | .set(20, { city: 'Seoul' }) 237 | .set(100, { city: 'Kiev' }) 238 | .set(110, { city: 'Luxembourg' }) 239 | .set(18, { city: 'Minsk' }) 240 | .set(135, { city: 'Oslo' }) 241 | .set(50, { city: 'Stockholm' }) 242 | .set(25, { city: 'Washington' }) 243 | .set(62, { city: 'Lima' }) 244 | .set(64, { city: 'Beijing' }); 245 | 246 | bTree.remove(40); 247 | let res = bTree.get(40); // Remove root 248 | test.strictSame(res, undefined); 249 | test.strictSame(bTree.root[0].key, 50); 250 | 251 | res = bTree.remove(50); // Remove root 252 | test.strictSame(res.city, 'Stockholm'); 253 | test.strictSame(bTree.root[0].key, 62); 254 | 255 | res = bTree.remove(100); 256 | test.strictSame(res.city, 'Kiev'); 257 | test.strictSame(bTree.root.length, 4); 258 | 259 | res = bTree.remove(62); 260 | test.strictSame(res.city, 'Lima'); 261 | test.strictSame(bTree.root.length, 4); 262 | 263 | res = bTree.remove(18); 264 | test.strictSame(res.city, 'Minsk'); 265 | 266 | bTree.remove(80); 267 | bTree.remove(64); 268 | bTree.remove(135); 269 | 270 | res = bTree.remove(0); // Try to remove nonexistent node 271 | test.strictSame(res, undefined); 272 | 273 | bTree.remove(25); 274 | bTree.remove(30); 275 | bTree.remove(20); 276 | bTree.remove(110); 277 | // Now bTree is empty 278 | test.strictSame(bTree.root.length, 1); 279 | test.strictSame(bTree.root[0].key, undefined); 280 | test.strictSame(bTree.root[0].child, null); 281 | res = bTree.remove(0); // Try to remove nonexistent node 282 | test.strictSame(res, undefined); 283 | 284 | bTree 285 | .set(40, { city: 'Abu Dhabi' }) 286 | .set(50, { city: 'Ankara' }) 287 | .set(60, { city: 'Jerusalem' }) 288 | .set(30, { city: 'Seoul' }) 289 | .set(20, { city: 'Kiev' }) 290 | .set(19, { city: 'Luxembourg' }) 291 | .set(18, { city: 'Minsk' }) 292 | .set(17, { city: 'Oslo' }) 293 | .set(16, { city: 'Stockholm' }) 294 | .set(15, { city: 'Washington' }) 295 | .set(22, { city: 'Lima' }) 296 | .set(23, { city: 'Beijing' }); 297 | 298 | res = bTree.remove(30); 299 | test.strictSame(res, { city: 'Seoul' }); 300 | 301 | res = bTree.remove(23); 302 | test.strictSame(res, { city: 'Beijing' }); 303 | 304 | res = bTree.remove(22); 305 | test.strictSame(res, { city: 'Lima' }); 306 | 307 | res = bTree.remove(100); 308 | test.strictSame(res, undefined); 309 | test.end(); 310 | }); 311 | -------------------------------------------------------------------------------- /test/cache.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metatests = require('metatests'); 4 | const common = require('..'); 5 | 6 | metatests.test('cache create add get', (test) => { 7 | const cache = common.cache(); 8 | 9 | cache.add('key1', 'value1'); 10 | cache.add('key2', 'value2'); 11 | cache.add('key2', 'value3'); 12 | 13 | test.strictSame(cache.get('key1'), 'value1'); 14 | test.strictSame(cache.get('key2'), 'value3'); 15 | test.end(); 16 | }); 17 | 18 | metatests.test('cache del key', (test) => { 19 | const cache = common.cache(); 20 | 21 | cache.add('key1', 'value1'); 22 | cache.add('key2', 'value2'); 23 | cache.del('key1'); 24 | 25 | test.strictSame(cache.get('key1'), undefined); 26 | test.strictSame(cache.get('key2'), 'value2'); 27 | 28 | test.strictSame(cache.get('key3'), undefined); 29 | cache.del('key3'); 30 | test.strictSame(cache.get('key3'), undefined); 31 | 32 | test.end(); 33 | }); 34 | 35 | metatests.test('cache clr', (test) => { 36 | const cache = common.cache(); 37 | 38 | cache.add('key1', 'value1'); 39 | cache.add('str1', 'value2'); 40 | cache.clr('st'); 41 | 42 | test.strictSame(cache.get('key1'), 'value1'); 43 | test.strictSame(cache.get('str1'), undefined); 44 | test.end(); 45 | }); 46 | 47 | metatests.test('cache clr with fn', (test) => { 48 | const cache = common.cache(); 49 | cache.add('str1', 'value'); 50 | cache.add('str2', 'value'); 51 | const clrFn = test.mustCall((key, val) => { 52 | test.assert(key.startsWith('str')); 53 | test.strictSame(val, 'value'); 54 | }, 2); 55 | cache.clr('str', clrFn); 56 | test.end(); 57 | }); 58 | 59 | metatests.test('cache calcSize', (test) => { 60 | const cache = common.cache(); 61 | 62 | cache.add('key1', { length: 10, str: '0123456789' }); 63 | cache.add('key2', { length: 20, str: '01234567890123456789' }); 64 | cache.add('key2', { length: 30, str: '012345678901234567890123456789' }); 65 | 66 | test.strictSame(cache.get('key2').str, '012345678901234567890123456789'); 67 | test.strictSame(cache.allocated, 40); 68 | 69 | cache.add('key2', null); 70 | test.strictSame(cache.allocated, 10); 71 | 72 | test.end(); 73 | }); 74 | -------------------------------------------------------------------------------- /test/callbacks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metatests = require('metatests'); 4 | const common = require('..'); 5 | 6 | metatests.case( 7 | 'Common / callbacks', 8 | { common }, 9 | { 10 | 'common.falseness': [[[], false]], 11 | 'common.trueness': [[[], true]], 12 | 'common.emptyness': [[[], undefined]], 13 | }, 14 | ); 15 | 16 | metatests.test('unsafeCallback', (test) => { 17 | const callback = (...args) => { 18 | test.strictSame(args, [1, 2, 3]); 19 | test.end(); 20 | }; 21 | const args = [1, 2, 3, callback]; 22 | const cb = common.unsafeCallback(args); 23 | test.strictSame(cb, callback); 24 | cb(...args); 25 | }); 26 | 27 | metatests.test('unsafeCallback without callback', (test) => { 28 | const args = ['a', 'b', 'c']; 29 | const cb = common.unsafeCallback(args); 30 | test.strictSame(args, ['a', 'b', 'c']); 31 | test.strictSame(cb, null); 32 | test.end(); 33 | }); 34 | 35 | metatests.test('safeCallback', (test) => { 36 | const callback = (...args) => { 37 | test.strictSame(args, [10, 20, 30, 40, 50]); 38 | test.end(); 39 | }; 40 | const args = [10, 20, 30, 40, 50, callback]; 41 | const wrappedCb = common.safeCallback(args); 42 | test.strictSame(typeof wrappedCb, 'function'); 43 | wrappedCb(...args); 44 | }); 45 | 46 | metatests.test('safeCallback without callback', (test) => { 47 | const args = [11, 22, 33]; 48 | const wrappedCb = common.safeCallback(args); 49 | test.strictSame(args, [11, 22, 33]); 50 | test.strictSame(wrappedCb, common.emptiness); 51 | test.end(); 52 | }); 53 | 54 | metatests.test('safeCallback return emptiness', (test) => { 55 | const args = [3, 2, 1]; 56 | const wrappedCb = common.safeCallback(args); 57 | test.strictSame(wrappedCb, common.emptiness); 58 | wrappedCb(...args); 59 | test.end(); 60 | }); 61 | 62 | metatests.test('onceCallback prevent callback twice', (test) => { 63 | const callback = (...args) => { 64 | test.strictSame(args, ['A', 'B', 'C']); 65 | test.end(); 66 | }; 67 | const args = ['A', 'B', 'C', callback]; 68 | const wrappedCb = common.onceCallback(args); 69 | test.strictSame(typeof wrappedCb, 'function'); 70 | wrappedCb(...args); 71 | wrappedCb(...args); 72 | }); 73 | 74 | metatests.test('onceCallback without callback', (test) => { 75 | const args = ['A', 'B', 'C']; 76 | const wrappedCb = common.onceCallback(args); 77 | test.strictSame(wrappedCb, common.emptiness); 78 | test.end(); 79 | }); 80 | 81 | metatests.test('requiredCallback', (test) => { 82 | const callback = (...args) => { 83 | test.strictSame(args, [100, 200, 300]); 84 | test.end(); 85 | }; 86 | const args = [100, 200, 300, callback]; 87 | const wrappedCb = common.requiredCallback(args); 88 | test.strictSame(typeof wrappedCb, 'function'); 89 | wrappedCb(...args); 90 | }); 91 | 92 | metatests.test('requiredCallback raise', (test) => { 93 | const args = [-1, -2, -3]; 94 | try { 95 | const wrappedCb = common.requiredCallback(args); 96 | wrappedCb(...args); 97 | } catch (err) { 98 | test.strictSame(!!err, true); 99 | test.end(); 100 | } 101 | }); 102 | 103 | metatests.test('once', (test) => { 104 | const fn = () => { 105 | test.end(); 106 | }; 107 | const wrapped = common.once(fn); 108 | wrapped(); 109 | wrapped(); 110 | }); 111 | 112 | metatests.test('once without function', (test) => { 113 | const wrapped = common.once(null); 114 | test.strictSame(wrapped, common.emptiness); 115 | wrapped(); 116 | test.end(); 117 | }); 118 | 119 | metatests.test('nop', (test) => { 120 | const callback = (err) => { 121 | test.strictSame(err, null); 122 | test.end(); 123 | }; 124 | 125 | common.nop(callback); 126 | }); 127 | 128 | metatests.test('noop', (test) => { 129 | const incomingValue = 42; 130 | const callback = (err, result) => { 131 | test.strictSame(err, null); 132 | test.strictSame(result, null); 133 | test.end(); 134 | }; 135 | 136 | common.noop(incomingValue, callback); 137 | }); 138 | 139 | metatests.test('unsafeFunction', (test) => { 140 | const fn = () => 42; 141 | const otherValue = 42; 142 | test.strictSame(common.unsafeFunction(fn), fn); 143 | test.strictSame(common.unsafeFunction(otherValue), null); 144 | test.end(); 145 | }); 146 | 147 | metatests.test('safeFunction', (test) => { 148 | const fn = () => 42; 149 | const otherValue = 42; 150 | test.strictSame(common.safeFunction(fn), fn); 151 | test.strictSame(common.safeFunction(otherValue), common.emptiness); 152 | test.end(); 153 | }); 154 | 155 | metatests.test('identity function', (test) => { 156 | test.strictSame(common.id(10), 10); 157 | test.strictSame(common.id({ data: 10 }), { data: 10 }); 158 | test.end(); 159 | }); 160 | 161 | metatests.test('async identity function', (test) => { 162 | let sync = true; 163 | const cb = (err, data) => { 164 | test.error(err); 165 | test.assertNot(sync); 166 | test.strictSame(data, { data: 10 }); 167 | test.end(); 168 | }; 169 | 170 | common.asyncId({ data: 10 }, cb); 171 | sync = false; 172 | }); 173 | -------------------------------------------------------------------------------- /test/data.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*eslint max-len: ["error", { "code": 120 }]*/ 4 | 5 | const metatests = require('metatests'); 6 | const common = require('..'); 7 | 8 | metatests.case( 9 | 'Common / data types', 10 | { common }, 11 | { 12 | 'common.isScalar': [ 13 | [0, true], 14 | ['value1', true], 15 | [50, true], 16 | [true, true], 17 | [false, true], 18 | [null, false], 19 | [undefined, true], 20 | [NaN, true], 21 | [Infinity, true], 22 | [[], false], 23 | [{}, false], 24 | ['', true], 25 | ], 26 | 'common.copy': [ 27 | [ 28 | [1, 2, 3], 29 | [1, 2, 3], 30 | ], 31 | [[], []], 32 | [ 33 | [true, false], 34 | [true, false], 35 | ], 36 | ], 37 | 'common.clone': [ 38 | [{}, {}], 39 | [{ a: [1, 2, 3] }, { a: [1, 2, 3] }], 40 | [ 41 | { a: 1, b: 2, c: 3 }, 42 | { a: 1, b: 2, c: 3 }, 43 | ], 44 | [{ a: { b: 2, c: 3 } }, { a: { b: 2, c: 3 } }], 45 | [{ a: new Date('2000-01-01') }, { a: {} }], 46 | [ 47 | Object.assign(Object.create(null), { a: 1, b: 2, c: 3 }), 48 | { a: 1, b: 2, c: 3 }, 49 | ], 50 | ], 51 | 'common.duplicate': [ 52 | [{}, {}], 53 | [{ a: [1, 2, 3] }, { a: [1, 2, 3] }], 54 | [ 55 | { a: 1, b: 2, c: 3 }, 56 | { a: 1, b: 2, c: 3 }, 57 | ], 58 | [{ a: { b: 2, c: 3 } }, { a: { b: 2, c: 3 } }], 59 | [{ a: new Date('2000-01-01') }, { a: new Date('2000-01-01') }], 60 | ], 61 | 'common.getByPath': [ 62 | [ 63 | { item: { subitem: { value: 'Gagarin' } } }, 64 | 'item.subitem.value', 65 | 'Gagarin', 66 | ], 67 | [{ item: { subitem: { value: 123 } } }, 'item.subitem.value', 123], 68 | [{ item: { subitem: { value: true } } }, 'item.subitem.value', true], 69 | [{ item: { subitem: { value: false } } }, 'item.subitem.value', false], 70 | [ 71 | { item: { subitem: { value: 'Gagarin' } } }, 72 | 'item.subitem.none', 73 | undefined, 74 | ], 75 | [{ item: { subitem: { value: null } } }, 'item.subitem.value', null], 76 | [ 77 | { item: { subitem: { value: 'Gagarin' } } }, 78 | 'item.none.value', 79 | undefined, 80 | ], 81 | [ 82 | { item: { subitem: { value: 'Gagarin' } } }, 83 | 'item.subitem', 84 | { value: 'Gagarin' }, 85 | ], 86 | ], 87 | 'common.setByPath': [ 88 | [ 89 | { item: { subitem: { value: 'Gagarin' } } }, 90 | 'item.subitem.value', 91 | 'Gagarin', 92 | true, 93 | ], 94 | [ 95 | { item: { subitem: { value: 'Gagarin' } } }, 96 | 'item.subitem.none', 97 | 'Gagarin', 98 | true, 99 | ], 100 | [{ item: { subitem: { value: 123 } } }, 'item.subitem.value', 123, true], 101 | [ 102 | { item: { subitem: { value: true } } }, 103 | 'item.subitem.value', 104 | true, 105 | true, 106 | ], 107 | [ 108 | { item: { subitem: { value: false } } }, 109 | 'item.subitem.value', 110 | false, 111 | true, 112 | ], 113 | [ 114 | { item: { subitem: { value: 'Gagarin' } } }, 115 | 'item.subitem.none', 116 | undefined, 117 | true, 118 | ], 119 | [ 120 | { item: { subitem: { value: null } } }, 121 | 'item.subitem.value', 122 | null, 123 | true, 124 | ], 125 | [ 126 | { item: { subitem: { value: 'Gagarin' } } }, 127 | 'item.none.value', 128 | undefined, 129 | true, 130 | ], 131 | [{ item: { subitem: { value: 'Gagarin' } } }, 'none.value', 123, true], 132 | [ 133 | { item: { subitem: { value: 'Gagarin' } } }, 134 | 'item.subitem', 135 | { value: 'Gagarin' }, 136 | true, 137 | ], 138 | ], 139 | 'common.deleteByPath': [ 140 | [{ item: { surname: 'Gagarin', name: 'Yuri' } }, 'item.name', true], 141 | [{ item: { surname: 'Gagarin', name: 'Yuri' } }, 'item.noname', false], 142 | [{ item: { surname: 'Gagarin', name: 'Yuri' } }, 'item', true], 143 | [{ item: { surname: 'Gagarin', name: 'Yuri' } }, 'unknown', false], 144 | [ 145 | Object.assign(Object.create(null), { 146 | item: { surname: 'Gagarin', name: 'Yuri' }, 147 | }), 148 | 'item', 149 | true, 150 | ], 151 | ], 152 | 'common.merge': [ 153 | [ 154 | ['a', 'b'], 155 | ['a', 'c'], 156 | ['a', 'b', 'c'], 157 | ], 158 | [ 159 | ['a', 'b'], 160 | ['a', 'b'], 161 | ['a', 'b'], 162 | ], 163 | [ 164 | ['b', 'c'], 165 | ['a', 'b'], 166 | ['b', 'c', 'a'], 167 | ], 168 | [ 169 | ['a', 'b'], 170 | ['c', 'd'], 171 | ['a', 'b', 'c', 'd'], 172 | ], 173 | [ 174 | [1, 2, 3], 175 | [1, 2, 3], 176 | [1, 2, 3], 177 | ], 178 | [ 179 | [1, 2, 3], 180 | [4, 5, 1], 181 | [1, 2, 3, 4, 5], 182 | ], 183 | ], 184 | 'common.mergeObjects': [ 185 | [ 186 | (_, a, b) => a + b, 187 | { a: 'a', b: 'b' }, 188 | { a: 'a', b: 'c' }, 189 | { a: 'aa', b: 'bc' }, 190 | ], 191 | [(_, a, b) => a || b, { a: 'a' }, { d: 'd', a: 'c' }, { a: 'a', d: 'd' }], 192 | [ 193 | (_, a, b) => (a || 0) + (b || 0), 194 | { a: 1, b: 2, c: 3 }, 195 | { a: 1, b: 2 }, 196 | { a: 2, b: 4, c: 3 }, 197 | ], 198 | [ 199 | (key) => key, 200 | Object.assign(Object.create(null), { 1: 'a' }), 201 | { 1: '1' }, 202 | ], 203 | ], 204 | }, 205 | ); 206 | 207 | metatests.test('setByPath', (test) => { 208 | const obj = { a: {} }; 209 | test.assert(common.setByPath(obj, 'a.b.c', 42)); 210 | test.strictSame(obj.a.b.c, 42); 211 | test.end(); 212 | }); 213 | 214 | metatests.test('setByPath non-object', (test) => { 215 | const obj = { a: 10 }; 216 | test.assertNot(common.setByPath(obj, 'a.b.c', 42)); 217 | test.end(); 218 | }); 219 | 220 | metatests.test('setByPath non-object first', (test) => { 221 | const nonobj = 10; 222 | test.assertNot(common.setByPath(nonobj, 'a.b.c', 42)); 223 | test.end(); 224 | }); 225 | 226 | metatests.test('setByPath non-object last', (test) => { 227 | const obj = { a: { b: 10 } }; 228 | test.assertNot(common.setByPath(obj, 'a.b.c', 42)); 229 | test.end(); 230 | }); 231 | 232 | metatests.test('deleteByPath', (test) => { 233 | const obj = { a: { b: { c: 42 } } }; 234 | test.assert(common.deleteByPath(obj, 'a.b.c')); 235 | test.assertNot(obj.a.b.c); 236 | test.end(); 237 | }); 238 | 239 | metatests.test('deleteByPath non-existent', (test) => { 240 | const obj = { a: {} }; 241 | test.assertNot(common.deleteByPath(obj, 'a.b.c')); 242 | test.end(); 243 | }); 244 | 245 | metatests.test('deleteByPath non-existent last', (test) => { 246 | const obj = { a: { b: {} } }; 247 | test.assertNot(common.deleteByPath(obj, 'a.b.c')); 248 | test.end(); 249 | }); 250 | 251 | metatests.test('duplicate correctly handling object prototypes', (test) => { 252 | const objects = [{}, new Date(), Object.create(null)]; 253 | objects.forEach((obj) => { 254 | const res = common.duplicate(obj); 255 | test.strictSame(res.constructor, obj.constructor); 256 | }); 257 | test.end(); 258 | }); 259 | 260 | metatests.test('duplicate handling only object own properties', (test) => { 261 | const buf = Buffer.from('test data'); 262 | const res = common.duplicate(buf); 263 | test.strictSame(res, buf); 264 | test.end(); 265 | }); 266 | 267 | metatests.test('duplicate correctly handles circular references', (test) => { 268 | const obj = { a: {} }; 269 | obj.a.obj = obj; 270 | const res = common.duplicate(obj); 271 | test.sameTopology(res, obj); 272 | test.end(); 273 | }); 274 | 275 | metatests.test('clone handling only object own properties', (test) => { 276 | const obj = { a: 1 }; 277 | Object.setPrototypeOf(obj, { b: 2 }); 278 | const res = common.clone(obj); 279 | test.strictSame(res, obj); 280 | test.end(); 281 | }); 282 | 283 | metatests.testSync('mergeObjects correctly handles ownProperties', (test) => { 284 | const objects = [{ a: 1 }, { a: 2 }]; 285 | Object.setPrototypeOf(objects[1], { b: 3 }); 286 | const actual = common.mergeObjects((_, a, b) => a + b, ...objects); 287 | test.strictSame(actual, { a: 3 }); 288 | }); 289 | 290 | metatests.testSync( 291 | 'mergeObjects passes correct arguments to merger', 292 | (test) => { 293 | const objs = [{ a: 13 }, { a: 42 }, { a: 99 }]; 294 | const actual = common.mergeObjects( 295 | test.mustCall((key, ...args) => { 296 | test.strictSame(key, 'a'); 297 | test.strictSame(args, [13, 42, 99]); 298 | return Math.max(...args); 299 | }), 300 | ...objs, 301 | ); 302 | test.strictSame(actual, { a: 99 }); 303 | }, 304 | ); 305 | -------------------------------------------------------------------------------- /test/enum.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metatests = require('metatests'); 4 | const { Enum } = require('..'); 5 | 6 | metatests.test('Enum with key/value', (test) => { 7 | const Month = Enum.from({ 8 | Jan: 'January', 9 | Feb: 'February', 10 | Mar: 'March', 11 | Apr: 'April', 12 | May: 'May', 13 | Jun: 'June', 14 | Jul: 'July', 15 | Aug: 'August', 16 | Sep: 'September', 17 | Oct: 'October', 18 | Nov: 'November', 19 | Dec: 'December', 20 | }); 21 | 22 | test.strictSame(typeof Month, 'function'); 23 | test.strictSame(typeof Month.values, 'object'); 24 | test.strictSame(Array.isArray(Month.values), false); 25 | 26 | test.strictSame(Month.has('May'), true); 27 | test.strictSame(Month.key('Aug'), 7); 28 | 29 | const may = Month.from('May'); 30 | test.strictSame(typeof may, 'object'); 31 | test.strictSame(may.value, 'May'); 32 | test.strictSame(may.index, 4); 33 | test.strictSame(may.data, 'May'); 34 | 35 | test.strictSame(+may, 4); 36 | test.strictSame(may + '', '4'); 37 | 38 | test.end(); 39 | }); 40 | 41 | metatests.test('Enum string month keys', (test) => { 42 | const Month = Enum.from( 43 | 'January', 44 | 'February', 45 | 'March', 46 | 'April', 47 | 'May', 48 | 'June', 49 | 'July', 50 | 'August', 51 | 'September', 52 | 'October', 53 | 'November', 54 | 'December', 55 | ); 56 | 57 | test.strictSame(typeof Month, 'function'); 58 | test.strictSame(Array.isArray(Month.values), true); 59 | 60 | test.strictSame(Month.has('May'), true); 61 | test.strictSame(Month.has('Aug'), false); 62 | test.strictSame(Month.key('August'), 7); 63 | 64 | const may = Month.from('May'); 65 | test.strictSame(typeof may, 'object'); 66 | test.strictSame(Month.has('May'), true); 67 | test.strictSame(may.value, 'May'); 68 | test.strictSame(may.index, 4); 69 | test.strictSame(may.data, undefined); 70 | 71 | test.strictSame(+may, 4); 72 | test.strictSame(may + '', '4'); 73 | 74 | test.end(); 75 | }); 76 | 77 | metatests.test('Enum string month typed keys', (test) => { 78 | const Month = Enum.from({ 79 | 1: 'January', 80 | 2: 'February', 81 | 3: 'March', 82 | 4: 'April', 83 | 5: 'May', 84 | 6: 'June', 85 | 7: 'July', 86 | 8: 'August', 87 | 9: 'September', 88 | 10: 'October', 89 | 11: 'November', 90 | 12: 'December', 91 | }); 92 | 93 | test.strictSame(typeof Month, 'function'); 94 | test.strictSame(typeof Month.values, 'object'); 95 | test.strictSame(Array.isArray(Month.values), false); 96 | 97 | test.strictSame(Month.has('5'), true); 98 | test.strictSame(Month.has(13), false); 99 | 100 | const may = Month.from('5'); 101 | test.strictSame(typeof may, 'object'); 102 | test.strictSame(may.value, '5'); 103 | test.strictSame(may.index, 4); 104 | test.strictSame(may.data, 'May'); 105 | 106 | test.strictSame(+may, 4); 107 | test.strictSame(may + '', '4'); 108 | 109 | test.end(); 110 | }); 111 | 112 | metatests.test('Enum hundreds keys', (test) => { 113 | const Hundreds = Enum.from(100, 200, 300, 400, 500); 114 | 115 | const h100 = Hundreds.from(100); 116 | const h200 = Hundreds.from(200); 117 | const h500 = Hundreds.from(500); 118 | 119 | test.strictSame(Hundreds.from(-1), Enum.NaE); 120 | test.strictSame(Hundreds.from(0), Enum.NaE); 121 | test.strictSame(Hundreds.from(600), Enum.NaE); 122 | test.strictSame(Hundreds.from('Hello'), Enum.NaE); 123 | 124 | test.strictSame(typeof Hundreds, 'function'); 125 | test.strictSame(Array.isArray(Hundreds.values), true); 126 | test.strictSame(Hundreds.values.length, 5); 127 | 128 | test.strictSame(h100.value, 100); 129 | test.strictSame(h100.index, 0); 130 | test.strictSame(h100.data, undefined); 131 | 132 | test.strictSame(+h100, 0); 133 | test.strictSame(h100 + '', '0'); 134 | test.strictSame(h100.value, 100); 135 | 136 | test.strictSame(+h200, 1); 137 | test.strictSame(h200 + '', '1'); 138 | test.strictSame(h200.value, 200); 139 | 140 | test.strictSame(+h500, 4); 141 | test.strictSame(h500 + '', '4'); 142 | test.strictSame(h500.value, 500); 143 | 144 | test.end(); 145 | }); 146 | 147 | metatests.test('Enum hundreds keys array', (test) => { 148 | const Hundreds = Enum.from([100, 200, 300, 400, 500]); 149 | 150 | test.strictSame(Hundreds.from(0), Enum.NaE); 151 | 152 | const h100 = Hundreds.from(100); 153 | test.strictSame(h100.value, 100); 154 | test.strictSame(h100.index, 0); 155 | test.strictSame(h100.data, undefined); 156 | 157 | test.end(); 158 | }); 159 | 160 | metatests.test('Enum.NaE property', (test) => { 161 | test.strictSame(Object.getOwnPropertyDescriptor(Enum, 'NaE'), { 162 | writable: false, 163 | enumerable: false, 164 | configurable: false, 165 | value: Enum.NaE, 166 | }); 167 | test.end(); 168 | }); 169 | -------------------------------------------------------------------------------- /test/events.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metatests = require('metatests'); 4 | const events = require('events'); 5 | const common = require('..'); 6 | 7 | metatests.test('emitter', (test) => { 8 | const ee = common.emitter(); 9 | ee.on('name', () => { 10 | test.end(); 11 | }); 12 | ee.emit('name'); 13 | }); 14 | 15 | metatests.test('forward all events', (test) => { 16 | test.plan(3); 17 | 18 | const sourceEmitter = common.emitter(); 19 | const targetEmitter = common.emitter(); 20 | 21 | common.forwardEvents(sourceEmitter, targetEmitter); 22 | 23 | targetEmitter.on('testEvent1', () => { 24 | test.pass('event #1'); 25 | }); 26 | 27 | targetEmitter.on('testEvent2', () => { 28 | test.pass('event #2'); 29 | }); 30 | 31 | targetEmitter.on('testEvent3', () => { 32 | test.pass('event #3'); 33 | }); 34 | 35 | sourceEmitter.emit('testEvent1'); 36 | sourceEmitter.emit('testEvent2'); 37 | sourceEmitter.emit('testEvent3'); 38 | }); 39 | 40 | metatests.test('forward all events by method', (test) => { 41 | test.plan(3); 42 | 43 | const sourceEmitter = common.emitter(); 44 | const targetEmitter = common.emitter(); 45 | 46 | sourceEmitter.forward(targetEmitter); 47 | 48 | targetEmitter.on('testEvent1', () => { 49 | test.pass('event #1'); 50 | }); 51 | 52 | targetEmitter.on('testEvent2', () => { 53 | test.pass('event #2'); 54 | }); 55 | 56 | targetEmitter.on('testEvent3', () => { 57 | test.pass('event #3'); 58 | }); 59 | 60 | sourceEmitter.emit('testEvent1'); 61 | sourceEmitter.emit('testEvent2'); 62 | sourceEmitter.emit('testEvent3'); 63 | }); 64 | 65 | metatests.test('forward a single event', (test) => { 66 | test.plan(1); 67 | 68 | const sourceEventEmitter = new events.EventEmitter(); 69 | const targetEventEmitter = new events.EventEmitter(); 70 | 71 | common.forwardEvents(sourceEventEmitter, targetEventEmitter, 'testEvent'); 72 | 73 | targetEventEmitter.on('testEvent', () => { 74 | test.pass('event handler must be called'); 75 | }); 76 | 77 | sourceEventEmitter.emit('testEvent'); 78 | }); 79 | 80 | metatests.test('forward a single event under a new name', (test) => { 81 | test.plan(1); 82 | 83 | const sourceEventEmitter = new events.EventEmitter(); 84 | const targetEventEmitter = new events.EventEmitter(); 85 | 86 | common.forwardEvents(sourceEventEmitter, targetEventEmitter, { 87 | testEvent: 'renamedEvent', 88 | }); 89 | 90 | targetEventEmitter.on('renamedEvent', () => { 91 | test.pass('event handler must be called'); 92 | }); 93 | 94 | sourceEventEmitter.emit('testEvent'); 95 | }); 96 | 97 | metatests.test('forward multiple events', (test) => { 98 | test.plan(2); 99 | 100 | const sourceEventEmitter = new events.EventEmitter(); 101 | const targetEventEmitter = new events.EventEmitter(); 102 | 103 | common.forwardEvents(sourceEventEmitter, targetEventEmitter, [ 104 | 'event1', 105 | 'event2', 106 | ]); 107 | 108 | targetEventEmitter.on('event1', () => { 109 | test.pass('first event handler must be called'); 110 | }); 111 | 112 | targetEventEmitter.on('event2', () => { 113 | test.pass('second event handler must be called'); 114 | }); 115 | 116 | sourceEventEmitter.emit('event1'); 117 | sourceEventEmitter.emit('event2'); 118 | }); 119 | -------------------------------------------------------------------------------- /test/fixtures/callerFilepath.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const common = require('../../'); 4 | 5 | module.exports = (depth) => common.callerFilepath(depth); 6 | -------------------------------------------------------------------------------- /test/fixtures/empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metarhia/common/f8ba4d3e5d8d85c3406d99099b187c5bd93ada37/test/fixtures/empty -------------------------------------------------------------------------------- /test/fixtures/greetings: -------------------------------------------------------------------------------- 1 | Hello world, Καλημέρα κόσμε, コンニチハ 2 | -------------------------------------------------------------------------------- /test/flags.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metatests = require('metatests'); 4 | const { Flags, Uint64 } = require('..'); 5 | 6 | metatests.test('FlagsClass.from', (test) => { 7 | const Numeri = Flags.from('Uno', 'Due', 'Tre', 'Quatro'); 8 | test.type(Numeri, 'function'); 9 | 10 | const num = Numeri.from('Due', 'Tre'); 11 | 12 | test.type(num, 'object'); 13 | test.strictSame(num.get('Uno'), false); 14 | test.strictSame(num.get('Due'), true); 15 | test.strictSame(num.get('Tre'), true); 16 | test.strictSame(num.get('Quatro'), false); 17 | 18 | test.end(); 19 | }); 20 | 21 | metatests.test('new FlagsClass', (test) => { 22 | const Numeri = Flags.from('Uno', 'Due', 'Tre', 'Quatro'); 23 | test.type(Numeri, 'function'); 24 | 25 | const num = new Numeri('Due', 'Tre'); 26 | 27 | test.type(num, 'object'); 28 | test.strictSame(num.get('Uno'), false); 29 | test.strictSame(num.get('Due'), true); 30 | test.strictSame(num.get('Tre'), true); 31 | test.strictSame(num.get('Quatro'), false); 32 | 33 | test.end(); 34 | }); 35 | 36 | metatests.test('new FlagsClass from Uint64', (test) => { 37 | const Numeri = Flags.from('Uno', 'Due', 'Tre', 'Quatro'); 38 | test.type(Numeri, 'function'); 39 | 40 | const num = new Numeri(new Uint64('0b10110')); 41 | 42 | test.type(num, 'object'); 43 | test.strictSame(num.get('Uno'), false); 44 | test.strictSame(num.get('Due'), true); 45 | test.strictSame(num.get('Tre'), true); 46 | test.strictSame(num.get('Quatro'), false); 47 | 48 | test.end(); 49 | }); 50 | 51 | metatests.test('FlagsClass.get', (test) => { 52 | const Numeri = Flags.from('Uno', 'Due', 'Tre', 'Quatro'); 53 | const num = Numeri.from('Due', 'Tre'); 54 | 55 | test.strictSame(num.get('Uno'), false); 56 | test.strictSame(num.get('Due'), true); 57 | test.strictSame(num.get('Tre'), true); 58 | test.strictSame(num.get('Quatro'), false); 59 | 60 | test.end(); 61 | }); 62 | 63 | metatests.test('FlagsClass.has', (test) => { 64 | const Numeri = Flags.from('Uno', 'Due', 'Tre', 'Quatro'); 65 | 66 | test.strictSame(Numeri.has('Uno'), true); 67 | test.strictSame(Numeri.has('Due'), true); 68 | test.strictSame(Numeri.has('Tre'), true); 69 | test.strictSame(Numeri.has('Quatro'), true); 70 | 71 | test.strictSame(Numeri.has('Cinque'), false); 72 | test.strictSame(Numeri.has('Sei'), false); 73 | 74 | test.end(); 75 | }); 76 | 77 | metatests.test('FlagsClass.set', (test) => { 78 | const Numeri = Flags.from('Uno', 'Due', 'Tre', 'Quatro'); 79 | const num = Numeri.from(); 80 | 81 | num.set('Due'); 82 | num.set('Tre'); 83 | 84 | test.strictSame(num.get('Due'), true); 85 | test.strictSame(num.get('Tre'), true); 86 | 87 | num.set('Due'); 88 | num.set('Tre'); 89 | 90 | test.strictSame(num.get('Due'), true); 91 | test.strictSame(num.get('Tre'), true); 92 | 93 | test.end(); 94 | }); 95 | 96 | metatests.test('FlagsClass.unset', (test) => { 97 | const Numeri = Flags.from('Uno', 'Due', 'Tre', 'Quatro'); 98 | const num = Numeri.from('Due', 'Tre'); 99 | 100 | num.unset('Due'); 101 | num.unset('Tre'); 102 | 103 | test.strictSame(num.get('Due'), false); 104 | test.strictSame(num.get('Tre'), false); 105 | 106 | num.unset('Due'); 107 | num.unset('Tre'); 108 | 109 | test.strictSame(num.get('Due'), false); 110 | test.strictSame(num.get('Tre'), false); 111 | 112 | test.end(); 113 | }); 114 | 115 | metatests.test('FlagsClass.toggle', (test) => { 116 | const Numeri = Flags.from('Uno', 'Due', 'Tre', 'Quatro'); 117 | const num = Numeri.from(); 118 | 119 | num.toggle('Due'); 120 | num.toggle('Tre'); 121 | 122 | test.strictSame(num.get('Due'), true); 123 | test.strictSame(num.get('Tre'), true); 124 | 125 | num.toggle('Due'); 126 | num.toggle('Tre'); 127 | 128 | test.strictSame(num.get('Due'), false); 129 | test.strictSame(num.get('Tre'), false); 130 | 131 | test.end(); 132 | }); 133 | 134 | metatests.test('FlagsClass.toString', (test) => { 135 | const Numeri = Flags.from('Uno', 'Due', 'Tre', 'Quatro'); 136 | const nums = [ 137 | [[], '0000'], 138 | [['Uno'], '0001'], 139 | [['Due'], '0010'], 140 | [['Tre'], '0100'], 141 | [['Quatro'], '1000'], 142 | [['Uno', 'Due'], '0011'], 143 | [['Due', 'Tre'], '0110'], 144 | [['Uno', 'Quatro'], '1001'], 145 | [['Tre', 'Quatro'], '1100'], 146 | [['Uno', 'Due', 'Tre', 'Quatro'], '1111'], 147 | ]; 148 | nums.forEach((num) => 149 | test.strictSame(Numeri.from(...num[0]).toString(), num[1]), 150 | ); 151 | 152 | test.end(); 153 | }); 154 | 155 | metatests.test('FlagsClass.toNumber', (test) => { 156 | const Numeri = Flags.from('Uno', 'Due', 'Tre', 'Quatro'); 157 | const nums = [ 158 | [[], false], 159 | [['Uno'], 1], 160 | [['Due'], 2], 161 | [['Tre'], 4], 162 | [['Quatro'], 8], 163 | [['Uno', 'Due'], 3], 164 | [['Due', 'Tre'], 6], 165 | [['Uno', 'Quatro'], 9], 166 | [['Tre', 'Quatro'], 12], 167 | [['Uno', 'Due', 'Tre', 'Quatro'], 15], 168 | ]; 169 | nums.forEach((num) => 170 | test.strictSame(Numeri.from(...num[0]).toNumber(), new Uint64(num[1])), 171 | ); 172 | 173 | test.end(); 174 | }); 175 | 176 | metatests.test('FlagsClass.toPrimitive', (test) => { 177 | const Numeri = Flags.from('Uno', 'Due', 'Tre', 'Quatro'); 178 | const nums = [ 179 | [[], 0], 180 | [['Uno'], 1], 181 | [['Due'], 2], 182 | [['Tre'], 4], 183 | [['Quatro'], 8], 184 | [['Uno', 'Due'], 3], 185 | [['Due', 'Tre'], 6], 186 | [['Uno', 'Quatro'], 9], 187 | [['Tre', 'Quatro'], 12], 188 | [['Uno', 'Due', 'Tre', 'Quatro'], 15], 189 | ]; 190 | nums.forEach((num) => test.strictSame(+Numeri.from(...num[0]), num[1])); 191 | 192 | test.end(); 193 | }); 194 | 195 | metatests.test('Create new instance with too much arguments', (test) => { 196 | const values = new Array(65); 197 | const message = 'Flags does not support more than 64 values'; 198 | test.throws(() => { 199 | Flags.from(...values); 200 | }, new TypeError(message)); 201 | test.end(); 202 | }); 203 | 204 | metatests.test('Get incorrect key', (test) => { 205 | const Numeri = Flags.from('Uno', 'Due', 'Tre', 'Quatro'); 206 | const num = Numeri.from(); 207 | const message = 'Flags instance does not have key Cinque'; 208 | test.throws(() => { 209 | num.get('Cinque'); 210 | }, new TypeError(message)); 211 | test.end(); 212 | }); 213 | 214 | metatests.test('Set incorrect key', (test) => { 215 | const Numeri = Flags.from('Uno', 'Due', 'Tre', 'Quatro'); 216 | const num = Numeri.from(); 217 | const message = 'Flags instance does not have key Cinque'; 218 | test.throws(() => { 219 | num.set('Cinque'); 220 | }, new TypeError(message)); 221 | test.end(); 222 | }); 223 | 224 | metatests.test('Unset incorrect key', (test) => { 225 | const Numeri = Flags.from('Uno', 'Due', 'Tre', 'Quatro'); 226 | const num = Numeri.from(); 227 | const message = 'Flags instance does not have key Cinque'; 228 | test.throws(() => { 229 | num.unset('Cinque'); 230 | }, new TypeError(message)); 231 | test.end(); 232 | }); 233 | 234 | metatests.test('Toggle incorrect key', (test) => { 235 | const Numeri = Flags.from('Uno', 'Due', 'Tre', 'Quatro'); 236 | const num = Numeri.from(); 237 | const message = 'Flags instance does not have key Cinque'; 238 | test.throws(() => { 239 | num.toggle('Cinque'); 240 | }, new TypeError(message)); 241 | test.end(); 242 | }); 243 | -------------------------------------------------------------------------------- /test/fp.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metatests = require('metatests'); 4 | const common = require('..'); 5 | 6 | metatests.test('partial ', (test) => { 7 | const func = (a, b, c, d) => a + b + c + d; 8 | const fn1 = common.partial(func); 9 | const fn2 = common.partial(func, 10); 10 | const fn3 = common.partial(func, 10, 20); 11 | const fn4 = common.partial(func, 10, 20, 30); 12 | const fn5 = common.partial(func, 10, 20, 30, 40); 13 | const result1 = fn1(10, 20, 30, 40); 14 | const result2 = fn2(20, 30, 40); 15 | const result3 = fn3(30, 40); 16 | const result4 = fn4(40); 17 | const result5 = fn5(); 18 | test.strictSame(result1, 100); 19 | test.strictSame(result2, 100); 20 | test.strictSame(result3, 100); 21 | test.strictSame(result4, 100); 22 | test.strictSame(result5, 100); 23 | test.end(); 24 | }); 25 | 26 | metatests.test('omap', (test) => { 27 | const persons = { 28 | vlad: { age: 20, side: 'good' }, 29 | dziuba: { age: 20, side: 'evil' }, 30 | }; 31 | const expected = { vlad: 'good', dziuba: 'evil' }; 32 | const result = common.omap((p) => p.side, persons); 33 | test.strictSame(result, expected); 34 | test.end(); 35 | }); 36 | 37 | metatests.test('compose', (test) => { 38 | const fn1 = (x) => x + 1; 39 | const fn2 = (x) => x * 3; 40 | const fn3 = (x) => x - 2; 41 | 42 | const composedFunction = common.compose(fn1, fn2, fn3); 43 | test.strictSame(composedFunction(4), 13); 44 | test.strictSame(composedFunction(10), 31); 45 | 46 | test.end(); 47 | }); 48 | 49 | metatests.test('compose without arguments', (test) => { 50 | const emptyComposed = common.compose(); 51 | test.strictSame(emptyComposed(1, 2, 3), 1); 52 | test.end(); 53 | }); 54 | 55 | metatests.test('maybe', (test) => { 56 | const fn = (expected, mustCall) => { 57 | const f = (actual) => { 58 | test.strictSame(actual, expected); 59 | return actual * 2; 60 | }; 61 | return mustCall ? test.mustCall(f) : test.mustNotCall(f); 62 | }; 63 | 64 | test.strictSame(common.maybe(fn(2, true), 2, 2), 4); 65 | test.strictSame(common.maybe(fn(1), 2, undefined), 2); 66 | test.strictSame(common.maybe(fn(1), 2, null), 2); 67 | test.strictSame(common.maybe(fn(0, true), 1, 0), 0); 68 | test.end(); 69 | }); 70 | 71 | metatests.test('zip', (test) => { 72 | const data = [ 73 | [1, 2, 3], 74 | ['one', 'two', 'three'], 75 | ['один', 'два', 'три'], 76 | ]; 77 | const expected = [ 78 | [1, 'one', 'один'], 79 | [2, 'two', 'два'], 80 | [3, 'three', 'три'], 81 | ]; 82 | const res = common.zip(...data); 83 | test.strictSame(res, expected); 84 | test.end(); 85 | }); 86 | 87 | metatests.test('zip with no elements', (test) => { 88 | const res = common.zip(); 89 | test.strictSame(res, []); 90 | test.end(); 91 | }); 92 | 93 | metatests.test('replicate', (test) => { 94 | const expected = [true, true, true, true, true]; 95 | const result = common.replicate(5, true); 96 | test.strictSame(result, expected); 97 | test.end(); 98 | }); 99 | 100 | metatests.test('zipWith', (test) => { 101 | const data = [ 102 | [1, 2, 3], 103 | ['one', 'two', 'three'], 104 | ['один', 'два', 'три'], 105 | ]; 106 | const makeDict = (num, eng, rus) => ({ num, eng, rus }); 107 | const expected = [ 108 | { num: 1, eng: 'one', rus: 'один' }, 109 | { num: 2, eng: 'two', rus: 'два' }, 110 | { num: 3, eng: 'three', rus: 'три' }, 111 | ]; 112 | const res = common.zipWith(makeDict, ...data); 113 | test.strictSame(res, expected); 114 | test.end(); 115 | }); 116 | 117 | metatests.test('curry(f)(1)(2)(3)', (test) => { 118 | const sum = (x, y, z) => x + y + z; 119 | const res = common.curry(sum)(1)(2)(3); 120 | test.strictSame(res, 6); 121 | test.end(); 122 | }); 123 | 124 | metatests.test('curry(f, 1)(2)(3)', (test) => { 125 | const sum = (x, y, z) => x + y + z; 126 | const res = common.curry(sum, 1)(2)(3); 127 | test.strictSame(res, 6); 128 | test.end(); 129 | }); 130 | 131 | metatests.test('curry(f, 1, 2)(3)', (test) => { 132 | const sum = (x, y, z) => x + y + z; 133 | const res = common.curry(sum, 1, 2)(3); 134 | test.strictSame(res, 6); 135 | test.end(); 136 | }); 137 | 138 | metatests.test('curry(f, 1, 2, 3)', (test) => { 139 | const sum = (x, y, z) => x + y + z; 140 | const res = common.curry(sum, 1, 2, 3); 141 | test.strictSame(res, 6); 142 | test.end(); 143 | }); 144 | 145 | metatests.test('curry(f, 1)(2, 3)', (test) => { 146 | const sum = (x, y, z) => x + y + z; 147 | const res = common.curry(sum, 1)(2, 3); 148 | test.strictSame(res, 6); 149 | test.end(); 150 | }); 151 | 152 | metatests.test('curry(f)(1, 2, 3)', (test) => { 153 | const sum = (x, y, z) => x + y + z; 154 | const res = common.curry(sum)(1, 2, 3); 155 | test.strictSame(res, 6); 156 | test.end(); 157 | }); 158 | 159 | metatests.testSync('multiple curry of sum(x, y)', (test) => { 160 | const sum = (x, y) => x + y; 161 | const sumCurry = common.curry(sum); 162 | const addOne = sumCurry(1); 163 | const addTwo = sumCurry(2); 164 | test.strictSame(addOne(10), 11); 165 | test.strictSame(addOne(20), 21); 166 | test.strictSame(addTwo(10), 12); 167 | test.strictSame(addTwo(20), 22); 168 | }); 169 | 170 | metatests.testSync('multiple curry of sum(x, y, z)', (test) => { 171 | const sum = (x, y, z) => x + y + z; 172 | const sumCurry = common.curry(sum); 173 | const addOneTwo = sumCurry(1, 2); 174 | const addTwoThree = sumCurry(2, 3); 175 | test.strictSame(addOneTwo(10), 13); 176 | test.strictSame(addOneTwo(20), 23); 177 | test.strictSame(addTwoThree(10), 15); 178 | test.strictSame(addTwoThree(20), 25); 179 | }); 180 | 181 | metatests.testSync('curry of identity', (test) => { 182 | const id = (x) => x; 183 | const idCurry = common.curry(id); 184 | test.strictSame(idCurry(10), 10); 185 | test.strictSame(idCurry(20), 20); 186 | 187 | test.strictSame(common.curry(id, 10), 10); 188 | }); 189 | 190 | metatests.testSync('curry of unit', (test) => { 191 | const unit = () => 42; 192 | const unitCurry = common.curry(unit); 193 | test.strictSame(unitCurry(), 42); 194 | }); 195 | 196 | metatests.testSync('redundant args must be ignored', (test) => { 197 | const add = (x, y) => x + y; 198 | const addCurry = common.curry(add); 199 | test.strictSame(addCurry(1, 2, 4), 3); 200 | test.strictSame(common.curry(add, 1, 2, 4), 3); 201 | 202 | const sum = (x, y, z) => x + y + z; 203 | const sumCurry = common.curry(sum); 204 | test.strictSame(sumCurry(1, 2, 3, 4, 5), 6); 205 | test.strictSame(common.curry(sum, 1, 2, 3, 4, 5), 6); 206 | }); 207 | 208 | metatests.test('curryN', (test) => { 209 | const sum = (x, y, z) => x + y + z; 210 | const sumC = common.curryN(sum, 2, 1); 211 | const sumC2 = sumC(2); 212 | const res = sumC2(3); 213 | test.strictSame(res, 6); 214 | test.end(); 215 | }); 216 | 217 | metatests.test('curryTwice', (test) => { 218 | const sum = (x, y) => x + y; 219 | test.strictSame(common.curryTwice(sum)(1)(2), 3); 220 | test.end(); 221 | }); 222 | 223 | metatests.test('applyArgs', (test) => { 224 | const argsFn = common.applyArgs(1, 2, 3); 225 | const fn = test.mustCall((a, b, c) => { 226 | test.strictSame([a, b, c], [1, 2, 3]); 227 | return a + b + c; 228 | }); 229 | test.strictSame(argsFn(fn), 6); 230 | test.end(); 231 | }); 232 | 233 | metatests.test('either', (test) => { 234 | const fnEither = common.either((x) => x * 2); 235 | 236 | const res = fnEither(1, 2); 237 | 238 | test.strictSame(res, 2); 239 | test.end(); 240 | }); 241 | 242 | metatests.test('either with one error and one success', (test) => { 243 | const fnError = new Error('either with error'); 244 | const fn = (x) => { 245 | if (x === 1) { 246 | throw fnError; 247 | } else { 248 | return x * 2; 249 | } 250 | }; 251 | const fnEither = common.either(fn); 252 | 253 | const res = fnEither(1, 2); 254 | 255 | test.strictSame(res, 4); 256 | test.end(); 257 | }); 258 | 259 | metatests.test('either with all errors', (test) => { 260 | const fnError1 = new Error('either with error 1'); 261 | const fnError2 = new Error('either with error 2'); 262 | const fn = (x) => { 263 | if (x === 1) { 264 | throw fnError1; 265 | } else { 266 | throw fnError2; 267 | } 268 | }; 269 | const fnEither = common.either(fn); 270 | 271 | test.throws(fnEither.bind(null, 1, 2), fnError2); 272 | test.end(); 273 | }); 274 | 275 | metatests.test('restLeft', (test) => { 276 | const expectedArgs = [3, 4, 5]; 277 | const expectedArg1 = 1; 278 | const expectedArg2 = 2; 279 | const expectedCallbackArgs = [6, 7, 8]; 280 | const af = common.restLeft((args, arg1, arg2, callback) => { 281 | test.strictSame(args, expectedArgs); 282 | test.strictSame(arg1, expectedArg1); 283 | test.strictSame(arg2, expectedArg2); 284 | callback(6, 7, 8); 285 | }); 286 | af(1, 2, 3, 4, 5, (...args) => { 287 | test.strictSame(args, expectedCallbackArgs); 288 | test.end(); 289 | }); 290 | }); 291 | -------------------------------------------------------------------------------- /test/fs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metatests = require('metatests'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const { 7 | mkdirp, 8 | mkdirpPromise, 9 | rmdirp, 10 | rmRecursive, 11 | rmRecursivePromise, 12 | } = require('..'); 13 | 14 | const testMkdirp = metatests.test('mkdir'); 15 | const mkdirpTestDir = path.join('test', 'ex1', 'ex2'); 16 | const RMDIRP_TEST_DIR = 'testDir'; 17 | const RMRECURSIVE_TEST_DIR = path.join('test', 'rmRecursiveTest'); 18 | 19 | const removeUsedDirs = (test, cb) => { 20 | fs.rmdir(mkdirpTestDir, (err) => { 21 | if (err && err.code !== 'ENOENT') test.error(err); 22 | fs.rmdir(path.dirname(mkdirpTestDir), (err) => { 23 | if (err && err.code !== 'ENOENT') test.error(err); 24 | cb(); 25 | }); 26 | }); 27 | }; 28 | 29 | testMkdirp.beforeEach(removeUsedDirs); 30 | testMkdirp.afterEach(removeUsedDirs); 31 | 32 | testMkdirp.test('create 2 directories using mode', (test) => { 33 | mkdirp(mkdirpTestDir, 0o777, (err) => { 34 | test.error(err, 'Cannot create directory'); 35 | test.end(); 36 | }); 37 | }); 38 | 39 | testMkdirp.test('create 2 directories without mode', (test) => { 40 | mkdirp(mkdirpTestDir, (err) => { 41 | test.error(err, 'Cannot create directory'); 42 | test.end(); 43 | }); 44 | }); 45 | 46 | testMkdirp.test('mkdirpPromise with mode', () => 47 | mkdirpPromise(mkdirpTestDir, 0o777), 48 | ); 49 | 50 | testMkdirp.test('mkdirpPromise without mode', () => 51 | mkdirpPromise(mkdirpTestDir), 52 | ); 53 | 54 | metatests.test('rmdirp test', (test) => 55 | fs.mkdir(RMDIRP_TEST_DIR, (err) => { 56 | if (err && err.code !== 'EEXISTS') { 57 | test.bailout(err); 58 | } 59 | fs.mkdir(path.join(RMDIRP_TEST_DIR, 'subdir1'), (err) => { 60 | if (err && err.code !== 'EEXISTS') { 61 | test.bailout(err); 62 | } 63 | rmdirp(path.join(RMDIRP_TEST_DIR, 'subdir1'), (err) => { 64 | if (err) { 65 | test.bailout(err); 66 | } 67 | fs.access(RMDIRP_TEST_DIR, (err) => { 68 | test.isError(err); 69 | test.end(); 70 | }); 71 | }); 72 | }); 73 | }), 74 | ); 75 | 76 | const createHierarchy = (hierarchy, cb) => { 77 | if (hierarchy.length === 0) { 78 | cb(null); 79 | return; 80 | } 81 | 82 | const { path: file, data } = hierarchy[0]; 83 | 84 | if (file.endsWith(path.sep)) { 85 | fs.mkdir(file, (err) => { 86 | if (err) cb(err); 87 | else createHierarchy(hierarchy.slice(1), cb); 88 | }); 89 | } else { 90 | fs.writeFile(file, data, (err) => { 91 | if (err) cb(err); 92 | else createHierarchy(hierarchy.slice(1), cb); 93 | }); 94 | } 95 | }; 96 | 97 | const hierarchy = [ 98 | { path: ['.', path.sep] }, 99 | { path: ['1', path.sep] }, 100 | { path: ['2', path.sep] }, 101 | { path: ['2', '3'], data: 'data' }, 102 | { path: ['2', '4.file'], data: 'data' }, 103 | { path: ['2', '5', path.sep] }, 104 | { path: ['2', '6', path.sep] }, 105 | { path: ['2', '6', '7', path.sep] }, 106 | ].map((f) => { 107 | f.path = path.join(RMRECURSIVE_TEST_DIR, ...f.path); 108 | return f; 109 | }); 110 | 111 | const rmRecursiveTest = metatests.test('recursively remove folder hierarchy'); 112 | rmRecursiveTest.endAfterSubtests(); 113 | 114 | rmRecursiveTest.beforeEach((test, cb) => { 115 | createHierarchy(hierarchy, (err) => { 116 | if (err) { 117 | test.fail('Cannot create folder hierarchy', err); 118 | test.end(); 119 | cb(); 120 | return; 121 | } 122 | fs.access(RMRECURSIVE_TEST_DIR, (err) => { 123 | if (err) { 124 | test.fail('Cannot access created folder hierarchy', err); 125 | test.end(); 126 | } 127 | cb(); 128 | }); 129 | }); 130 | }); 131 | 132 | rmRecursiveTest.afterEach((test, cb) => { 133 | fs.access(RMRECURSIVE_TEST_DIR, (err) => { 134 | if (!err) test.fail('Created folder hierarchy was not removed', err); 135 | cb(); 136 | }); 137 | }); 138 | 139 | rmRecursiveTest.test('rmRecursive', (test) => { 140 | rmRecursive( 141 | RMRECURSIVE_TEST_DIR, 142 | test.cbFail('Cannot remove folder hierarchy with callbacks', () => 143 | test.end(), 144 | ), 145 | ); 146 | }); 147 | 148 | rmRecursiveTest.test('rmRecursivePromise', () => 149 | rmRecursivePromise(RMRECURSIVE_TEST_DIR), 150 | ); 151 | -------------------------------------------------------------------------------- /test/id.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*eslint max-len: ["error", { "code": 120 }]*/ 4 | 5 | const metatests = require('metatests'); 6 | const common = require('..'); 7 | 8 | const characters = 9 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 10 | const secret = 'secret'; 11 | const length = 64; 12 | 13 | metatests.case( 14 | 'Common / id', 15 | { common }, 16 | { 17 | 'common.validateToken': [ 18 | [ 19 | secret, 20 | 'XFHczfaqXaaUmIcKfHNF9YAY4BRaMX5Z4Bx99rsB5UA499mTjmewlrWTKTCp77bc', 21 | true, 22 | ], 23 | [ 24 | secret, 25 | 'XFHczfaqXaaUmIcKfHNF9YAY4BRaMX5Z4Bx99rsB5UA499mTjmewlrWTKTCp77bK', 26 | false, 27 | ], 28 | [ 29 | secret, 30 | '2XpU8oAewXwKJJSQeY0MByY403AyXprFdhB96zPFbpJxlBqHA3GfBYeLxgHxBhhZ', 31 | false, 32 | ], 33 | [secret, 'WRONG-STRING', false], 34 | [secret, '', false], 35 | ], 36 | 'common.generateToken': [ 37 | [ 38 | secret, 39 | characters, 40 | length, 41 | (token) => common.validateToken(secret, token), 42 | ], 43 | ], 44 | 'common.crcToken': [ 45 | [ 46 | secret, 47 | common.generateKey(length - 4, characters), 48 | (crc) => crc.length === 4, 49 | ], 50 | ], 51 | 'common.idToChunks': [ 52 | [0, ['0000', '0000']], 53 | [1, ['0001', '0000']], 54 | [30, ['001e', '0000']], 55 | [123456789, ['cd15', '075b']], 56 | [123456789123, ['1a83', 'be99', '001c']], 57 | [9007199254740991, ['ffff', 'ffff', 'ffff', '001f']], 58 | ], 59 | 'common.idToPath': [ 60 | [0, '0000/0000'], 61 | [1, '0001/0000'], 62 | [30, '001e/0000'], 63 | [123456789, 'cd15/075b'], 64 | [123456789123, '1a83/be99/001c'], 65 | [9007199254740991, 'ffff/ffff/ffff/001f'], 66 | ], 67 | 'common.pathToId': [ 68 | ['0000/0000', 0], 69 | ['0001/0000', 1], 70 | ['001e/0000', 30], 71 | ['1000/0000', 4096], 72 | ['ffff/0000', 65535], 73 | ['0000/0001', 65536], 74 | ['e240/0001', 123456], 75 | ['0000/001e', 1966080], 76 | ['cd15/075b', 123456789], 77 | ['0000/1000', 268435456], 78 | ['0000/ffff', 4294901760], 79 | ['0000/0000/0001', 4294967296], 80 | ['1a83/be99/001c', 123456789123], 81 | ['0000/0000/1000', 17592186044416], 82 | ['0000/0000/ffff', 281470681743360], 83 | ['ffff/ffff/ffff/001f', 9007199254740991], 84 | ], 85 | }, 86 | ); 87 | 88 | metatests.test('generateStorageKey', (test) => { 89 | const key = common.generateStorageKey(); 90 | test.strictSame(Array.isArray(key), true); 91 | test.strictSame(key.length, 3); 92 | const [dir1, dir2, file] = key; 93 | test.strictSame(dir1.length, 2); 94 | test.strictSame(dir2.length, 2); 95 | test.strictSame(file.length, 8); 96 | test.strictSame(key.join('/').length, 14); 97 | test.end(); 98 | }); 99 | 100 | metatests.test('common.hash() with common.validateHash()', (test) => { 101 | const password = 'password'; 102 | const salt = 'salt'; 103 | const hashValue = common.hash(password, salt); 104 | test.assert(common.validateHash(hashValue, password, salt)); 105 | test.end(); 106 | }); 107 | 108 | metatests.test('generateGUID', (test) => { 109 | const guidRegExp = new RegExp( 110 | '^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$', 111 | 'i', 112 | ); 113 | const guid = common.generateGUID(); 114 | test.assert(guidRegExp.test(guid)); 115 | test.end(); 116 | }); 117 | -------------------------------------------------------------------------------- /test/math.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metatests = require('metatests'); 4 | const common = require('..'); 5 | 6 | metatests.case( 7 | 'Common / math', 8 | { common }, 9 | { 10 | 'common.random': [ 11 | [0, 10, (result) => result >= 0 && result <= 10], 12 | [1, 10, (result) => result >= 1 && result <= 10], 13 | [-1, 10, (result) => result >= -1 && result <= 10], 14 | [10, 20, (result) => result >= 10 && result <= 20], 15 | [10, 0, (result) => result >= 0 && result <= 10], 16 | [20, (result) => result >= 0 && result <= 20], 17 | [10, 10, 10], 18 | ], 19 | 'common.cryptoRandom': [[(result) => result >= 0 && result <= 1]], 20 | }, 21 | ); 22 | 23 | metatests.test('cryptoPrefetcher with invalid arguments', (test) => { 24 | test.throws( 25 | () => common.cryptoPrefetcher(10, 8), 26 | new RangeError('buffer size must be a multiple of value size'), 27 | ); 28 | test.end(); 29 | }); 30 | 31 | metatests.test('cryptoPrefetcher', (test) => { 32 | const valueSize = 4; 33 | const prefetcher = common.cryptoPrefetcher(valueSize * 5, valueSize); 34 | for (let i = 0; i < 10; i++) { 35 | const buf = prefetcher.next(); 36 | test.assert(Buffer.isBuffer(buf)); 37 | test.strictSame(buf.length, valueSize); 38 | } 39 | test.end(); 40 | }); 41 | 42 | metatests.testSync('cryptoPrefetcher for of', (test) => { 43 | const valueSize = 8; 44 | const cryptoPrefetcher = common.cryptoPrefetcher(valueSize * 5, valueSize); 45 | let i = 0; 46 | for (const buf of cryptoPrefetcher) { 47 | test.assert(Buffer.isBuffer(buf)); 48 | test.strictSame(buf.length, valueSize); 49 | if (++i === 10) break; 50 | } 51 | test.strictSame(i, 10); 52 | }); 53 | 54 | metatests.testSync( 55 | 'cryptoPrefetcher [Symbol.iterator] must be iterator', 56 | (test) => { 57 | const valueSize = 8; 58 | const cryptoPrefetcher = common.cryptoPrefetcher(valueSize * 5, valueSize); 59 | const it = cryptoPrefetcher[Symbol.iterator](); 60 | let i = 0; 61 | for (const buf of it) { 62 | test.assert(Buffer.isBuffer(buf)); 63 | test.strictSame(buf.length, valueSize); 64 | if (++i === 10) break; 65 | } 66 | test.strictSame(i, 10); 67 | }, 68 | ); 69 | 70 | metatests.testSync('cryptoPrefetcher for of wrapped', (test) => { 71 | const valueSize = 8; 72 | const cryptoPrefetcher = common.cryptoPrefetcher(valueSize * 5, valueSize); 73 | let i = 0; 74 | for (const buf of common.iter(cryptoPrefetcher).take(10)) { 75 | i++; 76 | test.assert(Buffer.isBuffer(buf)); 77 | test.strictSame(buf.length, valueSize); 78 | } 79 | test.strictSame(i, 10); 80 | }); 81 | -------------------------------------------------------------------------------- /test/mp.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metatests = require('metatests'); 4 | const common = require('..'); 5 | 6 | const iface = { 7 | fn1(x) { 8 | return x * 1; 9 | }, 10 | fn2: (x) => x * 2, 11 | prop1: 'string', 12 | prop2: 1000, 13 | prop3: false, 14 | prop4: undefined, 15 | prop5: null, 16 | }; 17 | 18 | iface.fn3 = function (x) { 19 | return x * 3; 20 | }; 21 | 22 | metatests.test('methods', (test) => { 23 | const result = common.methods(iface); 24 | test.strictSame(result, ['fn1', 'fn2', 'fn3']); 25 | test.end(); 26 | }); 27 | 28 | metatests.test('properties', (test) => { 29 | const result = common.properties(iface); 30 | test.strictSame(result, ['prop1', 'prop2', 'prop3', 'prop4', 'prop5']); 31 | test.end(); 32 | }); 33 | -------------------------------------------------------------------------------- /test/network.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metatests = require('metatests'); 4 | const common = require('..'); 5 | 6 | const net = require('net'); 7 | const os = require('os'); 8 | 9 | metatests.case( 10 | 'Common / network', 11 | { common }, 12 | { 13 | 'common.ipToInt': [ 14 | ['127.0.0.1', 2130706433], 15 | ['10.0.0.1', 167772161], 16 | ['192.168.1.10', -1062731510], 17 | ['165.225.133.150', -1511946858], 18 | ['0.0.0.0', 0], 19 | ['wrong-string', Number.NaN], 20 | ['', 0], 21 | ['8.8.8.8', 0x08080808], 22 | [undefined, 0x7f000001], 23 | ], 24 | 'common.parseHost': [ 25 | ['', 'no-host-name-in-http-headers'], 26 | ['domain.com', 'domain.com'], 27 | ['localhost', 'localhost'], 28 | ['domain.com:8080', 'domain.com'], 29 | ['localhost:8080', 'localhost'], 30 | ], 31 | }, 32 | ); 33 | 34 | metatests.test('localIPs', (test) => { 35 | const ips = common.localIPs(); 36 | test.assert(Array.isArray(ips)); 37 | test.assert(ips.every((ip) => net.isIPv4(ip))); 38 | 39 | // caching: 40 | const networkInterfacesOriginal = os.networkInterfaces; 41 | os.networkInterfaces = test.mustNotCall(() => {}, 'networkInterfaces'); 42 | test.strictSame(common.localIPs(), ips); 43 | os.networkInterfaces = networkInterfacesOriginal; 44 | test.end(); 45 | }); 46 | -------------------------------------------------------------------------------- /test/oop.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metatests = require('metatests'); 4 | const common = require('..'); 5 | 6 | metatests.test('override', (test) => { 7 | const fn = test.mustNotCall(); 8 | const obj = { 9 | fn, 10 | a: 1, 11 | }; 12 | common.override(obj, function fn() { 13 | return this.a; 14 | }); 15 | test.strictSame(obj.fn(), 1); 16 | test.strictSame(obj.fn.inherited, fn); 17 | test.end(); 18 | }); 19 | 20 | class Parent { 21 | constructor() { 22 | this.property0 = 'from Parent.constructor'; 23 | } 24 | 25 | method1() { 26 | this.property1 = 'from Parent.method1'; 27 | } 28 | } 29 | 30 | class Lazy { 31 | constructor() { 32 | this.property2 = 'from Lazy.constructor'; 33 | } 34 | 35 | method2() { 36 | this.property3 = 'from Lazy.method2'; 37 | } 38 | } 39 | 40 | class Child extends Parent { 41 | constructor() { 42 | super(); 43 | this.property4 = 'from Child.constructor'; 44 | } 45 | 46 | method3() { 47 | this.property5 = 'from Child.method3'; 48 | } 49 | } 50 | 51 | common.mixin(Child.prototype, Lazy.prototype); 52 | 53 | metatests.test('multiple inheritance with mixin', (test) => { 54 | const obj = new Child(); 55 | obj.method1(); 56 | obj.method2(); 57 | obj.method3(); 58 | test.strictSame(obj.property0, 'from Parent.constructor'); 59 | test.strictSame(obj.property4, 'from Child.constructor'); 60 | test.strictSame(obj.property1, 'from Parent.method1'); 61 | test.strictSame(obj.property3, 'from Lazy.method2'); 62 | test.strictSame(obj.property5, 'from Child.method3'); 63 | test.end(); 64 | }); 65 | -------------------------------------------------------------------------------- /test/pool.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metatests = require('metatests'); 4 | const { Pool } = require('..'); 5 | 6 | const BUF_LEN = 1000; 7 | 8 | metatests.test( 9 | 'Pool#get() on Pool without factory must return null when empty', 10 | (test) => { 11 | const p = new Pool(); 12 | test.strictSame(p.get(), null); 13 | test.end(); 14 | }, 15 | ); 16 | 17 | metatests.test( 18 | 'Pool#get() on Pool without factory must return any value put before', 19 | (test) => { 20 | const p = new Pool(); 21 | for (let i = 0; i < 10; i++) { 22 | p.put(Buffer.allocUnsafe(BUF_LEN)); 23 | } 24 | const b = p.get(); 25 | test.assert(Buffer.isBuffer(b)); 26 | test.strictSame(b.length, BUF_LEN); 27 | test.end(); 28 | }, 29 | ); 30 | 31 | metatests.test( 32 | 'Pool#get() on empty Pool with factory must return values retrieved from it', 33 | (test) => { 34 | const p = new Pool(test.mustCall(() => Buffer.allocUnsafe(BUF_LEN), 2)); 35 | const b1 = p.get(); 36 | const b2 = p.get(); 37 | test.assert(Buffer.isBuffer(b1)); 38 | test.assert(Buffer.isBuffer(b2)); 39 | test.strictSame(b1.length, BUF_LEN); 40 | test.strictSame(b2.length, BUF_LEN); 41 | test.end(); 42 | }, 43 | ); 44 | -------------------------------------------------------------------------------- /test/sort.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*eslint max-len: ["error", { "code": 120 }]*/ 4 | 5 | const metatests = require('metatests'); 6 | const common = require('..'); 7 | 8 | const CONFIG_FILES_PRIORITY = [ 9 | 'sandbox.js', 10 | 'log.js', 11 | 'scale.js', 12 | 'servers.js', 13 | 'databases.js', 14 | 'sessions.js', 15 | 'tasks.js', 16 | 'application.js', 17 | 'files.js', 18 | 'filestorage.js', 19 | 'mail.js', 20 | 'hosts.js', 21 | 'routes.js', 22 | ]; 23 | 24 | metatests.case( 25 | 'Common / sort', 26 | { common }, 27 | { 28 | 'common.sortComparePriority': [ 29 | [CONFIG_FILES_PRIORITY, 'files.js', 'sandbox.js', 1], 30 | [CONFIG_FILES_PRIORITY, 'filestorage.js', 'routes.js', -1], 31 | [CONFIG_FILES_PRIORITY, 'unknown.js', 'sandbox.js', 1], 32 | [CONFIG_FILES_PRIORITY, 'log.js', 'sandbox.js', 1], 33 | [CONFIG_FILES_PRIORITY, 'sandbox.js', 'sandbox.js', 0], 34 | [CONFIG_FILES_PRIORITY, 'log.js', 'log.js', 0], 35 | [CONFIG_FILES_PRIORITY, 'tasks.js', 'application.js', -1], 36 | [CONFIG_FILES_PRIORITY, 'tasks.js', 'missing_file', -1], 37 | ], 38 | 'common.sortCompareDirectories': [ 39 | [{ name: '/abc' }, { name: 'abc.ext' }, -1], 40 | [{ name: 'ABC.ext' }, { name: '/abc' }, 1], 41 | [{ name: 'abc' }, { name: 'ABC.ext' }, 1], 42 | [{ name: '/ABC' }, { name: '/abc.ext' }, -1], 43 | [{ name: '/abc.ext' }, { name: '/ABC' }, 1], 44 | [{ name: '/abc.ext' }, { name: '/ABC' }, 1], 45 | [{ name: '/ABC' }, { name: '/ABC' }, 0], 46 | [{ name: 'abc.ext' }, { name: 'abc.ext' }, 0], 47 | [{ name: 'abc.ext' }, { name: 'def.ext' }, -1], 48 | [{ name: 'def.ext' }, { name: 'abc.ext' }, 1], 49 | ], 50 | 'common.sortCompareByName': [ 51 | [{ name: 'abc' }, { name: 'def' }, -1], 52 | [{ name: 'def' }, { name: 'abc' }, 1], 53 | [{ name: 'abc' }, { name: 'abc' }, 0], 54 | [{ name: 'def' }, { name: 'def' }, 0], 55 | [{ name: 'abc' }, { name: 'a' }, 1], 56 | [{ name: 'a' }, { name: 'abc' }, -1], 57 | [{ name: '123' }, { name: 'name' }, -1], 58 | ], 59 | }, 60 | ); 61 | 62 | metatests.test('sortCompareDirectories', (test) => { 63 | const array = [ 64 | { name: 'file0.txt' }, 65 | { name: '/dir' }, 66 | { name: 'file1.txt' }, 67 | { name: 'file0.txt' }, 68 | { name: '/foo' }, 69 | ]; 70 | const sorted = [ 71 | { name: '/dir' }, 72 | { name: '/foo' }, 73 | { name: 'file0.txt' }, 74 | { name: 'file0.txt' }, 75 | { name: 'file1.txt' }, 76 | ]; 77 | test.strictSame(array.sort(common.sortCompareDirectories), sorted); 78 | test.end(); 79 | }); 80 | 81 | metatests.test('sortCompareByName', (test) => { 82 | const array = [{ name: 'c' }, { name: 'a' }, { name: 'a' }, { name: 'b' }]; 83 | const sorted = [{ name: 'a' }, { name: 'a' }, { name: 'b' }, { name: 'c' }]; 84 | test.strictSame(array.sort(common.sortCompareByName), sorted); 85 | test.end(); 86 | }); 87 | -------------------------------------------------------------------------------- /test/stream.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const util = require('util'); 6 | 7 | const metatests = require('metatests'); 8 | const common = require('..'); 9 | 10 | const fixturesPaths = ['empty', 'greetings'].map((f) => 11 | path.join(__dirname, 'fixtures', f), 12 | ); 13 | const readFile = util.promisify(fs.readFile); 14 | 15 | metatests.test( 16 | 'MemoryWritable correctly works with no encoding specified', 17 | async (test) => { 18 | for (const path of fixturesPaths) { 19 | const originalReadable = fs.createReadStream(path); 20 | const memoryStream = new common.MemoryWritable(); 21 | originalReadable.pipe(memoryStream); 22 | test.strictSame(await readFile(path), await memoryStream.getData()); 23 | } 24 | }, 25 | ); 26 | 27 | metatests.test( 28 | 'MemoryWritable correctly works the specified encoding', 29 | async (test) => { 30 | for (const path of fixturesPaths) { 31 | const originalReadable = fs.createReadStream(path); 32 | const memoryStream = new common.MemoryWritable(); 33 | originalReadable.pipe(memoryStream); 34 | test.strictSame( 35 | await readFile(path, { encoding: 'utf8' }), 36 | await memoryStream.getData('utf8'), 37 | ); 38 | } 39 | }, 40 | ); 41 | 42 | metatests.test('MemoryWritable rejects invalid encoding', async (test) => { 43 | const memoryStream = new common.MemoryWritable(); 44 | memoryStream.end('hello'); 45 | await test.rejects(memoryStream.getData('invalid encoding')); 46 | }); 47 | 48 | metatests.test('MemoryWritable handles custom sizeLimit', async (test) => { 49 | const totalSize = 60; 50 | const limitSize = 40; 51 | const memoryStream = new common.MemoryWritable(limitSize); 52 | test.rejects( 53 | () => { 54 | const result = memoryStream.getData(); 55 | memoryStream.write(Buffer.alloc(totalSize)); 56 | return result; 57 | }, 58 | new RangeError(`size limit exceeded by ${totalSize - limitSize} bytes`), 59 | ); 60 | }); 61 | -------------------------------------------------------------------------------- /test/strings.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*eslint max-len: ["error", { "code": 120 }]*/ 4 | 5 | const metatests = require('metatests'); 6 | const common = require('..'); 7 | 8 | metatests.case( 9 | 'Metarhia common library', 10 | { common }, 11 | { 12 | 'common.subst': [ 13 | ['Hello, name', { name: 'Ali' }, '', true, 'Hello, name'], 14 | [ 15 | 'Hello, @date@', 16 | { date: new Date(0) }, 17 | '', 18 | true, 19 | 'Hello, 1970-01-01 00:00', 20 | ], 21 | ['Hello, @n@', { name: 'Ali' }, '', true, 'Hello, [undefined]'], 22 | ['Hello, @name@', { name: null }, '', true, 'Hello, [null]'], 23 | [ 24 | 'Hello, @name@', 25 | { name: { first: 'Ali' } }, 26 | '', 27 | true, 28 | 'Hello, [object]', 29 | ], 30 | ['Hello, @name@', { name: ['Ali'] }, '', true, 'Hello, [array]'], 31 | ['Hello, @name@', { name: 'Ali' }, '', true, 'Hello, Ali'], 32 | [ 33 | 'Hello, @.name@', 34 | { person: { name: 'Ali' } }, 35 | 'person', 36 | true, 37 | 'Hello, Ali', 38 | ], 39 | [ 40 | '@w@, @.name@', 41 | { w: 'Hello', person: { name: 'Ali' } }, 42 | 'person', 43 | true, 44 | 'Hello, Ali', 45 | ], 46 | ['Hello, @.value@', { name: 'Ali' }, 'name', true, 'Hello, Ali'], 47 | ['Hello, @username', {}, '', true, 'Hello, @username'], 48 | ['Hello, @name@', { name: '' }, '', true, 'Hello, <Ali>'], 49 | ['Hello, @name@', { name: '' }, '', false, 'Hello, '], 50 | ], 51 | 'common.section': [ 52 | ['All you need is JavaScript', 'is', ['All you need ', ' JavaScript']], 53 | ['All you need is JavaScript', 'no', ['All you need is JavaScript', '']], 54 | ['All you need is JavaScript', 'JavaScript', ['All you need is ', '']], 55 | ['All you need is JavaScript', 'All', ['', ' you need is JavaScript']], 56 | ['All you need is JavaScript', 'a', ['All you need is J', 'vaScript']], 57 | ], 58 | 'common.rsection': [ 59 | ['All you need is JavaScript', 'is', ['All you need ', ' JavaScript']], 60 | ['All you need is JavaScript', 'no', ['All you need is JavaScript', '']], 61 | ['All you need is JavaScript', 'JavaScript', ['All you need is ', '']], 62 | ['All you need is JavaScript', 'All', ['', ' you need is JavaScript']], 63 | ['All you need is JavaScript', 'a', ['All you need is Jav', 'Script']], 64 | ], 65 | 'common.split': [ 66 | ['a,b,c,d', ',', 2, ['a', 'b']], 67 | ['a,b,c,d', ['a', 'b', 'c', 'd']], 68 | ['a;b;c;d', ';', ['a', 'b', 'c', 'd']], 69 | ['a,b,c,d', ';', ['a,b,c,d']], 70 | ], 71 | 'common.rsplit': [ 72 | ['a,b,c,d', ',', 2, ['c', 'd']], 73 | ['a,b,c,d', ['a', 'b', 'c', 'd']], 74 | ['a;b;c;d', ';', ['a', 'b', 'c', 'd']], 75 | ['a,b,c,d', ';', ['a,b,c,d']], 76 | ], 77 | 'common.htmlEscape': [ 78 | ['text', 'text'], 79 | ['', '<tag>'], 80 | ['You & Me', 'You &amp; Me'], 81 | ['You & Me', 'You & Me'], 82 | ['"Quotation"', '"Quotation"'], 83 | ], 84 | 'common.fileExt': [ 85 | ['/dir/dir/file.txt', 'txt'], 86 | ['/dir/dir/file.txt', 'txt'], 87 | ['\\dir\\file.txt', 'txt'], 88 | ['/dir/dir/file.txt', 'txt'], 89 | ['/dir/file.txt', 'txt'], 90 | ['/dir/file.TXt', 'txt'], 91 | ['//file.txt', 'txt'], 92 | ['file.txt', 'txt'], 93 | ['/dir.ext/', 'ext'], 94 | ['/dir/', ''], 95 | ['/', ''], 96 | ['.', ''], 97 | ['', ''], 98 | ], 99 | 'common.removeExt': [ 100 | ['/dir/dir/file.txt', '/dir/dir/file'], 101 | ['\\dir\\file.txt', '\\dir\\file'], 102 | ['//file.txt', '//file'], 103 | ['file.txt', 'file'], 104 | ['/dir.ext/', '/dir'], 105 | ['/dir.ext/file.txt', '/dir.ext/file'], 106 | ['.', ''], 107 | ], 108 | 'common.spinalToCamel': [ 109 | ['hello-world', 'helloWorld'], 110 | ['hello_world', 'helloWorld'], 111 | ['one-two-three', 'oneTwoThree'], 112 | ['one_two_three', 'oneTwoThree'], 113 | ['OneTwoThree', 'OneTwoThree'], 114 | ['oneTwoThree', 'oneTwoThree'], 115 | ['hello', 'hello'], 116 | ['h', 'h'], 117 | ['-', ''], 118 | ['_', ''], 119 | ['', ''], 120 | ], 121 | 'common.capitalize': [ 122 | ['abc', 'Abc'], 123 | ['Abc', 'Abc'], 124 | ['aBC', 'Abc'], 125 | ['ABC', 'Abc'], 126 | ['a', 'A'], 127 | [' bc', ' Bc'], 128 | [' ', ' '], 129 | ['', ''], 130 | ['+', '+'], 131 | ], 132 | 'common.between': [ 133 | ['abcdefghijk', 'cd', 'h', 'efg'], 134 | ['field="value"', '"', '"', 'value'], 135 | ['field:"value"', '"', '"', 'value'], 136 | ['field[value]', '[', ']', 'value'], 137 | ['kjihgfedcba', 'cd', 'h', ''], 138 | ['kjihgfedcba', 'dc', 'h', ''], 139 | ['field="value"', '=', '=', ''], 140 | ['field[value]', '{', '}', ''], 141 | ['{a:"b",c:"d"}', '"', '"', 'b'], 142 | ['abcdefghijk', 'cd', 'efghijk'], 143 | ], 144 | 'common.escapeRegExp': [ 145 | [ 146 | '/path/to/res?search=this.that&a=b', 147 | '\\/path\\/to\\/res\\?search=this\\.that&a=b', 148 | ], 149 | [ 150 | '/path/to/res?search=this.that', 151 | '\\/path\\/to\\/res\\?search=this\\.that', 152 | ], 153 | ['/path/to/res?search', '\\/path\\/to\\/res\\?search'], 154 | ['/path/to/res', '\\/path\\/to\\/res'], 155 | ['/path', '\\/path'], 156 | ], 157 | 'common.newEscapedRegExp': [ 158 | [ 159 | '/path/to/res?search=this.that&a=b', 160 | /\/path\/to\/res\?search=this\.that&a=b/g, 161 | ], 162 | ['/path/to/res?search=this.that', /\/path\/to\/res\?search=this\.that/g], 163 | ['/path/to/res?search', /\/path\/to\/res\?search/g], 164 | ['/path/to/res', /\/path\/to\/res/g], 165 | ['/path', /\/path/g], 166 | ], 167 | 'common.addTrailingSlash': [ 168 | ['/path', '/path/'], 169 | ['/path/', '/path/'], 170 | ['/', '/'], 171 | ['', '/'], 172 | ], 173 | 'common.stripTrailingSlash': [ 174 | ['/path', '/path'], 175 | ['/path/', '/path'], 176 | ['/', ''], 177 | ['', ''], 178 | ], 179 | 'common.dirname': [ 180 | ['/path/dir/', '/path/'], 181 | ['/path/dir', '/path/'], 182 | ['/path/', '/'], 183 | ['/path', '/'], 184 | ['/', '/'], 185 | ['', './'], 186 | ], 187 | 'common.removeBOM': [ 188 | ['\uBBBF\uFEFFabc', 'abc'], 189 | ['\uBBBF\uFEFF', ''], 190 | ['\uFEFFabc', 'abc'], 191 | ['\uBBBFabc', 'abc'], 192 | ['\uFEFF', ''], 193 | ['\uBBBF', ''], 194 | ['abc', 'abc'], 195 | [42, 42], 196 | ], 197 | 'common.arrayRegExp': [ 198 | [['*'], /^.*$/], 199 | [['/css/*', '/folder*'], /^((\/css\/.*)|(\/folder.*))$/], 200 | [['/', '/js/*'], /^((\/)|(\/js\/.*))$/], 201 | [['/css/*.css'], /^\/css\/.*\.css$/], 202 | [['*/css/*'], /^.*\/css\/.*$/], 203 | [[], null], 204 | ], 205 | 'common.normalizeEmail': [ 206 | ['testemail@example.com', 'testemail@example.com'], 207 | ['testemail@EXAMPLE.COM', 'testemail@example.com'], 208 | ['testemail@eXaMpLe.CoM', 'testemail@example.com'], 209 | ['TestEmail@example.COM', 'TestEmail@example.com'], 210 | ['TESTEMAIL@EXAMPLE.COM', 'TESTEMAIL@example.com'], 211 | ], 212 | }, 213 | ); 214 | -------------------------------------------------------------------------------- /test/units.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metatests = require('metatests'); 4 | const common = require('..'); 5 | 6 | metatests.case( 7 | 'Common / units', 8 | { common }, 9 | { 10 | 'common.duration': [ 11 | ['1d', 86400000], 12 | ['2d', 172800000], 13 | ['10h', 36000000], 14 | ['7m', 420000], 15 | ['13s', 13000], 16 | ['2d 43s', 172843000], 17 | ['5d 17h 52m 1s', 496321000], 18 | ['1d 10h 7m 13s', 122833000], 19 | ['1s', 1000], 20 | [500, 500], 21 | [0, 0], 22 | ['', 0], 23 | ['15', 0], 24 | ['10q', 0], 25 | [null, 0], 26 | [undefined, 0], 27 | ], 28 | 'common.durationToString': [ 29 | [13000, '13s'], 30 | [420000, '7m'], 31 | [86400000, '1d'], 32 | [36000000, '10h'], 33 | [172800000, '2d'], 34 | [172843000, '2d 43s'], 35 | [496321000, '5d 17h 52m 1s'], 36 | [122833000, '1d 10h 7m 13s'], 37 | [1000, '1s'], 38 | [0, '0s'], 39 | ['', '0s'], 40 | ['15', '0s'], 41 | ['10q', '0s'], 42 | [null, '0s'], 43 | [undefined, '0s'], 44 | ], 45 | 'common.bytesToSize': [ 46 | [0, '0'], 47 | [1, '1'], 48 | [100, '100'], 49 | [999, '999'], 50 | [1000, '1 Kb'], 51 | [1023, '1 Kb'], 52 | [1024, '1 Kb'], 53 | [1025, '1 Kb'], 54 | [1111, '1 Kb'], 55 | [2222, '2 Kb'], 56 | [10000, '10 Kb'], 57 | [1000000, '1 Mb'], 58 | [100000000, '100 Mb'], 59 | [10000000000, '10 Gb'], 60 | [1000000000000, '1 Tb'], 61 | [100000000000000, '100 Tb'], 62 | [10000000000000000, '10 Pb'], 63 | [1000000000000000000, '1 Eb'], 64 | [100000000000000000000, '100 Eb'], 65 | [10000000000000000000000, '10 Zb'], 66 | [1000000000000000000000000, '1 Yb'], 67 | ], 68 | 'common.sizeToBytes': [ 69 | ['', NaN], 70 | [0, 0], 71 | ['0', 0], 72 | ['1', 1], 73 | [512, 512], 74 | ['100', 100], 75 | ['999', 999], 76 | ['1 Kb', 1000], 77 | ['2 Kb', 2000], 78 | ['10 Kb', 10000], 79 | ['1 Mb', 1000000], 80 | ['100 Mb', 100000000], 81 | ['10 Gb', 10000000000], 82 | ['1 Tb', 1000000000000], 83 | ['100 Tb', 100000000000000], 84 | ['10 Pb', 10000000000000000], 85 | ['1 Eb', 1000000000000000000], 86 | ['100 Eb', 100000000000000000000], 87 | ['10 Zb', 10000000000000000000000], 88 | ['1 Yb', 1000000000000000000000000], 89 | ], 90 | }, 91 | ); 92 | -------------------------------------------------------------------------------- /test/utilities.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metatests = require('metatests'); 4 | const common = require('..'); 5 | 6 | const path = require('path'); 7 | 8 | metatests.test('safe require success', (test) => { 9 | const safeRequire = common.safe(require); 10 | const result = safeRequire('./mp'); 11 | test.strictSame(!!result[0], false); 12 | test.strictSame(!!result[1], true); 13 | test.end(); 14 | }); 15 | 16 | metatests.test('safe require fail', (test) => { 17 | const safeRequire = common.safe(require); 18 | const result = safeRequire('./name'); 19 | test.strictSame(!!result[0], true); 20 | test.strictSame(!!result[1], false); 21 | test.end(); 22 | }); 23 | 24 | metatests.test('safe parser success', (test) => { 25 | const parser = common.safe(JSON.parse); 26 | const result = parser('{"a":5}'); 27 | test.strictSame(!!result[0], false); 28 | test.strictSame(!!result[1], true); 29 | test.end(); 30 | }); 31 | 32 | metatests.test('safe parser fail', (test) => { 33 | const parser = common.safe(JSON.parse); 34 | const result = parser('{a:}'); 35 | test.strictSame(!!result[0], true); 36 | test.strictSame(!!result[1], false); 37 | test.end(); 38 | }); 39 | 40 | const callerFilepathFixture = require('./fixtures/callerFilepath'); 41 | 42 | metatests.test('Check called filename/filepath', (test) => { 43 | test.ok(common.callerFilepath().endsWith(path.join('test', 'utilities.js'))); 44 | test.strictSame(common.callerFilename(), 'utilities.js'); 45 | test.end(); 46 | }); 47 | 48 | metatests.test('Check called filename/filepath parent', (test) => { 49 | child(test, 1); 50 | child(test, /child/); 51 | test.end(); 52 | }); 53 | 54 | function child(test, depth) { 55 | test.ok( 56 | common.callerFilepath(depth).endsWith(path.join('test', 'utilities.js')), 57 | ); 58 | test.strictSame(common.callerFilename(depth), 'utilities.js'); 59 | } 60 | 61 | metatests.test('Check filepath filter all', (test) => { 62 | test.strictSame(common.callerFilepath(/./), ''); 63 | test.end(); 64 | }); 65 | 66 | metatests.test('Check filepath with indirection', (test) => { 67 | test.ok(callerFilepathFixture(1).endsWith(path.join('test', 'utilities.js'))); 68 | test.ok( 69 | callerFilepathFixture(/callerFilepath/).endsWith( 70 | path.join('test', 'utilities.js'), 71 | ), 72 | ); 73 | test.end(); 74 | }); 75 | 76 | metatests.testSync('Check captureMaxStack', (test) => { 77 | const stack = common.captureMaxStack(); 78 | test.log(stack); 79 | test.assert(stack.match(/Error[: \w]*\n/), 'stack must match a regexp'); 80 | }); 81 | 82 | metatests.test('Check called filename/filepath custom stack', (test) => { 83 | const limit = Error.stackTraceLimit; 84 | Error.stackTraceLimit = 1; 85 | const stack = new Error().stack; 86 | Error.stackTraceLimit = limit; 87 | test.strictSame(common.callerFilepath(0, stack), ''); 88 | test.strictSame(common.callerFilename(0, stack), ''); 89 | test.end(); 90 | }); 91 | -------------------------------------------------------------------------------- /tools/esmodules-export-gen.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | const fs = require('fs'); 7 | const common = require('../common'); 8 | 9 | const COMMON_JS_FILENAME = 'common.js'; 10 | const COMMON_MJS_FILEPATH = './common.mjs'; 11 | 12 | const header = (indexPath) => 13 | `// This is an automaticaly generated file. DO NOT MODIFY MANUALLY. 14 | import common from './${indexPath}'; 15 | 16 | export default common; 17 | 18 | `; 19 | 20 | const generateExports = (exportsData) => 21 | Object.keys(exportsData) 22 | .map((name) => `export const ${name} = common.${name};`) 23 | .join('\n'); 24 | 25 | const root = path.join(__dirname, '..'); 26 | const indexDirectory = path.dirname(path.resolve(COMMON_MJS_FILEPATH)); 27 | const indexRel = path.relative(indexDirectory, path.resolve(root)); 28 | const indexRelPath = path.join(indexRel, COMMON_JS_FILENAME); 29 | 30 | const exportsString = header(indexRelPath) + generateExports(common) + '\n'; 31 | 32 | fs.writeFile(COMMON_MJS_FILEPATH, exportsString, (err) => { 33 | if (err) { 34 | console.error('Failed to generate ECMAScript Modules export file.\n' + err); 35 | process.exit(1); 36 | } 37 | console.log('Successfully generated ECMAScript Modules export file.'); 38 | }); 39 | -------------------------------------------------------------------------------- /tools/unicode-category-parser.js: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2018 mdsf contributors 4 | // (see https://github.com/metarhia/mdsf/blob/master/AUTHORS). 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 'use strict'; 24 | 25 | const fs = require('fs'); 26 | const http = require('http'); 27 | const path = require('path'); 28 | const readline = require('readline'); 29 | 30 | const UNICODE_CATEGORIES = ['Lu', 'Ll']; 31 | const UNICODE_VERSION = '11.0.0'; 32 | const UCD_LINK = 33 | 'http://www.unicode.org/Public/' + UNICODE_VERSION + '/ucd/UnicodeData.txt'; 34 | 35 | const OUTPUT_PATH = path.join(__dirname, '../lib/unicode-categories.js'); 36 | 37 | /* eslint-disable implicit-arrow-linebreak */ 38 | const getFileHeader = () => 39 | `// Copyright (c) 2017-2018 Metarhia contributors. Use of this source code is 40 | // governed by the MIT license that can be found in the LICENSE file. 41 | // 42 | // 43 | // This file contains data derived from the Unicode Data Files. 44 | // The following license applies to this data: 45 | // 46 | // COPYRIGHT AND PERMISSION NOTICE 47 | // 48 | // Copyright © 1991-2018 Unicode, Inc. All rights reserved. 49 | // Distributed under the Terms of Use in http://www.unicode.org/copyright.html. 50 | // 51 | // Permission is hereby granted, free of charge, to any person obtaining 52 | // a copy of the Unicode data files and any associated documentation 53 | // (the "Data Files") or Unicode software and any associated documentation 54 | // (the "Software") to deal in the Data Files or Software 55 | // without restriction, including without limitation the rights to use, 56 | // copy, modify, merge, publish, distribute, and/or sell copies of 57 | // the Data Files or Software, and to permit persons to whom the Data Files 58 | // or Software are furnished to do so, provided that either 59 | // (a) this copyright and permission notice appear with all copies 60 | // of the Data Files or Software, or 61 | // (b) this copyright and permission notice appear in associated 62 | // Documentation. 63 | // 64 | // THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF 65 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 66 | // WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 67 | // NONINFRINGEMENT OF THIRD PARTY RIGHTS. 68 | // IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS 69 | // NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL 70 | // DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, 71 | // DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 72 | // TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 73 | // PERFORMANCE OF THE DATA FILES OR SOFTWARE. 74 | // 75 | // Except as contained in this notice, the name of a copyright holder 76 | // shall not be used in advertising or otherwise to promote the sale, 77 | // use or other dealings in these Data Files or Software without prior 78 | // written authorization of the copyright holder. 79 | // 80 | // 81 | // This file is automatically generated by tools/unicode-category-parcer.js. 82 | // Unicode version ${UNICODE_VERSION}. 83 | // 84 | // Do not edit this file manually! 85 | /* eslint-disable */ 86 | module.exports = `; 87 | /* eslint-enable implicit-arrow-linebreak */ 88 | 89 | const resultObject = {}; 90 | resultObject.addRange = (range) => { 91 | if (range.max) { 92 | resultObject[range.category].push([range.min, range.max]); 93 | } else if (range.min) { 94 | resultObject[range.category].push(range.min); 95 | } 96 | }; 97 | 98 | UNICODE_CATEGORIES.forEach((category) => { 99 | resultObject[category] = []; 100 | }); 101 | 102 | http.get(UCD_LINK, (res) => { 103 | const linereader = readline.createInterface({ input: res, historySize: 0 }); 104 | let prevCategory; 105 | let range = {}; 106 | linereader.on('line', (line) => { 107 | const [code, , category] = line.split(';'); 108 | if (UNICODE_CATEGORIES.includes(category)) { 109 | const decimalCode = parseInt(code, 16); 110 | if (category === prevCategory) { 111 | range.max = decimalCode; 112 | } else { 113 | resultObject.addRange(range); 114 | range = { min: decimalCode, max: undefined, category }; 115 | } 116 | } 117 | prevCategory = category; 118 | }); 119 | 120 | linereader.on('close', () => { 121 | resultObject.addRange(range); 122 | const resultData = getFileHeader() + JSON.stringify(resultObject) + '\n'; 123 | fs.writeFileSync(OUTPUT_PATH, resultData); 124 | }); 125 | }); 126 | --------------------------------------------------------------------------------