better-lock 1.0.0 → 2.0.1-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/.eslintrc.js +24 -0
  2. package/.idea/better-lock.iml +1 -7
  3. package/.idea/codeStyles/Project.xml +336 -5
  4. package/.idea/compiler.xml +7 -0
  5. package/.idea/inspectionProfiles/Project_Default.xml +6 -2
  6. package/.idea/jsLibraryMappings.xml +0 -1
  7. package/.idea/modules.xml +1 -1
  8. package/.idea/prettier.xml +8 -0
  9. package/.prettierrc.yaml +1 -3
  10. package/.travis.yml +2 -2
  11. package/README.md +102 -53
  12. package/dist/better_lock.d.ts +50 -0
  13. package/dist/better_lock.js +317 -0
  14. package/dist/better_lock.js.map +1 -0
  15. package/dist/errors.d.ts +44 -0
  16. package/dist/errors.js +131 -0
  17. package/dist/errors.js.map +1 -0
  18. package/dist/index.d.ts +16 -0
  19. package/dist/index.js +28 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/internals.d.ts +75 -0
  22. package/dist/internals.js +75 -0
  23. package/dist/internals.js.map +1 -0
  24. package/dist/options.d.ts +54 -0
  25. package/dist/options.js +3 -0
  26. package/dist/options.js.map +1 -0
  27. package/dist/tools.d.ts +19 -0
  28. package/dist/tools.js +109 -0
  29. package/dist/tools.js.map +1 -0
  30. package/dist/types.d.ts +23 -0
  31. package/dist/types.js +3 -0
  32. package/dist/types.js.map +1 -0
  33. package/jest.config.js +4 -0
  34. package/package.json +30 -8
  35. package/spec/better_lock.spec.ts +614 -0
  36. package/src/better_lock.ts +430 -0
  37. package/src/errors.ts +124 -0
  38. package/src/index.ts +54 -0
  39. package/src/internals.ts +118 -0
  40. package/src/options.ts +63 -0
  41. package/src/tools.ts +111 -0
  42. package/src/types.ts +29 -0
  43. package/tsconfig.json +13 -0
  44. package/.idea/markdown-navigator/profiles_settings.xml +0 -3
  45. package/.idea/markdown-navigator.xml +0 -78
  46. package/.idea/misc.xml +0 -6
  47. package/.idea/watcherTasks.xml +0 -8
  48. package/index.js +0 -19
  49. package/spec/better_lock.spec.js +0 -583
  50. package/src/better_lock.js +0 -400
  51. package/src/errors.js +0 -81
  52. package/src/options.js +0 -104
  53. package/src/tools.js +0 -125
  54. package/src/types.js +0 -110
