@ls-stack/utils 3.8.0 → 3.9.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 CHANGED
@@ -1 +1,82 @@
1
- # Generic typescript utils
1
+ # @ls-stack/utils
2
+
3
+ Generic TypeScript utilities for modern JavaScript/TypeScript projects.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @ls-stack/utils
9
+ # or
10
+ pnpm add @ls-stack/utils
11
+ # or
12
+ yarn add @ls-stack/utils
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ Import specific utilities from their modules:
18
+
19
+ ```typescript
20
+ import { createAsyncQueue } from '@ls-stack/utils/asyncQueue';
21
+ import { deepEqual } from '@ls-stack/utils/deepEqual';
22
+ import { debounce } from '@ls-stack/utils/debounce';
23
+ ```
24
+
25
+ ## Available Utilities
26
+
27
+ This package includes a wide range of utilities for:
28
+
29
+ - **Array manipulation** (`arrayUtils`) - sorting, grouping, filtering
30
+ - **Async operations** (`asyncQueue`, `parallelAsyncCalls`, `promiseUtils`) - queue management and promise utilities
31
+ - **Type assertions** (`assertions`) - runtime type checking
32
+ - **Caching** (`cache`) - efficient caching with TTL support
33
+ - **Concurrency control** (`concurrentCalls`, `createThrottleController`) - rate limiting and throttling
34
+ - **Object utilities** (`objUtils`) - deep operations on objects
35
+ - **String utilities** (`stringUtils`) - string manipulation and formatting
36
+ - **Math utilities** (`mathUtils`) - mathematical operations and calculations
37
+ - **And many more...**
38
+
39
+ ## Documentation
40
+
41
+ Comprehensive API documentation is available in the [`docs/`](docs/) folder. Start with the [modules overview](docs/modules.md) to explore all available utilities.
42
+
43
+ ### Generating Documentation
44
+
45
+ To regenerate the documentation after making changes:
46
+
47
+ ```bash
48
+ pnpm docs
49
+ ```
50
+
51
+ For continuous updates during development:
52
+
53
+ ```bash
54
+ pnpm docs:watch
55
+ ```
56
+
57
+ ## Development
58
+
59
+ ```bash
60
+ # Install dependencies
61
+ pnpm install
62
+
63
+ # Run tests
64
+ pnpm test
65
+
66
+ # Run tests with UI
67
+ pnpm test:ui
68
+
69
+ # Build the library
70
+ pnpm build
71
+
72
+ # Lint code
73
+ pnpm lint
74
+ ```
75
+
76
+ ## License
77
+
78
+ MIT
79
+
80
+ ## Repository
81
+
82
+ [github:lucasols/utils](https://github.com/lucasols/utils)
@@ -29,9 +29,17 @@ __export(arrayUtils_exports, {
29
29
  isInArray: () => isInArray,
30
30
  rejectArrayUndefinedValues: () => rejectArrayUndefinedValues,
31
31
  rejectDuplicates: () => rejectDuplicates,
32
- sortBy: () => sortBy
32
+ sortBy: () => sortBy,
33
+ truncateArray: () => truncateArray
33
34
  });
34
35
  module.exports = __toCommonJS(arrayUtils_exports);
36
+
37
+ // src/assertions.ts
38
+ function isFunction(value) {
39
+ return typeof value === "function";
40
+ }
41
+
42
+ // src/arrayUtils.ts
35
43
  function filterAndMap(array, mapFilter) {
36
44
  const result = [];
37
45
  let i = -1;
@@ -133,6 +141,17 @@ function rejectDuplicates(array, getKey = (item) => item) {
133
141
  }
134
142
  return result;
135
143
  }
144
+ function truncateArray(array, maxLength, appendIfTruncated) {
145
+ const truncate = array.length > maxLength;
146
+ const result = truncate ? [...array.slice(0, maxLength)] : array;
147
+ if (truncate && appendIfTruncated) {
148
+ if (isFunction(appendIfTruncated)) {
149
+ return [...result, appendIfTruncated(array.length - maxLength)];
150
+ }
151
+ return [...result, appendIfTruncated];
152
+ }
153
+ return result;
154
+ }
136
155
  // Annotate the CommonJS export names for ESM import in node:
137
156
  0 && (module.exports = {
138
157
  arrayWithPrev,
@@ -144,5 +163,6 @@ function rejectDuplicates(array, getKey = (item) => item) {
144
163
  isInArray,
145
164
  rejectArrayUndefinedValues,
146
165
  rejectDuplicates,
147
- sortBy
166
+ sortBy,
167
+ truncateArray
148
168
  });
@@ -52,5 +52,6 @@ declare function findBeforeIndex<T>(array: T[], index: number, predicate: (item:
52
52
  declare function rejectArrayUndefinedValues<T extends unknown[]>(array: T): T;
53
53
  declare function hasDuplicates<T>(array: T[], getKey?: (item: T) => unknown): boolean;
54
54
  declare function rejectDuplicates<T>(array: T[], getKey?: (item: T) => unknown): T[];
55
+ declare function truncateArray<T>(array: T[], maxLength: number, appendIfTruncated?: T | ((truncatedCount: number) => T)): T[];
55
56
 
56
- export { type FilterAndMapReturn, arrayWithPrev, arrayWithPrevAndIndex, filterAndMap, findAfterIndex, findBeforeIndex, hasDuplicates, isInArray, rejectArrayUndefinedValues, rejectDuplicates, sortBy };
57
+ export { type FilterAndMapReturn, arrayWithPrev, arrayWithPrevAndIndex, filterAndMap, findAfterIndex, findBeforeIndex, hasDuplicates, isInArray, rejectArrayUndefinedValues, rejectDuplicates, sortBy, truncateArray };
@@ -52,5 +52,6 @@ declare function findBeforeIndex<T>(array: T[], index: number, predicate: (item:
52
52
  declare function rejectArrayUndefinedValues<T extends unknown[]>(array: T): T;
53
53
  declare function hasDuplicates<T>(array: T[], getKey?: (item: T) => unknown): boolean;
54
54
  declare function rejectDuplicates<T>(array: T[], getKey?: (item: T) => unknown): T[];
55
+ declare function truncateArray<T>(array: T[], maxLength: number, appendIfTruncated?: T | ((truncatedCount: number) => T)): T[];
55
56
 
56
- export { type FilterAndMapReturn, arrayWithPrev, arrayWithPrevAndIndex, filterAndMap, findAfterIndex, findBeforeIndex, hasDuplicates, isInArray, rejectArrayUndefinedValues, rejectDuplicates, sortBy };
57
+ export { type FilterAndMapReturn, arrayWithPrev, arrayWithPrevAndIndex, filterAndMap, findAfterIndex, findBeforeIndex, hasDuplicates, isInArray, rejectArrayUndefinedValues, rejectDuplicates, sortBy, truncateArray };
package/lib/arrayUtils.js CHANGED
@@ -8,8 +8,10 @@ import {
8
8
  isInArray,
9
9
  rejectArrayUndefinedValues,
10
10
  rejectDuplicates,
11
- sortBy
12
- } from "./chunk-XMUFRC2U.js";
11
+ sortBy,
12
+ truncateArray
13
+ } from "./chunk-UTFE4P3P.js";
14
+ import "./chunk-3XCS7FVO.js";
13
15
  export {
14
16
  arrayWithPrev,
15
17
  arrayWithPrevAndIndex,
@@ -20,5 +22,6 @@ export {
20
22
  isInArray,
21
23
  rejectArrayUndefinedValues,
22
24
  rejectDuplicates,
23
- sortBy
25
+ sortBy,
26
+ truncateArray
24
27
  };
@@ -95,13 +95,15 @@ var AsyncQueue = class {
95
95
  return deferred.promise;
96
96
  }
97
97
  resultifyAdd(fn, options) {
98
- const cb = async (ctx) => {
99
- if (isPromise(fn)) {
100
- return await fn;
101
- }
102
- return fn(ctx);
103
- };
104
- return this.add((ctx) => (0, import_t_result.resultify)(cb(ctx)), options);
98
+ return this.add(
99
+ (ctx) => (0, import_t_result.resultify)(async () => {
100
+ if (isPromise(fn)) {
101
+ return await fn;
102
+ }
103
+ return fn(ctx);
104
+ }),
105
+ options
106
+ );
105
107
  }
106
108
  async #processQueue() {
107
109
  if (this.#pending >= this.#concurrency || this.#queue.length === 0) {
@@ -125,20 +127,20 @@ var AsyncQueue = class {
125
127
  }
126
128
  const signal = signals.length > 1 ? AbortSignal.any(signals) : signals[0];
127
129
  let abortListener;
128
- const signalAbortPromise = new Promise((_, reject) => {
129
- if (signal) {
130
+ try {
131
+ if (signal?.aborted) {
130
132
  const error = signal.reason instanceof Error ? signal.reason : new DOMException("Aborted", "AbortError");
131
- if (signal.aborted) {
132
- reject(error);
133
- return;
134
- }
135
- abortListener = () => {
136
- reject(error);
137
- };
138
- signal.addEventListener("abort", abortListener, { once: true });
133
+ throw error;
139
134
  }
140
- });
141
- try {
135
+ const signalAbortPromise = new Promise((_, reject) => {
136
+ if (signal) {
137
+ const error = signal.reason instanceof Error ? signal.reason : new DOMException("Aborted", "AbortError");
138
+ abortListener = () => {
139
+ reject(error);
140
+ };
141
+ signal.addEventListener("abort", abortListener, { once: true });
142
+ }
143
+ });
142
144
  const taskRunPromise = task.run({ signal, id: task.id });
143
145
  this.events.emit("start", { id: task.id });
144
146
  const result = await Promise.race([taskRunPromise, signalAbortPromise]);
@@ -157,7 +159,7 @@ var AsyncQueue = class {
157
159
  this.#failed++;
158
160
  this.events.emit("error", {
159
161
  id: task.id,
160
- error: (0, import_t_result.unknownToError)(error)
162
+ error
161
163
  });
162
164
  }
163
165
  } catch (error) {
@@ -50,7 +50,7 @@ declare class AsyncQueueWithId<T, I, E extends ResultValidErrors = Error> extend
50
50
  add(fn: ((ctx: RunCtx<I>) => Promise<Result<T, E>> | Result<T, E>) | Promise<Result<T, E>>, options?: AddOptionsWithId<I>): Promise<Result<T, E | Error>>;
51
51
  resultifyAdd(fn: ((ctx: RunCtx<I>) => Promise<T> | T) | Promise<T>, options?: AddOptionsWithId<I>): Promise<Result<T, E | Error>>;
52
52
  }
53
- declare function createAsyncQueue<T, E extends ResultValidErrors = Error>(options?: AsyncQueueOptions): AsyncQueue<T, E, undefined>;
53
+ declare function createAsyncQueue<T, E extends ResultValidErrors = Error>(options?: AsyncQueueOptions): AsyncQueue<T, E>;
54
54
  declare function createAsyncQueueWithId<T, I, E extends ResultValidErrors = Error>(options?: AsyncQueueOptions): AsyncQueueWithId<T, I, E>;
55
55
 
56
56
  export { createAsyncQueue, createAsyncQueueWithId };
@@ -50,7 +50,7 @@ declare class AsyncQueueWithId<T, I, E extends ResultValidErrors = Error> extend
50
50
  add(fn: ((ctx: RunCtx<I>) => Promise<Result<T, E>> | Result<T, E>) | Promise<Result<T, E>>, options?: AddOptionsWithId<I>): Promise<Result<T, E | Error>>;
51
51
  resultifyAdd(fn: ((ctx: RunCtx<I>) => Promise<T> | T) | Promise<T>, options?: AddOptionsWithId<I>): Promise<Result<T, E | Error>>;
52
52
  }
53
- declare function createAsyncQueue<T, E extends ResultValidErrors = Error>(options?: AsyncQueueOptions): AsyncQueue<T, E, undefined>;
53
+ declare function createAsyncQueue<T, E extends ResultValidErrors = Error>(options?: AsyncQueueOptions): AsyncQueue<T, E>;
54
54
  declare function createAsyncQueueWithId<T, I, E extends ResultValidErrors = Error>(options?: AsyncQueueOptions): AsyncQueueWithId<T, I, E>;
55
55
 
56
56
  export { createAsyncQueue, createAsyncQueueWithId };
package/lib/asyncQueue.js CHANGED
@@ -58,13 +58,15 @@ var AsyncQueue = class {
58
58
  return deferred.promise;
59
59
  }
60
60
  resultifyAdd(fn, options) {
61
- const cb = async (ctx) => {
62
- if (isPromise(fn)) {
63
- return await fn;
64
- }
65
- return fn(ctx);
66
- };
67
- return this.add((ctx) => resultify(cb(ctx)), options);
61
+ return this.add(
62
+ (ctx) => resultify(async () => {
63
+ if (isPromise(fn)) {
64
+ return await fn;
65
+ }
66
+ return fn(ctx);
67
+ }),
68
+ options
69
+ );
68
70
  }
69
71
  async #processQueue() {
70
72
  if (this.#pending >= this.#concurrency || this.#queue.length === 0) {
@@ -88,20 +90,20 @@ var AsyncQueue = class {
88
90
  }
89
91
  const signal = signals.length > 1 ? AbortSignal.any(signals) : signals[0];
90
92
  let abortListener;
91
- const signalAbortPromise = new Promise((_, reject) => {
92
- if (signal) {
93
+ try {
94
+ if (signal?.aborted) {
93
95
  const error = signal.reason instanceof Error ? signal.reason : new DOMException("Aborted", "AbortError");
94
- if (signal.aborted) {
95
- reject(error);
96
- return;
97
- }
98
- abortListener = () => {
99
- reject(error);
100
- };
101
- signal.addEventListener("abort", abortListener, { once: true });
96
+ throw error;
102
97
  }
103
- });
104
- try {
98
+ const signalAbortPromise = new Promise((_, reject) => {
99
+ if (signal) {
100
+ const error = signal.reason instanceof Error ? signal.reason : new DOMException("Aborted", "AbortError");
101
+ abortListener = () => {
102
+ reject(error);
103
+ };
104
+ signal.addEventListener("abort", abortListener, { once: true });
105
+ }
106
+ });
105
107
  const taskRunPromise = task.run({ signal, id: task.id });
106
108
  this.events.emit("start", { id: task.id });
107
109
  const result = await Promise.race([taskRunPromise, signalAbortPromise]);
@@ -120,7 +122,7 @@ var AsyncQueue = class {
120
122
  this.#failed++;
121
123
  this.events.emit("error", {
122
124
  id: task.id,
123
- error: unknownToError(error)
125
+ error
124
126
  });
125
127
  }
126
128
  } catch (error) {
@@ -1,3 +1,7 @@
1
+ import {
2
+ isFunction
3
+ } from "./chunk-3XCS7FVO.js";
4
+
1
5
  // src/arrayUtils.ts
2
6
  function filterAndMap(array, mapFilter) {
3
7
  const result = [];
@@ -100,6 +104,17 @@ function rejectDuplicates(array, getKey = (item) => item) {
100
104
  }
101
105
  return result;
102
106
  }
107
+ function truncateArray(array, maxLength, appendIfTruncated) {
108
+ const truncate = array.length > maxLength;
109
+ const result = truncate ? [...array.slice(0, maxLength)] : array;
110
+ if (truncate && appendIfTruncated) {
111
+ if (isFunction(appendIfTruncated)) {
112
+ return [...result, appendIfTruncated(array.length - maxLength)];
113
+ }
114
+ return [...result, appendIfTruncated];
115
+ }
116
+ return result;
117
+ }
103
118
 
104
119
  export {
105
120
  filterAndMap,
@@ -111,5 +126,6 @@ export {
111
126
  findBeforeIndex,
112
127
  rejectArrayUndefinedValues,
113
128
  hasDuplicates,
114
- rejectDuplicates
129
+ rejectDuplicates,
130
+ truncateArray
115
131
  };
@@ -42,11 +42,30 @@ function isPromise(value) {
42
42
  return isObject(value) && "then" in value && isFunction(value.then);
43
43
  }
44
44
 
45
+ // src/arrayUtils.ts
46
+ function truncateArray(array, maxLength, appendIfTruncated) {
47
+ const truncate = array.length > maxLength;
48
+ const result = truncate ? [...array.slice(0, maxLength)] : array;
49
+ if (truncate && appendIfTruncated) {
50
+ if (isFunction(appendIfTruncated)) {
51
+ return [...result, appendIfTruncated(array.length - maxLength)];
52
+ }
53
+ return [...result, appendIfTruncated];
54
+ }
55
+ return result;
56
+ }
57
+
45
58
  // src/sleep.ts
46
59
  function sleep(ms) {
47
60
  return new Promise((resolve) => setTimeout(resolve, ms));
48
61
  }
49
62
 
63
+ // src/stringUtils.ts
64
+ function truncateString(str, length, ellipsis = "\u2026") {
65
+ if (str.length <= length) return str;
66
+ return str.slice(0, length - 1) + ellipsis;
67
+ }
68
+
50
69
  // src/concurrentCalls.ts
51
70
  var ConcurrentCalls = class {
52
71
  #pendingCalls = [];
@@ -266,7 +285,11 @@ var ConcurrentCallsWithMetadata = class {
266
285
  const allFailed = failedProcessing.length === total;
267
286
  const aggregatedError = failedProcessing.length > 0 ? new AggregateError(
268
287
  failedProcessing.map((f) => f.error),
269
- "One or more calls failed"
288
+ `${failedProcessing.length}/${total} calls failed: ${truncateArray(
289
+ failedProcessing.map((f) => truncateString(f.error.message, 20)),
290
+ 5,
291
+ "..."
292
+ ).join("; ")}`
270
293
  ) : null;
271
294
  return {
272
295
  succeeded: succeededProcessing,
@@ -1,6 +1,12 @@
1
+ import {
2
+ truncateString
3
+ } from "./chunk-4REIIZQY.js";
1
4
  import {
2
5
  sleep
3
6
  } from "./chunk-5DZT3Z5Z.js";
7
+ import {
8
+ truncateArray
9
+ } from "./chunk-UTFE4P3P.js";
4
10
  import {
5
11
  invariant,
6
12
  isPromise
@@ -226,7 +232,11 @@ var ConcurrentCallsWithMetadata = class {
226
232
  const allFailed = failedProcessing.length === total;
227
233
  const aggregatedError = failedProcessing.length > 0 ? new AggregateError(
228
234
  failedProcessing.map((f) => f.error),
229
- "One or more calls failed"
235
+ `${failedProcessing.length}/${total} calls failed: ${truncateArray(
236
+ failedProcessing.map((f) => truncateString(f.error.message, 20)),
237
+ 5,
238
+ "..."
239
+ ).join("; ")}`
230
240
  ) : null;
231
241
  return {
232
242
  succeeded: succeededProcessing,
package/lib/testUtils.cjs CHANGED
@@ -25,6 +25,11 @@ __export(testUtils_exports, {
25
25
  });
26
26
  module.exports = __toCommonJS(testUtils_exports);
27
27
 
28
+ // src/assertions.ts
29
+ function isObject(value) {
30
+ return typeof value === "object" && value !== null && !Array.isArray(value);
31
+ }
32
+
28
33
  // src/arrayUtils.ts
29
34
  function filterAndMap(array, mapFilter) {
30
35
  const result = [];
@@ -46,11 +51,6 @@ function arrayWithPrevAndIndex(array) {
46
51
  }));
47
52
  }
48
53
 
49
- // src/assertions.ts
50
- function isObject(value) {
51
- return typeof value === "object" && value !== null && !Array.isArray(value);
52
- }
53
-
54
54
  // src/deepEqual.ts
55
55
  var has = Object.prototype.hasOwnProperty;
56
56
  function find(iter, tar, maxDepth) {
package/lib/testUtils.js CHANGED
@@ -5,13 +5,13 @@ import {
5
5
  import {
6
6
  deepEqual
7
7
  } from "./chunk-JQFUKJU5.js";
8
- import {
9
- arrayWithPrevAndIndex,
10
- filterAndMap
11
- } from "./chunk-XMUFRC2U.js";
12
8
  import {
13
9
  clampMin
14
10
  } from "./chunk-HTCYUMDR.js";
11
+ import {
12
+ arrayWithPrevAndIndex,
13
+ filterAndMap
14
+ } from "./chunk-UTFE4P3P.js";
15
15
  import {
16
16
  isObject
17
17
  } from "./chunk-3XCS7FVO.js";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ls-stack/utils",
3
3
  "description": "Typescript utils",
4
- "version": "3.8.0",
4
+ "version": "3.9.1",
5
5
  "license": "MIT",
6
6
  "files": [
7
7
  "lib"
@@ -179,6 +179,9 @@
179
179
  "prettier-plugin-organize-imports": "^4.1.0",
180
180
  "tsm": "^2.3.0",
181
181
  "tsup": "^8.3.5",
182
+ "typedoc": "^0.28.4",
183
+ "typedoc-plugin-markdown": "^4.6.3",
184
+ "typedoc-plugin-missing-exports": "^4.0.0",
182
185
  "typescript": "^5.7.3",
183
186
  "typescript-eslint": "^8.21.0",
184
187
  "vite": "^6.0.11",
@@ -195,12 +198,14 @@
195
198
  "tsc": "tsc -p tsconfig.prod.json",
196
199
  "tsc:watch": "tsc -p tsconfig.prod.json --watch",
197
200
  "eslint": "CI=true eslint src/ scripts/ --color --max-warnings=0",
198
- "build": "pnpm test && pnpm lint && pnpm build:no-test && pnpm build:update-exports",
201
+ "build": "pnpm test && pnpm lint && pnpm build:no-test && pnpm docs && pnpm build:update-exports",
199
202
  "build:no-test": "tsup",
200
203
  "build:update-exports": "tsm --no-warnings scripts/updatePackageExports.ts",
201
204
  "build-test": "tsup --config tsup.test.config.ts",
202
205
  "pre-publish": "./scripts/check-if-is-sync.sh && pnpm build",
203
206
  "test:console-fmt": "tsm --no-warnings scripts/testConsoleFmt.ts",
204
- "bench:deepEqual": "tsm --no-warnings benchmarks/deepEqual.ts"
207
+ "bench:deepEqual": "tsm --no-warnings benchmarks/deepEqual.ts",
208
+ "docs": "typedoc",
209
+ "docs:watch": "typedoc --watch"
205
210
  }
206
211
  }