UNPKG

5.24 kBJavaScriptView Raw
1import { isChain } from '../../utils/is'
2import { format } from '../../utils/string'
3import { hasOwnProperty, lazy } from '../../utils/object'
4import { factory } from '../../utils/factory'
5
6const name = 'Chain'
7const dependencies = ['?on', 'math']
8
9export const createChainClass = /* #__PURE__ */ factory(name, dependencies, ({ on, math }) => {
10 /**
11 * @constructor Chain
12 * Wrap any value in a chain, allowing to perform chained operations on
13 * the value.
14 *
15 * All methods available in the math.js library can be called upon the chain,
16 * and then will be evaluated with the value itself as first argument.
17 * The chain can be closed by executing chain.done(), which will return
18 * the final value.
19 *
20 * The Chain has a number of special functions:
21 * - done() Finalize the chained operation and return the
22 * chain's value.
23 * - valueOf() The same as done()
24 * - toString() Returns a string representation of the chain's value.
25 *
26 * @param {*} [value]
27 */
28 function Chain (value) {
29 if (!(this instanceof Chain)) {
30 throw new SyntaxError('Constructor must be called with the new operator')
31 }
32
33 if (isChain(value)) {
34 this.value = value.value
35 } else {
36 this.value = value
37 }
38 }
39
40 /**
41 * Attach type information
42 */
43 Chain.prototype.type = 'Chain'
44 Chain.prototype.isChain = true
45
46 /**
47 * Close the chain. Returns the final value.
48 * Does the same as method valueOf()
49 * @returns {*} value
50 */
51 Chain.prototype.done = function () {
52 return this.value
53 }
54
55 /**
56 * Close the chain. Returns the final value.
57 * Does the same as method done()
58 * @returns {*} value
59 */
60 Chain.prototype.valueOf = function () {
61 return this.value
62 }
63
64 /**
65 * Get a string representation of the value in the chain
66 * @returns {string}
67 */
68 Chain.prototype.toString = function () {
69 return format(this.value)
70 }
71
72 /**
73 * Get a JSON representation of the chain
74 * @returns {Object}
75 */
76 Chain.prototype.toJSON = function () {
77 return {
78 mathjs: 'Chain',
79 value: this.value
80 }
81 }
82
83 /**
84 * Instantiate a Chain from its JSON representation
85 * @param {Object} json An object structured like
86 * `{"mathjs": "Chain", value: ...}`,
87 * where mathjs is optional
88 * @returns {Chain}
89 */
90 Chain.fromJSON = function (json) {
91 return new Chain(json.value)
92 }
93
94 /**
95 * Create a proxy method for the chain
96 * @param {string} name
97 * @param {Function} fn The function to be proxied
98 * If fn is no function, it is silently ignored.
99 * @private
100 */
101 function createProxy (name, fn) {
102 if (typeof fn === 'function') {
103 Chain.prototype[name] = chainify(fn)
104 }
105 }
106
107 /**
108 * Create a proxy method for the chain
109 * @param {string} name
110 * @param {function} resolver The function resolving with the
111 * function to be proxied
112 * @private
113 */
114 function createLazyProxy (name, resolver) {
115 lazy(Chain.prototype, name, function outerResolver () {
116 const fn = resolver()
117 if (typeof fn === 'function') {
118 return chainify(fn)
119 }
120
121 return undefined // if not a function, ignore
122 })
123 }
124
125 /**
126 * Make a function chainable
127 * @param {function} fn
128 * @return {Function} chain function
129 * @private
130 */
131 function chainify (fn) {
132 return function () {
133 const args = [this.value] // `this` will be the context of a Chain instance
134 for (let i = 0; i < arguments.length; i++) {
135 args[i + 1] = arguments[i]
136 }
137
138 return new Chain(fn.apply(fn, args))
139 }
140 }
141
142 /**
143 * Create a proxy for a single method, or an object with multiple methods.
144 * Example usage:
145 *
146 * Chain.createProxy('add', function add (x, y) {...})
147 * Chain.createProxy({
148 * add: function add (x, y) {...},
149 * subtract: function subtract (x, y) {...}
150 * }
151 *
152 * @param {string | Object} arg0 A name (string), or an object with
153 * functions
154 * @param {*} [arg1] A function, when arg0 is a name
155 */
156 Chain.createProxy = function (arg0, arg1) {
157 if (typeof arg0 === 'string') {
158 // createProxy(name, value)
159 createProxy(arg0, arg1)
160 } else {
161 // createProxy(values)
162 for (const name in arg0) {
163 if (hasOwnProperty(arg0, name) && excludedNames[name] === undefined) {
164 createLazyProxy(name, () => arg0[name])
165 }
166 }
167 }
168 }
169
170 const excludedNames = {
171 expression: true,
172 docs: true,
173 type: true,
174 classes: true,
175 json: true,
176 error: true,
177 isChain: true // conflicts with the property isChain of a Chain instance
178 }
179
180 // create proxy for everything that is in math.js
181 Chain.createProxy(math)
182
183 // register on the import event, automatically add a proxy for every imported function.
184 if (on) {
185 on('import', function (name, resolver, path) {
186 if (!path) {
187 // an imported function (not a data type or something special)
188 createLazyProxy(name, resolver)
189 }
190 })
191 }
192
193 return Chain
194}, { isClass: true })