@@ -1,400 +0,0 @@
1
- const tools = require('./tools');
2
-
3
- const { LockJob, KeyQueue } = require('./types');
4
- const {
5
- BetterLockInternalError,
6
- InvalidArgumentError,
7
- WaitTimeoutError,
8
- ExecutionTimeoutError,
9
- QueueOverflowError,
10
- JobAbortedError,
11
- } = require('./errors');
12
- const { BetterLockOptions, LockJobOptions, DEFAULT_OPTIONS } = require('./options');
13
-
14
- /**
15
- * Create a better lock object.
16
- * @constructor
17
- * @param {BetterLockOptions} options
18
- */
19
- function BetterLock(options) {
20
- options = new BetterLockOptions([BetterLock.DEFAULT_OPTIONS, options]);
21
-
22
- const log = tools.makeLog(options.name, options.log);
23
-
24
- /** @type {Object.<string, KeyQueue>} */
25
- const _queues = {};
26
-
27
- Object.assign(
28
- this,
29
- /** @lends {BetterLock.prototype} */ {
30
- canAcquire,
31
- acquire,
32
- abort,
33
- abortAll,
34
- }
35
- );
36
-
37
- // *******************************************************************************************************************
38
-
39
- /**
40
- * Return or create a key queue for given key
41
- * @return {KeyQueue}
42
- */
43
- function getQueue(key) {
44
- if (key === undefined || key === null) {
45
- key = KeyQueue.DEFAULT_QUEUE_KEY;
46
- }
47
- let queue = _queues[key];
48
- if (!queue) {
49
- queue = _queues[key] = new KeyQueue(key);
50
- }
51
- return queue;
52
- }
53
-
54
- /**
55
- * Returns true if the caller can *immediately* acquire the lock. There is nothing holding the key and nothing in the queue.
56
- * @param key
57
- */
58
- function canAcquire(key) {
59
- const queue = getQueue(key);
60
- return !queue.active && !queue.jobs.length;
61
- }
62
-
63
- function normalizeAndValidateKey(key) {
64
- if (key === undefined) {
65
- return undefined;
66
- }
67
- if (key === null) {
68
- return undefined;
69
- }
70
- if (tools.isString(key)) {
71
- return key;
72
- }
73
- if (tools.isNumber(key)) {
74
- return String(key);
75
- }
76
- // Invalid key value
77
- throw new InvalidArgumentError(
78
- options.name,
79
- 'key',
80
- 'a string, number, undefined, null, or an array of these types',
81
- key
82
- );
83
- }
84
-
85
- /**
86
- * Acquire the lock for given key or list of keys. If waiting on a list, keys will be acquired in order,
87
- * as they become free. Any deadlock prevention must be handled by the caller.
88
- *
89
- * Once the lock is acquired, we will call the executor function, with a "done" method.
90
- * You must call "done" to release the lock. Alternatively, return a Promise; lock will be released
91
- * once promise resolves or rejects.
92
- *
93
- * Return values from your executor will be passed to callback (or the resulting promise).
94
- * @template T
95
- * @param {string|Number|Array|undefined} [key] Named key or array of keys for this particular call. Calls with different keys will be run in parallel. Not required.
96
- * @param {function(done):Promise<T>} executor Function that will run inside the lock. Required.
97
- * @param {function} [callback] Function to be called after the executor finishes or if we never enter the lock (timeout, queue depletion). Leave out to use promises.
98
- * @param {LockJobOptions} [jobOptions] Options to be applied on this job only
99
- * @return {Promise<T>}
100
- */
101
- function acquire(key, executor, callback = undefined, jobOptions = undefined) {
102
- if (tools.isFunction(key) || tools.isObject(key)) {
103
- // Presume we weren't given a key
104
- jobOptions = callback;
105
- callback = executor;
106
- executor = key;
107
- key = undefined;
108
- }
109
-
110
- // Repackage "key" into an array of keys
111
- const keys = [];
112
- if (tools.isArray(key)) {
113
- // Make sure we don't add duplicates
114
- const seenKeys = {};
115
- for (let i = 0; i < key.length; i++) {
116
- const normalizedKey = normalizeAndValidateKey(key[i]);
117
- if (!seenKeys[normalizedKey]) {
118
- keys.push(normalizedKey);
119
- seenKeys[normalizedKey] = true;
120
- }
121
- }
122
- } else {
123
- keys.push(normalizeAndValidateKey(key));
124
- }
125
-
126
- // Create callback wrapper for promise interface
127
- if (!tools.isFunction(callback)) {
128
- if (jobOptions === undefined) {
129
- jobOptions = callback;
130
- }
131
-
132
- callback = tools.callbackWithPromise();
133
- }
134
-
135
- // Validate other options
136
- if (!tools.isFunction(executor)) {
137
- throw new InvalidArgumentError(options.name, 'executor', 'a function', executor);
138
- }
139
- if (!tools.isFunction(callback)) {
140
- throw new InvalidArgumentError(options.name, 'callback', 'a function', callback);
141
- }
142
-
143
- // Prepare job options
144
- const effectiveJobOptions = jobOptions
145
- ? new LockJobOptions([options, jobOptions])
146
- : // No need to create new object since these will be read-only and are subset of global options
147
- options;
148
-
149
- const job = new LockJob(keys, executor, callback, effectiveJobOptions);
150
-
151
- // Set incoming stack
152
- if (effectiveJobOptions.extend_stack_traces) {
153
- const tempErr = new Error();
154
- Error.captureStackTrace(tempErr, acquire);
155
- job.incoming_stack = tempErr.stack;
156
- }
157
-
158
- // Add job to its key queues
159
- const queuesToUpdate = [];
160
- for (let i = 0; i < keys.length; i++) {
161
- const queue = getQueue(keys[i]);
162
- queue.jobs.push(job);
163
- queuesToUpdate.push(queue);
164
- }
165
-
166
- // Start wait timer, if enabled
167
- if (tools.isNumber(job.options.wait_timeout)) {
168
- job.wait_timeout_id = setTimeout(onWaitTimeout.bind(null, job), job.options.wait_timeout);
169
-
170
- // NOTE: timeout of 0 will NOT trigger error if lock can be acquired immediately.
171
- }
172
-
173
- log(`Enqueued ${job}`);
174
-
175
- // Perform update of queues this job has modified. We don't do this in next tick or something,
176
- // because we want canAcquire to immediately return false
177
- update(queuesToUpdate);
178
-
179
- return callback.promise;
180
- }
181
-
182
- /**
183
- * Update state of given list of queues.
184
- * @param {KeyQueue[]} queues
185
- */
186
- function update(queues) {
187
- for (let i = 0; i < queues.length; i++) {
188
- const queue = queues[i];
189
-
190
- // If key is available, have next job grab it
191
- if (!queue.active && queue.jobs.length) {
192
- /** @type {LockJob} */
193
- const job = queue.jobs.splice(0, 1)[0];
194
- queue.active = job;
195
- job.waiting_count--;
196
-
197
- if (job.waiting_count <= 0) {
198
- // This job can be executed now.
199
- executeJob(job);
200
- }
201
- }
202
-
203
- // Handle wait queue overflow
204
- if (tools.isNumber(options.queue_size) && options.queue_size < queue.jobs.length) {
205
- // Overflow. Reject the most recent job
206
- const mostRecentJob = queue.jobs[queue.jobs.length - 1];
207
- log(
208
- `${queue.toString()} has overflown, so most recent job (${mostRecentJob}) was kicked out`
209
- );
210
-
211
- endJob(mostRecentJob, [
212
- new QueueOverflowError(options.name, queue.key, queue.jobs.length, mostRecentJob),
213
- ]);
214
- }
215
- }
216
- }
217
-
218
- /**
219
- * @param {LockJob} job
220
- */
221
- function executeJob(job) {
222
- log(`Executing ${job}`);
223
-
224
- // Clear wait timeout
225
- clearTimeout(job.wait_timeout_id);
226
- job.wait_timeout_id = null;
227
-
228
- // We want to do the rest of this in a separate context, because we don't want user executor code
229
- // to ever interfere with the code calling acquire().
230
-
231
- setImmediate(() => {
232
- // Make sure job hasn't been ended in the meantime
233
- if (job.executed_at || job.ended_at) {
234
- return;
235
- }
236
-
237
- // Validate this job is holding all queues it needs
238
- for (let i = 0; i < job.keys.length; i++) {
239
- const queue = getQueue(job.keys[i]);
240
- if (queue.active !== job) {
241
- // This should never happen
242
- return endJob(job, [
243
- new BetterLockInternalError(
244
- options.name,
245
- `Corrupted wait queue state for ${queue.key}`
246
- ),
247
- ]);
248
- }
249
- }
250
-
251
- // Mark job as having started executing
252
- job.executed_at = new Date();
253
-
254
- if (tools.isNumber(job.options.execution_timeout)) {
255
- job.execution_timeout_id = setTimeout(
256
- onExecutionTimeout.bind(null, job),
257
- job.options.execution_timeout
258
- );
259
- }
260
-
261
- if (job.executor.length > 0) {
262
- // Callback interface
263
- job.executor(lockDone);
264
- return;
265
- }
266
-
267
- // Promise interface
268
- let executorResult;
269
- try {
270
- executorResult = job.executor();
271
- } catch (err) {
272
- // Error thrown directly from executor
273
- lockDone(err);
274
- }
275
-
276
- // Promise sniffing
277
- // TODO if (options.promise_tester(promise)) {
278
- if (executorResult && executorResult.then && executorResult.catch) {
279
- executorResult.then(res => lockDone(null, res), err => lockDone(err));
280
- } else {
281
- // "promise" is just some random value. We don't have to wait
282
- lockDone(null, executorResult);
283
- }
284
-
285
- function lockDone() {
286
- log(`Done called for ${job}`);
287
-
288
- endJob(job, arguments);
289
- }
290
- });
291
- }
292
-
293
- /**
294
- * @param {LockJob} job
295
- */
296
- function onWaitTimeout(job) {
297
- log(`${job} has timed out after waiting in queue for ${new Date() - job.enqueued_at}ms`);
298
-
299
- endJob(job, [new WaitTimeoutError(options.name, job)]);
300
- }
301
-
302
- /**
303
- * @param {LockJob} job
304
- */
305
- function onExecutionTimeout(job) {
306
- log(`${job} has timed out after executing for ${new Date() - job.executed_at}ms`);
307
-
308
- endJob(job, [new ExecutionTimeoutError(options.name, job)]);
309
- }
310
-
311
- /**
312
- * @param {LockJob} job
313
- * @param {*[]|Array|IArguments} callbackArgs
314
- */
315
- function endJob(job, callbackArgs) {
316
- if (job.ended_at) {
317
- log(
318
- `WARNING: ${job} is trying to end, but it has already ended at ${job.ended_at.toISOString()}. Called with arguments: ${callbackArgs}`
319
- );
320
- return;
321
- }
322
-
323
- clearTimeout(job.wait_timeout_id);
324
- job.wait_timeout_id = null;
325
-
326
- clearTimeout(job.execution_timeout_id);
327
- job.execution_timeout_id = null;
328
-
329
- job.ended_at = new Date();
330
-
331
- const queuesToUpdate = [];
332
- for (let i = 0; i < job.keys.length; i++) {
333
- const queue = getQueue(job.keys[i]);
334
- queuesToUpdate.push(queue);
335
-
336
- if (queue.active === job) {
337
- // This job was holding on the queue's key
338
- queue.active = null;
339
- } else {
340
- // This job was still in wait list for this queue
341
- const index = queue.jobs.indexOf(job);
342
- if (index >= 0) {
343
- // Remove from queue
344
- queue.jobs.splice(index, 1);
345
- } else {
346
- // Something is wrong. Where did this job come from?
347
- log(
348
- `WARNING: ${job} is ending, but it is not found anywhere in the queue for ${queue.key}`
349
- );
350
- }
351
- }
352
- }
353
-
354
- // Call the callback
355
- try {
356
- job.callback.apply(null, callbackArgs);
357
- } finally {
358
- // Whatever happens, schedule a queue update
359
- setImmediate(update, queuesToUpdate);
360
- }
361
- }
362
-
363
- /**
364
- * Abort all jobs for a given key (or from the default job queue, if no key is given).
365
- * Job executors will not be called. Callbacks will be called with JobAbortedError.
366
- * Currently executing job will not be interrupted.
367
- * @param [key]
368
- */
369
- function abort(key = undefined) {
370
- const queue = getQueue(key);
371
- queue.jobs.slice().forEach(job => {
372
- endJob(job, [new JobAbortedError(options.name, job)]);
373
- });
374
- if (queue.active && !queue.active.executed_at) {
375
- // If we have a job holding this queue that hasn't been executed yet, abort it as well
376
- endJob(queue.active, [new JobAbortedError(options.name, queue.active)]);
377
- }
378
- }
379
-
380
- /**
381
- * Abort all pending jobs from all queues.
382
- * Currently executing jobs will not be interrupted.
383
- */
384
- function abortAll() {
385
- abort(undefined);
386
- for (const key in _queues) {
387
- // noinspection JSUnfilteredForInLoop
388
- abort(key);
389
- }
390
- }
391
- }
392
-
393
- /**
394
- * Default options to be used when creating BetterLock instances.
395
- */
396
- BetterLock.DEFAULT_OPTIONS = DEFAULT_OPTIONS;
397
-
398
- module.exports = {
399
- BetterLock,
400
- };
package/src/errors.js DELETED
@@ -1,81 +0,0 @@
1
- class BetterLockError extends Error {
2
- constructor(name, message, incomingStack = null) {
3
- if (name) {
4
- super(`[${name}] ${message}`);
5
- } else {
6
- super(message);
7
- }
8
- this.lock_name = name;
9
-
10
- if (incomingStack) {
11
- const withoutFirstLine = incomingStack
12
- .split('\n')
13
- .slice(1)
14
- .join('\n');
15
- this.stack =
16
- this.stack +
17
- '\n --------------------------------------------------------------------------------\n' +
18
- withoutFirstLine;
19
- }
20
- }
21
- }
22
-
23
- class BetterLockInternalError extends BetterLockError {
24
- constructor(name, message) {
25
- const lastChar = message[message.length - 1];
26
- if (lastChar !== '.' && lastChar !== '!' && lastChar !== '?') {
27
- message += '.';
28
- }
29
- super(name, message + ' This is probably a bug inside "better-lock" library');
30
- }
31
- }
32
-
33
- class InvalidArgumentError extends BetterLockError {
34
- constructor(name, argument, expected, actual) {
35
- super(name, `Argument "${argument}" must be ${expected} (got: "${actual}")`);
36
- this.argument = argument;
37
- }
38
- }
39
-
40
- class WaitTimeoutError extends BetterLockError {
41
- constructor(name, job) {
42
- const message = `${job} has timed out after ${new Date() - job.enqueued_at}ms in wait queue`;
43
- super(name, message, job.incoming_stack);
44
- this.job = job;
45
- }
46
- }
47
-
48
- class ExecutionTimeoutError extends BetterLockError {
49
- constructor(name, job) {
50
- const message = `${job} has timed out after ${new Date() - job.executed_at}ms of execution`;
51
- super(name, message, job.incoming_stack);
52
- this.job = job;
53
- }
54
- }
55
-
56
- class QueueOverflowError extends BetterLockError {
57
- constructor(name, key, count, job) {
58
- const message = `Too many jobs (${count}) waiting for ${key}. The most recent job (${job}) was kicked out`;
59
- super(name, message, job.incoming_stack);
60
- this.key = key;
61
- this.count = count;
62
- this.job = job;
63
- }
64
- }
65
-
66
- class JobAbortedError extends BetterLockError {
67
- constructor(name, job) {
68
- super(name, `${job} has been aborted by the user`, job.incoming_stack);
69
- this.job = job;
70
- }
71
- }
72
-
73
- module.exports = {
74
- BetterLockError,
75
- BetterLockInternalError,
76
- InvalidArgumentError,
77
- WaitTimeoutError,
78
- ExecutionTimeoutError,
79
- QueueOverflowError,
80
- JobAbortedError,
81
- };
package/src/options.js DELETED
@@ -1,104 +0,0 @@
1
- const { assign } = require('./tools');
2
-
3
- const ALIASES = {
4
- waitTimeout: 'wait_timeout',
5
- executionTimeout: 'execution_timeout',
6
- queueSize: 'queue_size',
7
- overflowStrategy: 'overflow_strategy',
8
- extendStackTraces: 'extend_stack_traces',
9
- };
10
-
11
- /**
12
- * Options that apply on BetterLock instance
13
- */
14
- class BetterLockOptions {
15
- constructor(/** BetterLockOptions|BetterLockOptions[] */ sources) {
16
- /**
17
- * How long can jobs wait in queue before timing out. Null to disable timeout.
18
- * @type {Number|null}
19
- */
20
- this.wait_timeout = undefined;
21
-
22
- /**
23
- * How long can a job be executing before timing out. Null to disable timeout.
24
- * If you do that, though, and you have a swallowed callback, the lock can remain locked permanently.
25
- * @type {Number|null}
26
- */
27
- this.execution_timeout = undefined;
28
-
29
- /**
30
- * Max queue size for waiting jobs.
31
- * @type {Number|null}
32
- */
33
- this.queue_size = undefined;
34
-
35
- /**
36
- * Lock name. This will be written in all logs and error messages, to help you distinguish between different locks
37
- * @type {string}
38
- */
39
- this.name = undefined;
40
-
41
- /**
42
- * Whether to log the internal actions. Set to true to use console.log. Alternatively, provide your own function
43
- * @type {boolean|function}
44
- */
45
- this.log = undefined;
46
-
47
- /**
48
- * In any error produced by BetterLock, extend the stack trace to include incoming trace
49
- * @type {boolean}
50
- */
51
- this.extend_stack_traces = undefined;
52
-
53
- /**
54
- * Function that detects whether value returned by executor is a promise.
55
- * This will ducktype-sniff the returned value. Depending on your Promise library of choice,
56
- * you might want to tighten or loosen this bit.
57
- * @type {function(p)}
58
- */
59
- this.promise_tester = p => p && typeof p.then === 'function';
60
-
61
- this.assign(sources);
62
- }
63
-
64
- assign(sources) {
65
- assign(this, sources, ALIASES);
66
- }
67
- }
68
-
69
- /**
70
- * Options that apply on LockJob
71
- */
72
- class LockJobOptions {
73
- constructor(/** LockJobOptions|LockJobOptions[] */ sources) {
74
- /**
75
- * How long can jobs wait in queue before timing out. Overrides BetterLockOptions value.
76
- * @type {Number|null}
77
- */
78
- this.wait_timeout = undefined;
79
-
80
- /**
81
- * How long can a job be executing before timing out. Null to disable timeout. Overrides BetterLockOptions value.
82
- * @type {Number|null}
83
- */
84
- this.execution_timeout = undefined;
85
-
86
- /**
87
- * In any error produced by BetterLock, extend the stack trace to include incoming trace. Overrides BetterLockOptions value.
88
- * @type {boolean}
89
- */
90
- this.extend_stack_traces = undefined;
91
-
92
- this.assign(sources);
93
- }
94
-
95
- assign(sources) {
96
- assign(this, sources, ALIASES);
97
- }
98
- }
99
-
100
- const DEFAULT_OPTIONS = new BetterLockOptions({
101
- extend_stack_traces: true,
102
- });
103
-
104
- module.exports = { BetterLockOptions, LockJobOptions, DEFAULT_OPTIONS };
package/src/tools.js DELETED
@@ -1,125 +0,0 @@
1
- function isString(val) {
2
- return typeof val === 'string';
3
- }
4
-
5
- function isFunction(val) {
6
- return typeof val === 'function';
7
- }
8
-
9
- function isObject(val) {
10
- return typeof val === 'object' && val !== null && !Array.isArray(val);
11
- }
12
-
13
- function isNumber(val, includeInfinity = false, includeNaN = false) {
14
- if (typeof val !== 'number') {
15
- return false;
16
- }
17
- if (!includeInfinity && (val === Number.POSITIVE_INFINITY || val === Number.NEGATIVE_INFINITY)) {
18
- return false;
19
- }
20
- if (!includeNaN && isNaN(val)) {
21
- return false;
22
- }
23
- return true;
24
- }
25
-
26
- function isArray(val) {
27
- return Array.isArray(val);
28
- }
29
-
30
- function noop() {}
31
-
32
- function makeLog(name, doLog) {
33
- if (!doLog) {
34
- return noop;
35
- }
36
-
37
- if (doLog === true) {
38
- doLog = console.log.bind(console);
39
- }
40
-
41
- if (name) {
42
- name = '[' + name + '] ';
43
- }
44
-
45
- return function log(msg) {
46
- if (name) {
47
- doLog(name + msg);
48
- } else {
49
- doLog(msg);
50
- }
51
- };
52
- }
53
-
54
- function callbackWithPromise() {
55
- let callback;
56
- const promise = new Promise((resolve, reject) => {
57
- callback = (err, result) => {
58
- if (err) {
59
- reject(err);
60
- } else {
61
- resolve(result);
62
- }
63
- };
64
- });
65
- callback.promise = promise;
66
- return callback;
67
- }
68
-
69
- /**
70
- * Assign properties from sources to target, skipping over undefined-s
71
- * @param target Target object which will be mutated
72
- * @param {object|Array} sources One or more source objects which will provide properties
73
- * @param {object} aliases Lookup mapping aliased properties from sources to properties on target
74
- */
75
- function assign(target, sources, aliases) {
76
- if (!isArray(sources)) {
77
- sources = [sources];
78
- }
79
-
80
- for (const source of sources) {
81
- if (!isObject(source)) {
82
- // We must have an object to assign
83
- continue;
84
- }
85
-
86
- for (const key in source) {
87
- if (!source.hasOwnProperty(key)) {
88
- continue;
89
- }
90
-
91
- const sourceValue = source[key];
92
- if (sourceValue === undefined) {
93
- // Do not overwrite undefineds
94
- continue;
95
- }
96
-
97
- const targetKey = (aliases && aliases[key]) || key;
98
- const targetValue = target[targetKey];
99
-
100
- if (isObject(sourceValue) && isObject(targetValue)) {
101
- // Merge deep.
102
- const cloneRoot = targetValue.constructor ? new targetValue.constructor() : {};
103
- target[targetKey] = assign(cloneRoot, targetValue, sourceValue);
104
- continue;
105
- }
106
-
107
- // In all other cases, copy by reference
108
- target[targetKey] = sourceValue;
109
- }
110
- }
111
-
112
- return target;
113
- }
114
-
115
- module.exports = {
116
- isString,
117
- isFunction,
118
- isObject,
119
- isNumber,
120
- isArray,
121
- noop,
122
- makeLog,
123
- callbackWithPromise,
124
- assign,
125
- };