7.7.0 • Published 3 years ago

o-toolbox v7.7.0

Weekly downloads
19
License
ISC
Repository
bitbucket
Last release
3 years ago

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.

Paths 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

7.7.0

3 years ago

7.6.0

3 years ago

7.5.0

3 years ago

7.4.0

3 years ago

7.3.0

3 years ago

7.2.0

3 years ago

7.1.0

3 years ago

7.0.0

3 years ago

6.3.2

3 years ago

6.3.1

3 years ago

6.3.0

3 years ago

6.1.0

3 years ago

6.2.1

3 years ago

6.2.0

3 years ago

6.0.0

3 years ago

5.0.1

3 years ago

5.0.0

3 years ago

4.0.0

3 years ago

3.0.0

4 years ago

2.1.0

4 years ago

2.0.0

4 years ago

1.4.0

4 years ago

1.3.0

4 years ago

1.2.0

4 years ago

1.1.0

4 years ago

1.0.0

4 years ago