mentie 0.3.16 → 0.3.17

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/modules/cache.js CHANGED
@@ -29,6 +29,11 @@ export function cache( key, value, expires_in_ms=Infinity ) {
29
29
  log.warn( `The cache key is ${ key }, this may indicate a bug in your cache logic` )
30
30
  }
31
31
 
32
+ if( typeof expires_in_ms !== 'number' ) {
33
+ log.warn( `cache() expires_in_ms must be a number, got ${ typeof expires_in_ms }` )
34
+ expires_in_ms = Infinity
35
+ }
36
+
32
37
  // If value is provided, save value and expiration
33
38
  if( value !== undefined ) _cache[key] = { value, expires: Date.now() + expires_in_ms }
34
39
 
@@ -117,10 +117,14 @@ env.is_android = () => env.is_web() && navigator.userAgent?.toUpperCase().includ
117
117
  * Checks if the code is running in a web browser and the platform is iOS.
118
118
  * @returns {boolean} True if the code is running in a web browser and the platform is made by Apple
119
119
  */
120
- env.is_apple = () => env.is_web() && navigator.userAgent?.toUpperCase().includes( 'MAC OS X' )
120
+ env.is_apple = () => {
121
+ if( !env.is_web() ) return false
122
+ const userAgent = navigator.userAgent?.toUpperCase()
123
+ return userAgent?.includes( 'MAC OS X' ) || userAgent?.includes( 'IPHONE' ) || userAgent?.includes( 'IPAD' )
124
+ }
121
125
 
122
126
  /**
123
- * Checks if the code is running in a web browser and the platform is Windows.
127
+ * Checks if the code is running in a web browser and the platform is Linux.
124
128
  * @returns {boolean} True if the code is running in a web browser and the platform is Linux, otherwise false.
125
129
  */
126
130
  env.is_linux = () => env.is_web() && navigator.userAgent?.toUpperCase().includes( 'LINUX' )
@@ -6,32 +6,37 @@
6
6
  * @returns {Object} signal_data - Object containing fetch options and abort signal
7
7
  * @returns {Object} signal_data.fetch_options - Options for usage with fetch requests
8
8
  * @returns {AbortController} signal_data.controller - Raw abort controller
9
- * @returns {Function} signal_data.abort_signal - Function to abort the request
10
- * @returns {Function} signal_data.abort - Alias for abort_signal
11
- * @returns {number} signal_data.timeout_id - Timeout ID
9
+ * @returns {number|null} signal_data.timeout_id - Timeout ID, or null if no timeout was set
10
+ * @example
11
+ * const { fetch_options, controller } = abort_controller( { timeout_ms: 5000 } )
12
+ * fetch( 'https://api.example.com/data', fetch_options )
13
+ * controller.abort() // Abort the request
12
14
  */
13
15
  export const abort_controller = ( { timeout_ms }={} ) => {
14
16
 
17
+ // Input validation
18
+ if( timeout_ms !== undefined && ( typeof timeout_ms !== 'number' || timeout_ms < 0 ) ) {
19
+ throw new Error( 'timeout_ms must be a non-negative number' )
20
+ }
21
+
15
22
  // Request with timeout
16
23
  const controller = new AbortController()
17
- const timeout_id = timeout_ms && setTimeout( () => {
24
+ const timeout_id = timeout_ms !== undefined ? setTimeout( () => {
18
25
  controller.abort()
19
- }, timeout_ms )
26
+ }, timeout_ms ) : null
27
+
28
+ // Clear timeout when controller is aborted
29
+ controller.signal.addEventListener( 'abort', () => {
30
+ if( timeout_id !== null ) clearTimeout( timeout_id )
31
+ } )
20
32
 
21
33
  const fetch_options = {
22
34
  signal: controller.signal
23
35
  }
24
36
 
25
- const abort = () => {
26
- if( timeout_ms ) clearTimeout( timeout_id )
27
- controller.abort()
28
- }
29
-
30
37
  return {
31
38
  fetch_options,
32
39
  controller,
33
- abort_signal: abort,
34
- abort,
35
40
  timeout_id
36
41
  }
37
42
 
@@ -60,7 +60,7 @@ export async function make_retryable( async_function, { retry_times = 5, cooldow
60
60
  *
61
61
  * @param {Array<Function>} async_function_array - Array of async functions to be throttled and retried.
62
62
  * @param {Object} options - Options for throttling and retrying.
63
- * @param {number} [options.max_parallell=2] - Maximum number of functions to run in parallel.
63
+ * @param {number} [options.max_parallel=2] - Maximum number of functions to run in parallel.
64
64
  * @param {number} [options.retry_times] - Number of times to retry each function.
65
65
  * @param {number} [options.cooldown_in_s] - Cooldown time in seconds between retries.
66
66
  * @param {number} [options.cooldown_entropy] - Random factor to add to the cooldown time.
@@ -112,17 +112,26 @@ export async function throttle_and_retry( async_function_array = [], { max_paral
112
112
  */
113
113
  export async function promise_timeout( promise, timeout_in_ms=60_000, throw_on_timeout=true ) {
114
114
 
115
- // Timeout finction
116
- const timeout = () => new Promise( ( res, rej ) => setTimeout( throw_on_timeout ? rej : () => res( 'timed out' ), timeout_in_ms ) )
115
+ // Timeout id placeholder
116
+ let timeout_id
117
+
118
+ // Timeout function that stores the timeout ID so we can clear it
119
+ const timeout = () => new Promise( ( res, rej ) => {
120
+ timeout_id = setTimeout( throw_on_timeout ? rej : () => res( 'timed out' ), timeout_in_ms )
121
+ } )
117
122
 
118
123
  // Race the promise against the timeout
119
- return Promise.race( [
124
+ try {
125
+ const result = await Promise.race( [
126
+ promise,
127
+ timeout()
128
+ ] )
120
129
 
121
- // If the promise resolves first, return the result (including throwsing on error)
122
- promise,
130
+ return result
123
131
 
124
- // If this resolves first, this function throws or returns a timeout message
125
- timeout()
126
- ] )
132
+ } finally {
133
+ // Clear the timeout
134
+ clearTimeout( timeout_id )
135
+ }
127
136
 
128
137
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mentie",
3
- "version": "0.3.16",
3
+ "version": "0.3.17",
4
4
  "description": "Mentor's toolbelt",
5
5
  "type": "module",
6
6
  "main": "index.js",