@creejs/commons-lang 1.0.1

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.
package/README.md ADDED
@@ -0,0 +1,2 @@
1
+ # commons-lang
2
+ Commons Utils About Language
package/index.js ADDED
@@ -0,0 +1,2 @@
1
+ 'use strict'
2
+ module.exports = require('./lib')
@@ -0,0 +1,58 @@
1
+ 'use strict'
2
+ /**
3
+ * @module ExecUtils
4
+ * @description Utils about how to execute task functions.
5
+ */
6
+
7
+ // @ts-ignore
8
+ const { assertFunction, assertArray } = require('./type-assert')
9
+ const { isPromise } = require('./type-utils')
10
+
11
+ /**
12
+ * Executes a task silently, suppressing any errors or rejections.
13
+ * @param {Function} task - The function to execute.
14
+ * @returns {Promise<*>|*} The return value of the task, or a Promise if the task is asynchronous.
15
+ */
16
+ function quiet (task) {
17
+ assertFunction(task)
18
+ try {
19
+ const rtnVal = task()
20
+ if (isPromise(rtnVal)) {
21
+ return rtnVal.catch(() => {
22
+ // do nothing
23
+ })
24
+ }
25
+ return rtnVal
26
+ } catch (e) {
27
+ // do nothing
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Executes a task quietly, capturing any errors and passing them to the errorKeeper.
33
+ * 1. Handles both synchronous and asynchronous (Promise) tasks.
34
+ * @param {Function} task - The function to execute.
35
+ * @param {Error[]} errorKeeper - The array to store any caught errors.
36
+ * @returns {Promise<*>|*} The return value of the task, or a Promise if the task is asynchronous.
37
+ */
38
+ function quietKeepError (task, errorKeeper) {
39
+ assertFunction(task)
40
+ assertArray(errorKeeper)
41
+ try {
42
+ const rtnVal = task()
43
+ if (isPromise(rtnVal)) {
44
+ // @ts-ignore
45
+ return rtnVal.catch(e => errorKeeper.push(e))
46
+ }
47
+ return rtnVal
48
+ } catch (e) {
49
+ // @ts-ignore
50
+ errorKeeper.push(e)
51
+ }
52
+ }
53
+ const ExecUtils = {
54
+ quiet,
55
+ quietKeepError
56
+ }
57
+
58
+ module.exports = ExecUtils
package/lib/index.js ADDED
@@ -0,0 +1,26 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * @module Lang
5
+ * @description Core language utilities for type checking, string manipulation, and common operations.
6
+ */
7
+
8
+ const LangUtils = require('./lang-utils')
9
+ const StringUtils = require('./string-utils')
10
+ const TypeUtils = require('./type-utils')
11
+ const TypeAssert = require('./type-assert')
12
+ const ExecUtils = require('./exec-utils')
13
+ const PromiseUtils = require('./promise-utils')
14
+
15
+ module.exports = {
16
+ LangUtils,
17
+ StringUtils,
18
+ TypeUtils,
19
+ TypeAssert,
20
+ ExecUtils,
21
+ PromiseUtils,
22
+ Lang: LangUtils,
23
+ String: StringUtils,
24
+ Type: TypeUtils,
25
+ Exec: ExecUtils
26
+ }
@@ -0,0 +1,110 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * @module LangUtils
5
+ * @description Language utility functions
6
+ */
7
+
8
+ /**
9
+ * Gets the constructor name of a value.
10
+ * @param {*} value - The value to check.
11
+ * @returns {string|undefined} The constructor name, or undefined if value has no constructor.
12
+ */
13
+ function constructorName (value) {
14
+ return value?.constructor?.name
15
+ }
16
+
17
+ /**
18
+ * Assigns default values from source objects to target object for undefined properties.
19
+ * @param {Object<string, any>} target - The target object to assign defaults to
20
+ * @param {...Object<string, any>} sources - Source objects containing default values
21
+ * @returns {Object<string, any>} The modified target object with defaults applied
22
+ * @throws {TypeError} If target is null or undefined
23
+ */
24
+ function defaults (target, ...sources) {
25
+ if (target == null) {
26
+ throw new TypeError('"target" must not be null or undefined')
27
+ }
28
+ for (const source of sources) {
29
+ if (source == null) {
30
+ continue
31
+ }
32
+ for (const key in source) {
33
+ if (target[key] === undefined) {
34
+ target[key] = source[key]
35
+ }
36
+ }
37
+ }
38
+ return target
39
+ }
40
+
41
+ /**
42
+ * Extends a target object with properties from one or more source objects.
43
+ * @param {Object<string, any>} target - The target object to extend (must not be null/undefined)
44
+ * @param {...Object<string, any>} sources - One or more source objects to copy properties from
45
+ * @returns {Object<string, any>} The modified target object
46
+ * @throws {TypeError} If target is null or undefined
47
+ */
48
+ function extend (target, ...sources) {
49
+ if (target == null) {
50
+ throw new TypeError('"target" must not be null or undefined')
51
+ }
52
+ for (const source of sources) {
53
+ if (source == null) {
54
+ continue
55
+ }
56
+ for (const key in source) {
57
+ target[key] = source[key]
58
+ }
59
+ }
60
+ return target
61
+ }
62
+
63
+ /**
64
+ * Compares two values for equality
65
+ * 1. First checks strict equality (===),
66
+ * 2. then checks if either value has an `equals` method and uses it.
67
+ * @param {*} value1 - First value to compare
68
+ * @param {*} value2 - Second value to compare
69
+ * @returns {boolean} True if values are equal, false otherwise
70
+ */
71
+ function equals (value1, value2) {
72
+ if (value1 === value2) {
73
+ return true
74
+ }
75
+ if (typeof value1?.equals === 'function') {
76
+ return value1.equals(value2)
77
+ }
78
+ if (typeof value2?.equals === 'function') {
79
+ return value2.equals(value1)
80
+ }
81
+ return false
82
+ }
83
+
84
+ /**
85
+ * Check if the current environment is a browser
86
+ * @returns {boolean}
87
+ */
88
+ function isBrowser () {
89
+ return typeof window !== 'undefined' && typeof document !== 'undefined'
90
+ }
91
+
92
+ /**
93
+ * Check if the current environment is a nodejs
94
+ * @returns {boolean}
95
+ */
96
+ function isNode () {
97
+ return !isBrowser()
98
+ }
99
+
100
+ const LangUtils = {
101
+ constructorName,
102
+ defaults,
103
+ extend,
104
+ extends: extend,
105
+ equals,
106
+ isBrowser,
107
+ isNode
108
+ }
109
+
110
+ module.exports = LangUtils
@@ -0,0 +1,317 @@
1
+ // @ts-nocheck
2
+ 'use strict'
3
+
4
+ /**
5
+ * @module PromiseUtils
6
+ * @description Promise utility functions for enhanced promise handling, including timeout, delay, parallel execution, and series execution.
7
+ */
8
+
9
+ const { assertNumber, assertPromise, assertArray, assertFunction } = require('./type-assert')
10
+ const { isPromise, isNumber } = require('./type-utils')
11
+ /**
12
+ * Creates a "Deferred" Object with Timeout support
13
+ * 1. timeout=-1, it means no timeout check
14
+ * @param {number} [timeout=-1] - Timeout duration in milliseconds
15
+ * @param {string} [timeoutMessage]
16
+ * @returns {{promise: Promise<*>, reject: function, resolve: function}}
17
+ */
18
+ function defer (timeout = -1, timeoutMessage) {
19
+ assertNumber(timeout)
20
+ const rtnVal = {}
21
+
22
+ let timerHandler
23
+ if (timeout >= 0) {
24
+ rtnVal.timerHandler = timerHandler = setTimeout(() => {
25
+ clearTimeout(timerHandler) // must clear it
26
+ rtnVal.timerCleared = true // easy to check in test case
27
+ rtnVal.reject(new Error(timeoutMessage ?? `Promise Timeout: ${timeout}ms`))
28
+ }, timeout)
29
+ rtnVal.timerHandler = timerHandler
30
+ }
31
+
32
+ rtnVal.promise = new Promise((resolve, reject) => {
33
+ rtnVal.resolve = (...args) => {
34
+ if (timerHandler != null) {
35
+ clearTimeout(timerHandler) // must clear it
36
+ rtnVal.timerCleared = true // easy to check in test case
37
+ }
38
+ rtnVal.resolved = true
39
+ resolve(...args)
40
+ }
41
+
42
+ rtnVal.reject = (err) => {
43
+ if (timerHandler != null) {
44
+ clearTimeout(timerHandler) // must clear it
45
+ rtnVal.timerCleared = true // easy to check in test case
46
+ }
47
+ rtnVal.rejected = true
48
+ reject(err)
49
+ }
50
+ })
51
+ rtnVal.promise.cancel = () => {
52
+ if (timerHandler != null) {
53
+ clearTimeout(timerHandler) // must clear it
54
+ rtnVal.timerCleared = true // easy to check in test case
55
+ }
56
+ rtnVal.rejected = true // easy to check in test case
57
+ rtnVal.canceled = rtnVal.promise.canceled = true // easy to check in test case
58
+ rtnVal.reject(new Error('Cancelled'))
59
+ }
60
+ return rtnVal
61
+ }
62
+
63
+ /**
64
+ * Creates a timeout wrapper around a promise that rejects if the promise doesn't resolve within the given time.
65
+ * @param {Promise<*>} promise - The promise to wrap with a timeout
66
+ * @param {number} [time=1] - Timeout duration in milliseconds
67
+ * @param {string} [message=`Promise Timeout: ${time}ms`] - Custom rejection message
68
+ * @returns {Promise<*>} A new promise that either resolves with the original promise's value, rejects with original promise's error or timeout error
69
+ * @throws {TypeError} If input is not a promise or timeout is not a number
70
+ */
71
+ function timeout (promise, time, message) {
72
+ assertPromise(promise)
73
+
74
+ time = time ?? 1
75
+ assertNumber(time)
76
+
77
+ const deferred = PromiseUtils.defer(time, message)
78
+ const startTs = Date.now()
79
+ promise.then((...args) => { // original promise settled, but timeout
80
+ const elapsed = Date.now() - startTs
81
+ if (elapsed <= time) {
82
+ deferred.resolve(...args)
83
+ } else {
84
+ deferred.reject(new Error(message ?? `Promise Timeout: ${time}ms`))
85
+ }
86
+ }).catch((err) => {
87
+ // prevent double reject
88
+ !deferred.resolved && !deferred.rejected && deferred.reject(err)
89
+ })
90
+ return deferred.promise
91
+ }
92
+
93
+ /**
94
+ * Excutes All promises in parallel and returns an array of results.
95
+ *
96
+ * Why:
97
+ * 1. Promise.allSettled() returns
98
+ * * { status: 'fulfilled', value: any } when promise fulfills
99
+ * * { status: 'rejected', reason: any } when promise rejects
100
+ * 2. It's NOT convenient to use Promise.allSettled() to get the results of all promises.
101
+ * * the data structure is not consistent when fullfilled or rejected
102
+ * * have to check "string" type of status to know sucess or failure
103
+ * @param {Promise} promises
104
+ * @returns {Array<{ok: boolean, result: any}>}
105
+ */
106
+ async function allSettled (promises) {
107
+ assertArray(promises)
108
+ const results = await Promise.allSettled(promises)
109
+ const rtnVal = []
110
+ for (const result of results) {
111
+ if (result.status === 'fulfilled') {
112
+ rtnVal.push({ ok: true, result: result.value })
113
+ }
114
+ if (result.status === 'rejected') {
115
+ rtnVal.push({ ok: false, result: result.reason })
116
+ }
117
+ }
118
+ return rtnVal
119
+ }
120
+
121
+ /**
122
+ * Execute the task Function, and ensure it returns a Promise.
123
+ * @param {function} task
124
+ * @returns {Promise<*>}
125
+ */
126
+ function returnValuePromised (task) {
127
+ try {
128
+ const taskRtnVal = task()
129
+ if (isPromise(taskRtnVal)) {
130
+ return taskRtnVal
131
+ }
132
+ return Promise.resolve(taskRtnVal)
133
+ } catch (e) {
134
+ return Promise.reject(e)
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Delays a promise by a specified time.
140
+ * 1. delay(), wait 1ms
141
+ * 2. delay(1000), wait 1000ms
142
+ * 3. delay(promise), after promise settled, wait 1000ms
143
+ * 4. delay(promise, 2000), after promise settled, wait 2000ms
144
+ *
145
+ * @param {Promise<*>|number|undefined} [promise] - The input promise to delay
146
+ * @param {number|undefined} [ms] - Minimum delay in milliseconds (default: 1)
147
+ * @returns {Promise} A new promise that settles after the delay period
148
+ */
149
+ function delay (promise, ms) {
150
+ if (isNumber(promise)) {
151
+ ms = promise
152
+ promise = Promise.resolve()
153
+ } else if (promise == null && ms == null) {
154
+ ms = 1
155
+ promise = Promise.resolve()
156
+ }
157
+ promise != null && assertPromise(promise)
158
+ ms = ms ?? 1000
159
+ assertNumber(ms)
160
+ const deferred = PromiseUtils.defer()
161
+ const startTs = Date.now()
162
+ promise
163
+ .then((...args) => {
164
+ const escaped = Date.now() - startTs
165
+ if (escaped < ms) {
166
+ setTimeout(() => deferred.resolve(...args), ms - escaped)
167
+ } else {
168
+ deferred.resolve(...args)
169
+ }
170
+ })
171
+ .catch((err) => {
172
+ const escaped = Date.now() - startTs
173
+ if (escaped < ms) {
174
+ setTimeout(() => deferred.reject(err), ms - escaped)
175
+ } else {
176
+ deferred.reject(err)
177
+ }
178
+ })
179
+ return deferred.promise
180
+ }
181
+
182
+ /**
183
+ * Fast-Fail mode to execute Tasks(functions) in series (one after another) and returns their results in order.
184
+ * 1. function are executed one by one
185
+ * 2. Fast Fail: if any tasks fail, the whole chain is rejected with the first error
186
+ * 3. if an element is not function, rejects the whole chain with Error(Not Function)
187
+ * @param {Function[]} promises
188
+ * @returns {Promise<Array>} Promise that resolves with an array of results in the same order as input tasks
189
+ */
190
+ async function series (tasks) {
191
+ assertArray(tasks)
192
+ const results = []
193
+ for (const task of tasks) {
194
+ assertFunction(task)
195
+ results.push(await task())
196
+ }
197
+ return results
198
+ }
199
+
200
+ /**
201
+ * AllSettled Mode to execute Tasks(functions) in series (one after another) and returns their results in order.
202
+ * 1. tasks are executed one by one
203
+ * 2. Each result is an object with `ok` (boolean) and `result` (resolved value or error).
204
+ * 3. if a task is not Function, rejects the whole chain with Error(Not Function)
205
+ * @param {Function[]} tasks
206
+ * @returns {Promise<Array<{ok: boolean, result: *}>>}
207
+ */
208
+ async function seriesAllSettled (tasks) {
209
+ assertArray(tasks)
210
+ const results = []
211
+ for (const task of tasks) {
212
+ assertFunction(task)
213
+ try {
214
+ results.push({ ok: true, result: await task() })
215
+ } catch (err) {
216
+ results.push({ ok: false, result: err })
217
+ }
218
+ }
219
+ return results
220
+ }
221
+
222
+ /**
223
+ * FastFail Mode to Execute tasks in parallel with a maximum concurrency limit
224
+ * 1. tasks are executed in parallel with a maximum concurrency limit
225
+ * 2. rejects whole chain with the first error, when first task fails
226
+ * @param {Function[]} tasks
227
+ * @param {number} [maxParallel=5]
228
+ * @returns {Promise<Array>} Array of resolved values from all promises
229
+ * @throws {TypeError} If input is not an array of function or maxParallel is not a number
230
+ */
231
+ async function parallel (tasks, maxParallel = 5) {
232
+ assertArray(tasks)
233
+ assertNumber(maxParallel)
234
+ if (maxParallel <= 0) {
235
+ throw new Error(`Invalid maxParallel: ${maxParallel}, should > 0`)
236
+ }
237
+ tasks.forEach((task) => assertFunction(task))
238
+ const rtnVal = []
239
+ // once for all, run all tasks
240
+ if (tasks.length <= maxParallel) {
241
+ const resultsForBatch = await Promise.all(tasks.map(task => PromiseUtils.returnValuePromised(task)))
242
+ rtnVal.push(...resultsForBatch)
243
+ return rtnVal
244
+ }
245
+ // run group by MaxParallel
246
+ const tasksToRun = []
247
+ for (const task of tasks) {
248
+ assertFunction(task)
249
+ tasksToRun.push(task)
250
+ if (tasksToRun.length >= maxParallel) {
251
+ const resultsForBatch = await Promise.all(tasksToRun.map(task => PromiseUtils.returnValuePromised(task)))
252
+ rtnVal.push(...resultsForBatch)
253
+ tasksToRun.length = 0
254
+ }
255
+ }
256
+ // Run all rested
257
+ if (tasksToRun.length > 0 && tasksToRun.length < maxParallel) {
258
+ const resultsForBatch = await Promise.all(tasksToRun.map(task => PromiseUtils.returnValuePromised(task)))
259
+ rtnVal.push(...resultsForBatch)
260
+ }
261
+ return rtnVal
262
+ }
263
+
264
+ /**
265
+ * AllSettled Mode to execute tasks in parallel with a maximum concurrency limit
266
+ * 1. tasks are executed in parallel with a maximum concurrency limit
267
+ * 2. all tasks will be executed, even some of them failed.
268
+ * @param {Function[]} tasks
269
+ * @param {number} [maxParallel=5] - Maximum number of tasks to run in parallel
270
+ * @returns {Promise<Array>} Array of resolved values from all promises
271
+ * @throws {TypeError} If input is not an array of function or maxParallel is not a number
272
+ */
273
+ async function parallelAllSettled (tasks, maxParallel = 5) {
274
+ assertArray(tasks)
275
+ assertNumber(maxParallel)
276
+ if (maxParallel <= 0) {
277
+ throw new Error(`Invalid maxParallel: ${maxParallel}, should > 0`)
278
+ }
279
+ tasks.forEach((task) => assertFunction(task))
280
+ const rtnVal = []
281
+ // once for all, run all promises
282
+ if (tasks.length <= maxParallel) {
283
+ const resultsForBatch = await PromiseUtils.allSettled(tasks.map(task => PromiseUtils.returnValuePromised(task)))
284
+ rtnVal.push(...resultsForBatch)
285
+ return rtnVal
286
+ }
287
+ // run group by MaxParallel
288
+ const tasksToRun = []
289
+ for (const task of tasks) {
290
+ assertFunction(task)
291
+ tasksToRun.push(task)
292
+ if (tasksToRun.length >= maxParallel) {
293
+ const resultsForBatch = await PromiseUtils.allSettled(tasksToRun.map(task => PromiseUtils.returnValuePromised(task)))
294
+ rtnVal.push(...resultsForBatch)
295
+ tasksToRun.length = 0
296
+ }
297
+ }
298
+ // Run all rested
299
+ if (tasksToRun.length > 0 && tasksToRun.length < maxParallel) {
300
+ const resultsForBatch = await PromiseUtils.allSettled(tasksToRun.map(task => PromiseUtils.returnValuePromised(task)))
301
+ rtnVal.push(...resultsForBatch)
302
+ }
303
+ return rtnVal
304
+ }
305
+ const PromiseUtils = {
306
+ defer,
307
+ delay,
308
+ timeout,
309
+ allSettled,
310
+ returnValuePromised,
311
+ series,
312
+ seriesAllSettled,
313
+ parallel,
314
+ parallelAllSettled
315
+ }
316
+
317
+ module.exports = PromiseUtils