@fuzdev/fuz_util 0.45.2 → 0.46.0

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/dist/regexp.js CHANGED
@@ -6,7 +6,7 @@ export const escape_regexp = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
6
6
  /**
7
7
  * Reset a RegExp's lastIndex to 0 for global and sticky patterns.
8
8
  * Ensures consistent behavior by clearing state that affects subsequent matches.
9
- * @mutates regexp sets lastIndex to 0 if regexp is global or sticky
9
+ * @mutates regexp - sets lastIndex to 0 if regexp is global or sticky
10
10
  */
11
11
  export const reset_regexp = (regexp) => {
12
12
  if (regexp.global || regexp.sticky) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_util",
3
- "version": "0.45.2",
3
+ "version": "0.46.0",
4
4
  "description": "utility belt for JS",
5
5
  "glyph": "🦕",
6
6
  "logo": "logo.svg",
@@ -61,16 +61,17 @@
61
61
  },
62
62
  "devDependencies": {
63
63
  "@changesets/changelog-git": "^0.2.1",
64
- "@fuzdev/fuz_code": "^0.38.0",
65
- "@fuzdev/fuz_css": "^0.43.0",
66
- "@fuzdev/fuz_ui": "^0.177.0",
64
+ "@fuzdev/fuz_code": "^0.40.0",
65
+ "@fuzdev/fuz_css": "^0.44.1",
66
+ "@fuzdev/fuz_ui": "^0.179.0",
67
67
  "@ryanatkn/eslint-config": "^0.9.0",
68
- "@ryanatkn/gro": "^0.184.0",
68
+ "@ryanatkn/gro": "^0.186.0",
69
69
  "@sveltejs/adapter-static": "^3.0.10",
70
70
  "@sveltejs/kit": "^2.49.1",
71
71
  "@sveltejs/package": "^2.5.7",
72
72
  "@sveltejs/vite-plugin-svelte": "^6.2.1",
73
73
  "@types/node": "^24.10.1",
74
+ "@webref/css": "^8.2.0",
74
75
  "dequal": "^2.0.3",
75
76
  "eslint": "^9.39.1",
76
77
  "eslint-plugin-svelte": "^3.13.1",
package/src/lib/array.ts CHANGED
@@ -9,7 +9,7 @@ export const to_array = <T>(value: T): T extends ReadonlyArray<any> ? T : Array<
9
9
 
10
10
  /**
11
11
  * Removes an element from `array` at `index` in an unordered manner.
12
- * @mutates array swaps element at index with last element, then removes last element
12
+ * @mutates array - swaps element at index with last element, then removes last element
13
13
  */
14
14
  export const remove_unordered = (array: Array<any>, index: number): void => {
15
15
  array[index] = array[array.length - 1];
package/src/lib/async.ts CHANGED
@@ -34,6 +34,77 @@ export const create_deferred = <T>(): Deferred<T> => {
34
34
  return {promise, resolve, reject};
35
35
  };
36
36
 
37
+ /**
38
+ * Runs an async function on each item with controlled concurrency.
39
+ * Like `map_concurrent` but doesn't collect results (more efficient for side effects).
40
+ *
41
+ * @param items array of items to process
42
+ * @param fn async function to apply to each item
43
+ * @param concurrency maximum number of concurrent operations
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * await each_concurrent(
48
+ * file_paths,
49
+ * async (path) => { await unlink(path); },
50
+ * 5, // max 5 concurrent deletions
51
+ * );
52
+ * ```
53
+ */
54
+ export const each_concurrent = async <T>(
55
+ items: Array<T>,
56
+ fn: (item: T, index: number) => Promise<void>,
57
+ concurrency: number,
58
+ ): Promise<void> => {
59
+ if (concurrency < 1) {
60
+ throw new Error('concurrency must be at least 1');
61
+ }
62
+
63
+ let next_index = 0;
64
+ let active_count = 0;
65
+ let rejected = false;
66
+
67
+ return new Promise((resolve, reject) => {
68
+ const run_next = (): void => {
69
+ // Stop spawning if we've rejected
70
+ if (rejected) return;
71
+
72
+ // Check if we're done
73
+ if (next_index >= items.length && active_count === 0) {
74
+ resolve();
75
+ return;
76
+ }
77
+
78
+ // Spawn workers up to concurrency limit
79
+ while (active_count < concurrency && next_index < items.length) {
80
+ const index = next_index++;
81
+ const item = items[index]!;
82
+ active_count++;
83
+
84
+ fn(item, index)
85
+ .then(() => {
86
+ if (rejected) return;
87
+ active_count--;
88
+ run_next();
89
+ })
90
+ .catch((error) => {
91
+ if (rejected) return;
92
+ rejected = true;
93
+ reject(error); // eslint-disable-line @typescript-eslint/prefer-promise-reject-errors
94
+ });
95
+ }
96
+ };
97
+
98
+ // Handle empty array
99
+ if (items.length === 0) {
100
+ resolve();
101
+ return;
102
+ }
103
+
104
+ run_next();
105
+ });
106
+ };
107
+
37
108
  /**
38
109
  * Maps over items with controlled concurrency, preserving input order.
39
110
  *
@@ -64,7 +135,6 @@ export const map_concurrent = async <T, R>(
64
135
  let next_index = 0;
65
136
  let active_count = 0;
66
137
  let rejected = false;
67
- let reject_error: unknown;
68
138
 
69
139
  return new Promise((resolve, reject) => {
70
140
  const run_next = (): void => {
@@ -93,8 +163,7 @@ export const map_concurrent = async <T, R>(
93
163
  .catch((error) => {
94
164
  if (rejected) return;
95
165
  rejected = true;
96
- reject_error = error;
97
- reject(reject_error); // eslint-disable-line @typescript-eslint/prefer-promise-reject-errors
166
+ reject(error); // eslint-disable-line @typescript-eslint/prefer-promise-reject-errors
98
167
  });
99
168
  }
100
169
  };
package/src/lib/dom.ts CHANGED
@@ -63,7 +63,7 @@ export const is_interactive = (el: any): boolean => {
63
63
  * @param event
64
64
  * @param immediate defaults to `true` to use `stopImmediatePropagation` over `stopPropagation`
65
65
  * @param preventDefault defaults to `true`
66
- * @mutates event calls preventDefault(), stopPropagation(), or stopImmediatePropagation()
66
+ * @mutates event - calls preventDefault(), stopPropagation(), or stopImmediatePropagation()
67
67
  * @returns
68
68
  */
69
69
  export const swallow = <
@@ -86,7 +86,7 @@ export const swallow = <
86
86
  /**
87
87
  * Handles the value of an event's target and invokes a callback.
88
88
  * Defaults to swallowing the event to prevent default actions and propagation.
89
- * @mutates event calls `swallow()` which mutates the event if `swallow_event` is true
89
+ * @mutates event - calls `swallow()` which mutates the event if `swallow_event` is true
90
90
  */
91
91
  export const handle_target_value =
92
92
  (cb: (value: any, event: any) => void, swallow_event = true) =>
package/src/lib/fetch.ts CHANGED
@@ -48,7 +48,7 @@ export interface FetchValueOptions<TValue, TParams = undefined> {
48
48
  * If the `value` is cached, only the cached safe subset of the `headers` are returned.
49
49
  * (currently just `etag` and `last-modified`)
50
50
  * Otherwise the full `res.headers` are included.
51
- * @mutates options.cache calls `cache.set()` to store fetched results if cache is provided
51
+ * @mutates options.cache - calls `cache.set()` to store fetched results if cache is provided
52
52
  */
53
53
  export const fetch_value = async <TValue = any, TParams = undefined>(
54
54
  url: string | URL,
package/src/lib/git.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type {SpawnOptions} from 'node:child_process';
2
2
  import {z} from 'zod';
3
3
 
4
- import {spawn, spawn_out} from './process.js';
4
+ import {spawn, spawn_out, spawn_result_to_message} from './process.js';
5
5
  import type {Flavored} from './types.js';
6
6
  import {to_file_path} from './path.js';
7
7
  import {fs_exists} from './fs.js';
@@ -65,11 +65,11 @@ export const git_remote_branch_exists = async (
65
65
  );
66
66
  if (result.ok) {
67
67
  return true;
68
- } else if (result.code === 2) {
68
+ } else if ('code' in result && result.code === 2) {
69
69
  return false;
70
70
  } else {
71
71
  throw Error(
72
- `git_remote_branch_exists failed for origin '${origin}' and branch '${final_branch}' with code ${result.code}`,
72
+ `git_remote_branch_exists failed for origin '${origin}' and branch '${final_branch}' with ${spawn_result_to_message(result)}`,
73
73
  );
74
74
  }
75
75
  };
@@ -235,7 +235,7 @@ export const git_fetch = async (
235
235
  const result = await spawn('git', args, options);
236
236
  if (!result.ok) {
237
237
  throw Error(
238
- `git_fetch failed for origin '${origin}' and branch '${branch}' with code ${result.code}`,
238
+ `git_fetch failed for origin '${origin}' and branch '${branch}' with ${spawn_result_to_message(result)}`,
239
239
  );
240
240
  }
241
241
  };
@@ -254,7 +254,9 @@ export const git_checkout = async (
254
254
  }
255
255
  const result = await spawn('git', ['checkout', branch], options);
256
256
  if (!result.ok) {
257
- throw Error(`git_checkout failed for branch '${branch}' with code ${result.code}`);
257
+ throw Error(
258
+ `git_checkout failed for branch '${branch}' with ${spawn_result_to_message(result)}`,
259
+ );
258
260
  }
259
261
  return current_branch;
260
262
  };
@@ -271,7 +273,7 @@ export const git_pull = async (
271
273
  if (branch) args.push(branch);
272
274
  const result = await spawn('git', args, options);
273
275
  if (!result.ok) {
274
- throw Error(`git_pull failed for branch '${branch}' with code ${result.code}`);
276
+ throw Error(`git_pull failed for branch '${branch}' with ${spawn_result_to_message(result)}`);
275
277
  }
276
278
  };
277
279
 
@@ -289,7 +291,9 @@ export const git_push = async (
289
291
  if (set_upstream) args.push('-u');
290
292
  const result = await spawn('git', args, options);
291
293
  if (!result.ok) {
292
- throw Error(`git_push failed for branch '${final_branch}' with code ${result.code}`);
294
+ throw Error(
295
+ `git_push failed for branch '${final_branch}' with ${spawn_result_to_message(result)}`,
296
+ );
293
297
  }
294
298
  };
295
299
 
@@ -311,7 +315,9 @@ export const git_push_to_create = async (
311
315
  push_args.push(final_branch);
312
316
  const result = await spawn('git', push_args, options);
313
317
  if (!result.ok) {
314
- throw Error(`git_push failed for branch '${final_branch}' with code ${result.code}`);
318
+ throw Error(
319
+ `git_push failed for branch '${final_branch}' with ${spawn_result_to_message(result)}`,
320
+ );
315
321
  }
316
322
  };
317
323
 
@@ -324,7 +330,9 @@ export const git_delete_local_branch = async (
324
330
  ): Promise<void> => {
325
331
  const result = await spawn('git', ['branch', '-D', branch], options);
326
332
  if (!result.ok) {
327
- throw Error(`git_delete_local_branch failed for branch '${branch}' with code ${result.code}`);
333
+ throw Error(
334
+ `git_delete_local_branch failed for branch '${branch}' with ${spawn_result_to_message(result)}`,
335
+ );
328
336
  }
329
337
  };
330
338
 
@@ -338,7 +346,9 @@ export const git_delete_remote_branch = async (
338
346
  ): Promise<void> => {
339
347
  const result = await spawn('git', ['push', origin, ':' + branch], options);
340
348
  if (!result.ok) {
341
- throw Error(`git_delete_remote_branch failed for branch '${branch}' with code ${result.code}`);
349
+ throw Error(
350
+ `git_delete_remote_branch failed for branch '${branch}' with ${spawn_result_to_message(result)}`,
351
+ );
342
352
  }
343
353
  };
344
354
 
package/src/lib/path.ts CHANGED
@@ -98,7 +98,7 @@ export const parse_path_pieces = (raw_path: string): Array<PathPiece> => {
98
98
  * Converts a string into a URL-compatible slug.
99
99
  * @param str the string to convert
100
100
  * @param map_special_characters if `true`, characters like `ñ` are converted to their ASCII equivalents, runs around 5x faster when disabled
101
- * @mutates special_char_mappers calls `get_special_char_mappers()` which lazily initializes the module-level array if `map_special_characters` is true
101
+ * @mutates special_char_mappers - calls `get_special_char_mappers()` which lazily initializes the module-level array if `map_special_characters` is true
102
102
  */
103
103
  export const slugify = (str: string, map_special_characters = true): string => {
104
104
  let s = str.toLowerCase();
@@ -123,7 +123,7 @@ let special_char_mappers: Array<(s: string) => string> | undefined;
123
123
  /**
124
124
  * Lazily constructs `special_char_mappers` which
125
125
  * converts special characters to their ASCII equivalents.
126
- * @mutates special_char_mappers pushes mapper functions into module-level array on first call
126
+ * @mutates special_char_mappers - pushes mapper functions into module-level array on first call
127
127
  */
128
128
  const get_special_char_mappers = (): Array<(s: string) => string> => {
129
129
  if (special_char_mappers) return special_char_mappers;
package/src/lib/print.ts CHANGED
@@ -7,7 +7,7 @@ export let st: typeof styleText = (_, v) => v;
7
7
 
8
8
  /**
9
9
  * Configures the module-level styling function for colored output.
10
- * @mutates st assigns the module-level `st` variable
10
+ * @mutates st - assigns the module-level `st` variable
11
11
  */
12
12
  export const configure_print_colors = (
13
13
  s: typeof styleText | null | undefined,