util/protect.js

  1. import { isObject } from 'util/lang'
  2. import {
  3. prop,
  4. } from 'path/consts'
  5. const chain = undefined
  6. /**
  7. * Proxy handler to protect object from mutations.
  8. * @memberof util
  9. * @see {@link util.protect} for more information.
  10. * @private
  11. * @since 0.3.0
  12. */
  13. class ProtectHandler {
  14. /**
  15. * Constructor.
  16. * @param {Object} chainWrapperRef The reference to the underlying ChainWrapper.
  17. * @param {Array} [path] The path of the proxy target.
  18. * @since 0.3.0
  19. */
  20. constructor(chainWrapperRef, path = []) {
  21. this.chainWrapperRef = chainWrapperRef
  22. this.path = path
  23. }
  24. /**
  25. * Handle property access.
  26. * @param {*} target Target of the access.
  27. * @param {string} property Accessed property.
  28. * @return {*} Either the value of the property or a new Proxy.
  29. * @since 0.3.0
  30. */
  31. get(target, property) {
  32. const reference = this._peek()[property]
  33. if (!isObject(reference)) return reference
  34. return new Proxy(reference, new ProtectHandler(this.chainWrapperRef, [...this.path, [prop, property]]))
  35. }
  36. /**
  37. * Handle property assignment.
  38. * @param {*} target Target of the assignment.
  39. * @param {string} property Assigned property.
  40. * @param {*} value Value to assign.
  41. * @return {boolean} True if the property was assigned.
  42. * @since 0.3.0
  43. */
  44. set(target, property, value) {
  45. this.chainWrapperRef.chainWrapper = this.chainWrapperRef.chainWrapper.set([...this.path, [prop, property]], value)
  46. return true
  47. }
  48. /**
  49. * Handle property deletion.
  50. * @param {*} target Target of the deletion.
  51. * @param {string} property Deleted property.
  52. * @return {boolean} True if the property was deleted.
  53. * @since 0.3.0
  54. */
  55. deleteProperty(target, property) {
  56. this.chainWrapperRef.chainWrapper = this.chainWrapperRef.chainWrapper.unset([...this.path, [prop, property]])
  57. return true
  58. }
  59. /**
  60. * Have a peek at target.
  61. * @return {*} Target with modifications applied.
  62. * @since 0.3.0
  63. */
  64. _peek() {
  65. let peeked
  66. this.chainWrapperRef.chainWrapper = this.chainWrapperRef.chainWrapper.peek(_peeked => { peeked = _peeked })
  67. return !this.path.length ? peeked : peeked[this.path]
  68. }
  69. }
  70. /**
  71. * 🚫 This is an experimental feature, do not use.<br />
  72. * Protect a reference, allowing to apply mutations on it, and then retrieve the result of the mutations.
  73. * @param {Object} reference Reference to protect.
  74. * @return {function} Function to be called with a callback accepting the protected reference, returning the result of the mutations performed by the callback.
  75. * @memberof util
  76. * @since 0.3.0
  77. * @private
  78. * @example
  79. * protect({ a: 1, b: { c: 3 } })(protectedRef => {
  80. * protectedRef.a++
  81. * protectedRef.b.d = protectedRef.a * protectedRef.b.c
  82. * delete protectedRef.b.c
  83. * }) // => { a: 2, b: { d: 6 } }
  84. */
  85. const protect = reference => {
  86. if (!isObject(reference)) throw TypeError('Cannot protect a non-object value')
  87. return callback => {
  88. const chainWrapper = chain(reference)
  89. const chainWrapperRef = { chainWrapper }
  90. const proxy = new Proxy(reference, new ProtectHandler(chainWrapperRef))
  91. callback(proxy)
  92. return chainWrapperRef.chainWrapper.value()
  93. }
  94. }
  95. export { protect }