seq/ChainWrapper.js

  1. import * as array from 'array'
  2. import * as collection from 'collection'
  3. import * as lang from 'lang'
  4. import * as math from 'math'
  5. import * as object from 'object'
  6. import * as string from 'string'
  7. import concat from 'lodash/concat'
  8. import flow from 'lodash/flow'
  9. import mapValues from 'lodash/mapValues'
  10. import toPath from 'lodash/toPath'
  11. /**
  12. * Wrapper allowing to make sequences of immutadot functions calls on an object.<br/>
  13. * Instances are created by calling {@link seq.chain}.<br/>
  14. * The result of such sequences must be unwrapped with {@link seq.ChainWrapper#value}.
  15. * @memberof seq
  16. * @see {@link seq.chain} for more information.
  17. * @private
  18. * @since 0.1.8
  19. */
  20. class ChainWrapper {
  21. /**
  22. * This constructor should not be called directly.<br/>
  23. * Instances are created by calling {@link seq.chain}.
  24. * @param {Object} wrapped The object to wrap.
  25. * @param {Array|string} [path] The path of the object on which functions are called.
  26. * @param {Array} [pFlow=[]] Current calls flow.
  27. * @see {@link seq.chain} for more information.
  28. * @since 0.1.8
  29. */
  30. constructor(wrapped, path, pFlow = []) {
  31. this._wrapped = wrapped
  32. this._path = path
  33. this._flow = pFlow
  34. this._commited = null
  35. }
  36. /**
  37. * Translates a relative path to an absolute path, using <code>this._path</code> as a base path.
  38. * @param {Array|string} path A relative path.
  39. * @return {Array} Absolute path.
  40. * @since 0.1.11
  41. */
  42. _absolutePath(path) {
  43. return concat(toPath(this._path), toPath(path))
  44. }
  45. /**
  46. * Add a function call to the sequence.
  47. * @param {function} fn The function to call.
  48. * @param {Array|string} path The path of the property to be set.
  49. * @param {...*} args The arguments for the function call.
  50. * @returns {seq.ChainWrapper} The new wrapper instance.
  51. * @since 0.1.8
  52. */
  53. _call(fn, path, args) {
  54. return new ChainWrapper(
  55. this._wrapped,
  56. this._path,
  57. concat(this._flow, object => fn(object, this._absolutePath(path), ...args)),
  58. )
  59. }
  60. /**
  61. * Executes the chain sequence.
  62. * @returns {seq.ChainWrapper} The new wrapper instance.
  63. * @since 0.3.0
  64. */
  65. commit() {
  66. if (this._flow.length === 0) return this
  67. if (this._commited === null)
  68. this._commited = new ChainWrapper(
  69. flow(this._flow)(this._wrapped),
  70. this._path,
  71. )
  72. return this._commited
  73. }
  74. /**
  75. * Executes the chain sequence and calls <code>callback</code> with the unwrapped object.
  76. * @param {seq.peekCallback} callback Function to be called with the resolved unwrapped value.
  77. * @returns {seq.ChainWrapper} The new wrapper instance.
  78. * @todo Add an example.
  79. * @since 0.3.0
  80. */
  81. peek(callback) {
  82. const commited = this.commit()
  83. callback(commited._wrapped)
  84. return commited
  85. }
  86. /**
  87. * Executes the chain sequence and returns the unwrapped object.
  88. * @returns {Object} Returns the resolved unwrapped object.
  89. * @example
  90. * chain({ nested1: { prop: 'old' }, nested2: { prop: 1 } })
  91. * .set('nested1.prop', 'new')
  92. * .unset('nested2.prop')
  93. * .value() // => { nested1: { prop: 'new' }, nested2: {} }
  94. * @see {@link seq.chain|chain} for more information.
  95. * @since 0.1.8
  96. */
  97. value() {
  98. return this.commit()._wrapped
  99. }
  100. }
  101. /**
  102. * Function to be called by {@link seq.peek|peek} with the resolved unwrapped value.
  103. * @memberof seq
  104. * @callback peekCallback
  105. * @param {Object} unwrapped The resolved unwrapped object
  106. * @since 0.3.0
  107. */
  108. // Add namespaces functions to the ChainWrapper prototype
  109. [
  110. array,
  111. collection,
  112. lang,
  113. math,
  114. object,
  115. string,
  116. ].forEach(namespace => Object.assign(
  117. ChainWrapper.prototype,
  118. mapValues(
  119. namespace,
  120. fn => function(path, ...args) {
  121. return this._call(fn, path, args) // eslint-disable-line no-invalid-this
  122. },
  123. ),
  124. ))
  125. export { ChainWrapper, ChainWrapper as default }