index.js

  1. const { mapObject } = require('underscore');
  2. const { at_maybe, cloneImpl, isLensClass } = require('./src-cjs/constants');
  3. const Errors = require('./cjs/errors');
  4. const fusion = require('./cjs/fusion').default;
  5. const Lens = require('./cjs/lens').default;
  6. const { eachFound, maybeDo } = require('./cjs/utils');
  7. let fuse = null;
  8. /**
  9. * @module natural-lenses
  10. * @summary Construct a Lens from the given indexing steps
  11. *
  12. * @param {...*} key A name or index to use in successive subscripting (i.e. square bracket) operations
  13. * @returns {Lens} The constructed lens
  14. *
  15. * @property {symbol} at_maybe Key for method implementing retrieval from a container
  16. * @property {symbol} clone Key for method implementing cloning of a container with modifications
  17. * @property {Function} eachFound [Documentation]{@link module:natural-lenses#eachFound}
  18. * @property {Function} Factory [Class]{@link Factory} for customized lens creation
  19. * @property {Function} fuse [Documentation]{@link module:natural-lenses#fuse}
  20. * @property {symbol} isLens Key for testing objects for "lens-ness"
  21. * @property {Function} JsContainerFactory [Class]{@link JsContainerFactory} for customized container creation
  22. * @property {Object} jsContainers {@link JsContainerFactory} for standard JavaScript containers (Map and Array)
  23. * @property {Function} maybeDo [Documentation]{@link module:natural-lenses#maybeDo}
  24. * @property {Function} nfocal [Construct]{@link module:natural-lenses#nfocal} a multifocal lens
  25. * @property {Function} polyfillImmutable [Documentation]{@link module:natural-lenses#polyfillImmutable}
  26. * @property {Function} Step [Class]{@link Step} for customized Lens steps
  27. *
  28. * @description
  29. * This module is (when `require`d) or exports as default (when `import`ed) a
  30. * Function accepting an arbitrary number of keys and returning a {@link Lens}.
  31. *
  32. * A Lens is a way to safely apply the indexing (i.e. square-bracket) operator
  33. * repeatedly, and to clone-with-changes a complex value to assist programming
  34. * in an immutable-data style.
  35. *
  36. * In addition to the properties enumerated here, all error classes are also
  37. * exposed as properties or named exports.
  38. */
  39. function makeLens(...keys) {
  40. return new Lens(...keys);
  41. }
  42. Object.defineProperties(makeLens, {
  43. at_maybe: {enumerable: true, value: at_maybe},
  44. clone: {enumerable: true, value: cloneImpl},
  45. eachFound: {enumerable: true, value: eachFound},
  46. isLens: {enumerable: true, value: isLensClass},
  47. maybeDo: {enumerable: true, value: maybeDo},
  48. ...mapObject(Errors, (cls) => ({enumerable: true, value: cls})),
  49. /**
  50. * @function module:natural-lenses#fuse
  51. * @summary Fuse multiple optics into a single, sequential application
  52. * @param {...Optic} optic Optic object to fuse
  53. * @returns {Lens|OpticArray} A single {@link Optic} joining the *optics*
  54. *
  55. * @description
  56. * To understand the slot reference of the returned optic, consider the
  57. * *getting* model: `optics[0]` will be applied to the input data, and
  58. * for all other optics (i > 0), `optics[i]` will be applied to the
  59. * result from `optics[i - 1]`. In other words, each optic *gets* something
  60. * within the optic to its left, with the leftmost *getting* from the
  61. * input data. Modifications affect the same slot.
  62. *
  63. * If all *optics* are [Lenses]{@link Lens}, the result will be a Lens. This
  64. * does not apply for Lens-derived objects (e.g. from [Factories]{@link Factory}) —
  65. * if such are passed, the result will always be an OpticArray.
  66. */
  67. fuse: {enumerable: true, get: () => {
  68. const OpticArray = require('./cjs/optic_array.js').default;
  69. fuse = fuse || fusion({ Lens, OpticArray });
  70. return fuse;
  71. }},
  72. /**
  73. * @function module:natural-lenses#nfocal
  74. * @summary Construct a multifocal lens
  75. * @param {Array.<Optic> | Object.<string,Optic>} lenses Collection of [Lenses]{@link Lens} to combine
  76. * @returns {ArrayNFocal|ObjectNFocal}
  77. *
  78. * @description
  79. * Where a standard lens looks at a single *slot* within a JSONic object, a
  80. * multifocal lens consists of multiple lenses (standard or multifocal) whose
  81. * results are aggregated into a single value, which can be a dictionary-like
  82. * Object or an Array.
  83. *
  84. * Pass an Array of lenses to create a multifocal lens that outputs an Array,
  85. * where the position of each lens in the input Array corresponds to the
  86. * output position of the data (when *getting* with the multifocal lens). When
  87. * *getting* in a Maybe context (`#get_maybe`), the result will always be an
  88. * Object with a `just` property, though some elements of the Array may be
  89. * empty.
  90. *
  91. * Pass an Object with lens values to create a multifocal lens outputting an
  92. * Object, which will bear the same properties as the input Object and whose
  93. * values are the values in the data referenced by the corresponding lens (when
  94. * *getting* using this multifocal lens).
  95. *
  96. * Be aware that the `xformInClone` method on these kinds of multifocal lenses
  97. * works differently from a basic Lens, since multifocals can have a
  98. * "stereoscopic" view of data. Instead of a single transformation function,
  99. * `xformInClone` (and it's `_maybe` variant) accept an iterable of key/transform
  100. * pairs (where the key is an integer index in the case of an Array multifocal).
  101. * This allows full control over the order in which transformations are applied
  102. * to the input data, resolving the issue of "stereoscopic conflict".
  103. */
  104. nfocal: {enumerable: true, get: () => (lenses) => {
  105. return require('./cjs/nfocal').makeNFocal(lenses);
  106. }},
  107. Factory: {enumerable: true, get: () => require('./cjs/lens_factory').default},
  108. JsContainerFactory: {enumerable: true, get: () => require('./cjs/js_container_factory').default},
  109. jsContainers: {enumerable: true, get: () => require('./cjs/js_container_factory').DEFAULT_FACTORY},
  110. polyfillImmutable: {enumerable: true, get: () => require('./cjs/immutable_support').polyfillImmutable},
  111. Step: {enumerable: true, get: () => require('./cjs/custom_step').default},
  112. });
  113. /**
  114. * @constant
  115. * @name module:natural-lenses#at_maybe
  116. * @type {symbol}
  117. *
  118. * @description
  119. * This constant is a key used for querying a method from container objects of
  120. * type `function(*): {@link Maybe}.<*>`. The value passed to this method will be
  121. * a *key* from a Lens -- the kind of value that should be passed for a
  122. * conceptual "indexing" of the container. For Object, this is a string property
  123. * name. For Array, this is an integer index, where negative values count
  124. * backward from the end of the Array. For Map, this uses `Map.prototype.has`
  125. * and `Map.prototype.get`.
  126. */
  127. /**
  128. * @constant
  129. * @name module:natural-lenses#clone
  130. * @type {symbol}
  131. *
  132. * @description
  133. * This constant is a key used for querying a method from container objects of
  134. * type `function({set: {0: *, 1: *}?, spliceOut: *?}): *`. The intent of the
  135. * method is to clone the container with some kind of alteration -- either
  136. * a key/index set to the given value in the clone, or a key/index deleted
  137. * from the clone.
  138. *
  139. * If the operation description passed contains a `set` property,
  140. * the value of that property should be an Array where element 0 is a key or
  141. * index into the container and element 1 is the value to set (cf. arguments
  142. * to `Map.prototype.set`).
  143. *
  144. * If the operation description passed contains a `spliceOut` property,
  145. * the value of that property should be a key or index to delete from the
  146. * container. Where possible, the result should be to leave the container
  147. * in a state where `container[at_maybe](key)` returns `{}` (a *Nothing*).
  148. * This is specifically a problem for [immutable.List]{@link external:immutable.List},
  149. * which offers only a dense presentation of elements: every non-negative index
  150. * less than `size` is a valid and "contained" entry. The implementation provided
  151. * by this library for implementing this method on
  152. * [immutable.List]{@link external:immutable.List} sets the value of the entry
  153. * in the clone to `undefined`.
  154. *
  155. * In the provided implemenation for Array, negative indexes are interpreted
  156. * counting backward from the end of the Array, as with `Array.prototype.slice`.
  157. *
  158. * `Symbol.species` is honored for determining the constructor used for the
  159. * clone; Object is a special case that defaults to Object if `Symbol.species`
  160. * is not present.
  161. */
  162. /**
  163. * @constant
  164. * @name module:natural-lenses#isLens
  165. * @type {symbol}
  166. *
  167. * @description
  168. * This property is set on every kind of object to be recognized by this
  169. * library as implementing lens-like behavior. Setting this property to
  170. * any truthy value on your own objects will cause this library to treat it
  171. * in many ways like a {@link Lens}.
  172. */
  173. module.exports = makeLens;