o-toolbox
v7.7.0
Published
Utilities for Node.js.
Downloads
12
Readme
Toolbox
Utilities for Node.js.
Installation
npm install o-toolbox
Documentation
http://o-programming-language.org/
Usage
Type
Utility to query the type of an object
const {Type} = require('o-toolbox')
Type.isUndefined(object)
Type.isNull(object)
Type.isBoolean(object)
Type.isString(object)
Type.isInteger(object)
Type.isFloat(object)
Type.isFunction(object)
Type.isArray(object)
Type.isObject(object)
Type.isPromiseAlike(object)
Type.respondsTo(object, methodName)
Using typeof
has some problems. typeof(null) === 'object'
, Number.isInteger()
exists but Number.isFloat()
does not and there is no type boolean
to name a few.
This utility aims to address some of these issues.
Using instanceof
is also discouraged since it does not allow full polymorphism. Instead of asking for the class of an object in some cases asking whether the object responds to message or not might be a better choice.
To ask that there's the method
const {Type} = require('o-toolbox')
Type.repondsTo(object, methodName)
Finally there's the method
Type.displayString(object)
which returns a string representation of the object suitable for debugging messages. For example
const {Type} = require('o-toolbox')
Type.displayString(undefined) === 'undefined'
Type.displayString(null) === 'null'
Type.displayString('a string') === "'a string'"
Type.displayString(new User()) === "a User"
Copy
Copies addresses the copy of an object without having to check first the type of the object. It copies undefined and null values, basic type values, arrays, objects and class instances.
To make a shallow copy of an object do
Copy.shallow(null)
Copy.shallow(1)
Copy.shallow({a: 1})
Copy.shallow(new SomeObject())
Copy.shallow([1,2,3])
If the object is an instance of a class it's expected to implement a .copy()
method
class SomeObject {
copy() {
return new this.constructor(this.props)
}
}
To make a deep copy of an object use
Copy.deep(null)
Copy.deep(1)
Copy.deep({a: 1})
Copy.deep(new SomeObject())
Copy.deep([1,2,3])
If the object is an instance of a class it's expected to implement a .copy()
and/or a .deepCopy()
method
const Copy = require('o-toolbox')
class SomeObject {
copy() {
return new this.constructor(this.props)
}
deepCopy() {
const propsCopy = this.props.map((prop)=> Copy.deep(prop))
return new this.constructor(propsCopy)
}
}
Dictionary
A subclass of js Map
.
It handles Map
methods and adds the protocol
const {Dictionary} = require('o-toolbox')
const dictionary = new Dictionary()
dictionary.isEmpty()
dictionary.getKeys()
dictionary.getValues()
dictionary.defines({ key: 'someKey' })
dictionary.define({ key: 'someKey', value: 10 })
dictionary.at ({ key: 'someKey'})
dictionary.at ({
key: 'someKey',
ifPresent: (value) => { ... }
})
dictionary.at ({
key: 'someKey',
ifAbsent: () => { ... }
})
dictionary.at ({
key: 'someKey',
ifPresent: (value) => { ... },
ifAbsent: () => { ... }
})
dictionary.mergeKeysFrom ({prop1: 1, prop2: 2})
dictionary.mergeKeysFromDictionary(otherDictionary)
// shallow copy
dictionary.copy(anotherDictionary)
// The following methods are sync and will not work as expected with async code.
dictionary.keysDo((key) => {
// ...
})
dictionary.valuesDo((key) => {
// ...
})
dictionary.keysAndValuesDo((key, value) => {
// ...
})
Set
A subclass of js Set
.
It handles Set
methods and also adds the protocol
const {Set} = require('o-toolbox')
const set = new Set()
set.isEmpty()
set.includes(item)
set.toArray()
set.equals(anotherSet)
set.contains(anotherSet)
set.intersects(anotherSet)
set.addAll(items)
set.remove(item)
set.removeAll(items)
set.union(anotherSet)
set.difference(anotherSet)
set.intersection(anotherSet)
set.copy()
SequentialCollection
An Array like object, with convenience methods and validations of boundaries, and polymorphic with other collections in this library
const {SequentialCollection} = require('o-toolbox')
const sequentialCollection = new SequentialCollection()
const sequentialCollection = SequentialCollection.with(item)
const sequentialCollection = SequentialCollection.withAll(items)
sequentialCollection.add(item)
sequentialCollection.addAll(items)
sequentialCollection.at({ index: 1 }) // raises an error if index 1 is out of bounds
sequentialCollection.at({ index: 1, ifAbsent: () => null }) // returns null if index 1 is out of bounds
sequentialCollection.countAllThat( (item, i) => item > 1)
sequentialCollection.clear()
sequentialCollection.filter( (item, i) => item > 1 )
sequentialCollection.forEach( (item, i) => {
// ...
})
sequentialCollection.forEachInReverseOrder( (item, i) => {
// ...
})
sequentialCollection.getFirst() // raises an error if empty
sequentialCollection.getFirstIndexThat({ block: (item, i) => item > 1 })
sequentialCollection.getFirstThat({ block: (item, i) => item > 1 })
sequentialCollection.getIndexOf(item)
sequentialCollection.getSize()
sequentialCollection.getLast() // raises an error if empty
sequentialCollection.isEmpty()
sequentialCollection.map( (item, i) => {
return // ...
})
sequentialCollection.remove(item)
sequentialCollection.removeAll(items)
sequentialCollection.removeAllThat( (item, i) => item > 1 )
const first = sequentialCollection.removeFirst()
const item = sequentialCollection.removeItemAt({ index: 1 })
const last = sequentialCollection.removeLast()
sequentialCollection.sortUsing({ block: (item1, item2) => {
if (item1 < item2) { return -1 }
if (item1 > item2) { return 1 }
return 0
})
sequentialCollection.toArray()
SortedCollection
A SequentialCollection that ensures the order of its items
Its interface is the same as SequentialCollection, except for the creation of the collection, which takes a sort block
const {SortedCollection} = require('o-toolbox')
const sortedCollection = new SortedCollection()
const sortedCollection = new SortedCollection({
sortBlock: (item1, item2) => {
if (item1 < item2) { return -1 }
if (item1 > item2) { return 1 }
return 0
}
})
const sortedCollection = SequentialCollection.with({
item: item,
sortBlock: (item1, item2) => {
if (item1 < item2) { return -1 }
if (item1 > item2) { return 1 }
return 0
}
})
const sortedCollection = SequentialCollection.withAll({
items: items,
sortBlock: (item1, item2) => {
if (item1 < item2) { return -1 }
if (item1 > item2) { return 1 }
return 0
}
})
Path
A path in the file system.
Path
s are immutable values, meaning that no Path
method changes its state. When a modification is done it returns a new Path
object.
// Path is not included in index.js for it depends on 'path' and 'fs' modules
const Path = require('o-toolbox/src/Path')
const path = new Path('/etc/logs/app.log')
path.exists()
path.isDirectory()
path.isFile()
// Returns true of the path string matches the given regex
path.matches(regex)
path.toString()
// subPath can be a String or a Path
path.append(subPath)
path.back()
path.back({ n: 2 })
path.getStats()
path.getFileExtension()
path.getFileExtension({dot: false})
path.getFilename()
path.getFilename({ extension: true })
path.getBasePath()
path.getSegments()
path.getFirstSegment()
path.getFirstSegments({ n: 2 })
path.getLastSegment()
path.getLastSegments({ n: 2 })
path.toAbsolutePath()
path.getFiles()
path.getFiles({ recursive: true })
path.getFiles({ matching: /[.]log^/ })
path.getFiles({ recursive: true, matching: /[.]log^/ })
// Returns the contents of the file as a String.
// Raises an error if the file does not exist
path.getFileContents()
// Returns the contents of the file as a JSON object.
// Raises an error if the file does not exist
// Raises an error if the file contents is not a valid JSON string
path.getJSONFileContents()
// Writes the given contents to the file
// Returns the path object.
// Creates the file if it does not exist
// Overrides the file if it exists
// Raises an error if the file directory does not exist
path.writeFileContents("File contents")
// Writes the given object as a JSON string to the file
// Returns the path object.
// Creates the file if it does not exist
// Overrides the file if it exists
// Raises an error if the file directory does not exist
path.writeJSONFileContents(object)
// Deletes the file
// Raises an error if the path does not exist
// Raises an error if the path is a directory
path.deleteFile()
// Deletes the file
// Does nothing if the path does not exist
// Raises an error if the path is a directory
path.ensureDeleteFile()
path.getDirectories()
path.getDirectories({ recursive: true })
path.getDirectories({ matching: /[.]log^/ })
path.getDirectories({ recursive: true, matching: /[.]log^/ })
// Creates all the directories in the Path
// If the directory exists does nothing
// If the directory partially exists creates the missing subdiretories
path.createDirectory()
path.files({
do: (filePath) => { ... }
})
path.files({
recursive: true,
do: (filePath) => { ... }
})
path.files({
recursive: true,
matching: /[.]log^/,
do: (filePath) => { ... }
})
path.directories({
do: (dirPath) => { ... }
})
path.directories({
recursive: true,
do: (dirPath) => { ... }
})
path.directories({
recursive: true,
matching: /[.]log^/,
do: (dirPath) => { ... }
})
path.filesAndDirectories({
do: (dirPath) => { ... }
})
path.filesAndDirectories({
recursive: true,
do: (dirPath) => { ... }
})
path.filesAndDirectories({
recursive: true,
matching: /[.]log^/,
do: (dirPath) => { ... }
})
Environment
Class to conditionally evaluate blocks of code depending on the environemnt where the script is running
The Environment object receives the values in process.env
or a copy with the variables of interest and provides methods for evaluating functions only in certain environments:
const {Environment} = require('o-toolbox')
const environment = Environment({ env: process.env })
environment.isProduction() // env.NODE_ENV = 'production'
environment.isDevelopment() // env.NODE_ENV = 'development'
environment.isTesting() // env.NODE_ENV = 'testing'
environment.on({
development: () => { ... },
staging: () => { ... },
production: () => { ... },
default: () => { ... }
})
environment.on({
development: () => app.use('/', debuggingRoutes)
})
Errors
A hierarchy of generic errors
RuntimeError
NotYetImplementedError
SubclassResponsibilityError
AbsentKeyError
AbsentMethodError
PathDoesNotExistError
UnexpectedTypeError
ParamsError
MissingRequiredParamError
UnexpectedParamError
Each of these errors can be raised with sync Exception
or with an async Promise rejection
const {RuntimeError} = require('o-toolbox')
RuntimeError.raise({ message: 'Error message here ...' })
RuntimeError.raise({ message: 'Error message here ...', async: true })
// which is the same as doing
new RuntimeError().raise({ message: 'Error message here ...' })
The following errors can be raised also with arguments using the method .raiseOn(...)
const {
SubclassResponsibilityError,
NotYetImplementedError
MissingRequiredParamError,
UnexpectedParamError
} = require('o-toolbox')
SubclassResponsibilityError.raiseOn({ methodName: 'someMethod', className: 'SomeClass' })
NotYetImplementedError.raiseOn({ methodName: 'someMethod', className: 'SomeClass' })
MissingRequiredParamError.raiseIfMissing({ param: n, paramName: 'n' })
MissingRequiredParamError.raiseOn({ paramName: 'n' })
UnexpectedParamError.raiseOn({ paramName: 'n' })
ImportMethods
ImportMethods utility is a simplified implementation of Mixins.
It lacks of inheritance with other Mixins applied to the same class and does not keep its own set of instance variables but in many circumstances it still stands as a good enough solution.
To import the methods of a Mixin class into a concrete class do
// A mixin to add an .equals(other) method to a class
class ComparableValueBehaviour {
equals (other) {
return this.getValue() == other.getValue()
}
}
const {ImportMethods} = require('o-toolbox')
const {ComparableValueBehaviour} = require('./ComparableValueBehaviour')
// A class
class CustomValue {
constructor(value) {
this.value = value
}
getValue() {
return this.value
}
}
// The addition of the mixin into the class
ImportMethods.all({ from: ComparableValueBehaviour, into: CustomValue })
// Now CustomValue instances respond to .equals()
const value1 = new CustomValue(1)
const value2 = new CustomValue(2)
value1.equals(value2)
ImportMethods also supports partial imports with
ImportMethods.all({
from: ComparableValueBehaviour,
into: CustomValue,
except: ['someMethod', 'anotherMethod']
})
ImportMethods.methods({
methodNames: ['someMethod', 'anotherMethod']
from: ComparableValueBehaviour,
into: CustomValue
})
ImportMethods.method({
methodName: 'someMethod',
from: ComparableValueBehaviour,
into: CustomValue
})
If possible it's encouraged to encapsulate the use of ImportMethods in a static method in the mixin to simplify its usage and avoid an additional require
statement
const {ImportMethods} = require('o-toolbox')
// A mixin to add an .equals(other) method
class ComparableValueBehaviour {
static attachTo(aClass) {
ImportMethods.all({ from: this, into: aClass })
}
equals (other) {
return this.getValue() == other.getValue()
}
}
const {ComparableValueBehaviour} = require('./ComparableValueBehaviour')
// A class
class CustomValue {
constructor(value) {
this.value = value
}
getValue() {
return this.value
}
}
ComparableValueBehaviour.attachTo(CustomValue)
// Now CustomValue instances respond to .equals()
const value1 = new CustomValue(1)
const value2 = new CustomValue(2)
value1.equals(value2)
NoOverridesImportMethods
NoOverridesImportMethods
behaves like ImportMethods
but if a method is defined in the target class it does not override it.
If the mixin provides default implementation of methods overriden in the target classes you should use NoOverridesImportMethods.all
or ImportMethods.all({ except: })
.
Protocol
Protocol
utility validates that a class defines all the methods in a given protocol.
To use it define a protocol to validate
const {Protocol} = require('o-toolbox')
class ComparableValueProtocol extends Protocol {
equals(other) {}
above(other) {}
below(other) {}
}
and with it validate that a class implements all its methods
ComparableValueProtocol.validate(SomeClass)
If the validation fails it raises an error.
If you prefer to ask for a boolean instead of raising an error use
ComparableValueProtocol.isFulfilledBy(SomeClass)
Typically you would validate the class right after its definition
const ComparableValueProtocol = require('./ComparableValueProtocol')
class SomeClass {
// ...
}
ComparableValueProtocol.validate(SomeClass)
Classes usually have 2 protocols. An implementation protocol stating the methods a subclass must implement and a public protocol stating the methods a user of the class can call.
The recomendation is to define and validate each protocol independently with its own Protocol class.
Random
Utility to generate random numbers
Important
This random number generator is not a good random generator and it's not suitable for statistical calculations, nor to use it in cryptographics
For not critical uses, like graphical animations and games, it's good enough
Generate a random number with any of the following methods:
const { Random } = require('o-toolbox')
const randomFloat = Random.numberBetween( 1, 10 ) // 1 and 10 are included
const randomInteger = Random.integerBetween( 1, 10 ) // 1 and 10 are included
const randomItem = Random.pickFrom( [ 'a', 'b', 'c' ] )
DoMe commands
DoMe commands are intended to be self-documented, please take a look at the files in DoMe/forDevelopment/inWindows
, or in DoMe/forDevelopment/inDocker