@furystack/utils 8.1.3 → 8.1.5

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.
@@ -6,8 +6,8 @@ export type DeepPartial<T> = {
6
6
  };
7
7
  /**
8
8
  * Deep merge two objects.
9
- * @param target The source object to be merged
10
- * @param sources The source objects
9
+ * @param target The target object to be merged into
10
+ * @param sources The source objects to merge
11
11
  * @returns A new instance with the merged values
12
12
  */
13
13
  export declare const deepMerge: <T>(target: T, ...sources: Array<DeepPartial<T> | undefined>) => T;
package/esm/deep-merge.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Deep merge two objects.
3
- * @param target The source object to be merged
4
- * @param sources The source objects
3
+ * @param target The target object to be merged into
4
+ * @param sources The source objects to merge
5
5
  * @returns A new instance with the merged values
6
6
  */
7
7
  export const deepMerge = (target, ...sources) => {
@@ -1,7 +1,6 @@
1
1
  /**
2
- *
3
2
  * @param value The value to check
4
- * @returns if the value is an instance of an async disposable object
3
+ * @returns whether the value is an instance of an async disposable object
5
4
  */
6
5
  export declare const isAsyncDisposable: (value: unknown) => value is AsyncDisposable;
7
6
  //# sourceMappingURL=is-async-disposable.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"is-async-disposable.d.ts","sourceRoot":"","sources":["../src/is-async-disposable.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,eAE3D,CAAA"}
1
+ {"version":3,"file":"is-async-disposable.d.ts","sourceRoot":"","sources":["../src/is-async-disposable.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,eAE3D,CAAA"}
@@ -1,7 +1,6 @@
1
1
  /**
2
- *
3
2
  * @param value The value to check
4
- * @returns if the value is an instance of an async disposable object
3
+ * @returns whether the value is an instance of an async disposable object
5
4
  */
6
5
  export const isAsyncDisposable = (value) => {
7
6
  return value?.[Symbol.asyncDispose] instanceof Function;
@@ -1 +1 @@
1
- {"version":3,"file":"is-async-disposable.js","sourceRoot":"","sources":["../src/is-async-disposable.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,KAAc,EAA4B,EAAE;IAC5E,OAAQ,KAAyB,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,YAAY,QAAQ,CAAA;AAC9E,CAAC,CAAA"}
1
+ {"version":3,"file":"is-async-disposable.js","sourceRoot":"","sources":["../src/is-async-disposable.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,KAAc,EAA4B,EAAE;IAC5E,OAAQ,KAAyB,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,YAAY,QAAQ,CAAA;AAC9E,CAAC,CAAA"}
@@ -1,7 +1,6 @@
1
1
  /**
2
- *
3
2
  * @param value The value to check
4
- * @returns if the value is an instance of a disposable object
3
+ * @returns whether the value is an instance of a disposable object
5
4
  */
6
5
  export declare const isDisposable: (value: unknown) => value is Disposable;
7
6
  //# sourceMappingURL=is-disposable.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"is-disposable.d.ts","sourceRoot":"","sources":["../src/is-disposable.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,UAEtD,CAAA"}
1
+ {"version":3,"file":"is-disposable.d.ts","sourceRoot":"","sources":["../src/is-disposable.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,UAEtD,CAAA"}
@@ -1,7 +1,6 @@
1
1
  /**
2
- *
3
2
  * @param value The value to check
4
- * @returns if the value is an instance of a disposable object
3
+ * @returns whether the value is an instance of a disposable object
5
4
  */
6
5
  export const isDisposable = (value) => {
7
6
  return value?.[Symbol.dispose] instanceof Function;
@@ -1 +1 @@
1
- {"version":3,"file":"is-disposable.js","sourceRoot":"","sources":["../src/is-disposable.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAc,EAAuB,EAAE;IAClE,OAAQ,KAAoB,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,QAAQ,CAAA;AACpE,CAAC,CAAA"}
1
+ {"version":3,"file":"is-disposable.js","sourceRoot":"","sources":["../src/is-disposable.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAc,EAAuB,EAAE;IAClE,OAAQ,KAAoB,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,QAAQ,CAAA;AACpE,CAAC,CAAA"}
@@ -15,7 +15,7 @@ export type ObservableValueOptions<T> = {
15
15
  * Defines a custom compare function to determine if the value should be updated and the observers should be notified
16
16
  * @param lastValue the last value
17
17
  * @param nextValue the next value
18
- * @returns if the value should be updated and the observers should be notified
18
+ * @returns whether the value should be updated and the observers should be notified
19
19
  */
20
20
  compare: (lastValue: T, nextValue: T) => boolean;
21
21
  };
@@ -58,7 +58,7 @@ export declare class ObservableValue<T> implements Disposable {
58
58
  /**
59
59
  * The observer will unsubscribe from the Observable
60
60
  * @param observer The ValueObserver instance
61
- * @returns if unsubscribing was successfull
61
+ * @returns whether unsubscribing was successful
62
62
  */
63
63
  unsubscribe(observer: ValueObserver<T>): boolean;
64
64
  /**
@@ -61,7 +61,7 @@ export class ObservableValue {
61
61
  /**
62
62
  * The observer will unsubscribe from the Observable
63
63
  * @param observer The ValueObserver instance
64
- * @returns if unsubscribing was successfull
64
+ * @returns whether unsubscribing was successful
65
65
  */
66
66
  unsubscribe(observer) {
67
67
  return this.observers.delete(observer);
@@ -40,5 +40,93 @@ export declare class PathHelper {
40
40
  */
41
41
  static getParentPath(path: string): string;
42
42
  static normalize(path: string): string;
43
+ /**
44
+ * Trims only the trailing slash from a URL or path
45
+ * @param url The URL or path to trim
46
+ * @returns The URL/path without trailing slash
47
+ * @example
48
+ * PathHelper.trimTrailingSlash('/api/') // '/api'
49
+ * PathHelper.trimTrailingSlash('http://example.com/') // 'http://example.com'
50
+ */
51
+ static trimTrailingSlash(url: string): string;
52
+ /**
53
+ * Trims only the leading slash from a path
54
+ * @param path The path to trim
55
+ * @returns The path without leading slash
56
+ * @example
57
+ * PathHelper.trimLeadingSlash('/api') // 'api'
58
+ * PathHelper.trimLeadingSlash('api') // 'api'
59
+ */
60
+ static trimLeadingSlash(path: string): string;
61
+ /**
62
+ * Ensures a path has a leading slash
63
+ * @param path The path to check
64
+ * @returns The path with a leading slash
65
+ * @example
66
+ * PathHelper.ensureLeadingSlash('api') // '/api'
67
+ * PathHelper.ensureLeadingSlash('/api') // '/api'
68
+ */
69
+ static ensureLeadingSlash(path: string): string;
70
+ /**
71
+ * Normalizes a base URL by ensuring it has no trailing slash
72
+ * @param baseUrl The base URL to normalize (e.g., 'http://example.com/' or '/api/')
73
+ * @returns The normalized base URL without trailing slash
74
+ * @example
75
+ * PathHelper.normalizeBaseUrl('http://example.com/') // 'http://example.com'
76
+ * PathHelper.normalizeBaseUrl('/api/') // '/api'
77
+ * PathHelper.normalizeBaseUrl('/api') // '/api'
78
+ */
79
+ static normalizeBaseUrl(baseUrl: string): string;
80
+ /**
81
+ * Joins a base URL with a path, handling slashes correctly
82
+ * Preserves protocols (http://, https://, etc.)
83
+ * @param baseUrl The base URL (with or without trailing slash)
84
+ * @param path The path to append (with or without leading slash)
85
+ * @returns The combined URL with correct slash handling
86
+ * @example
87
+ * PathHelper.joinUrl('http://example.com', '/path') // 'http://example.com/path'
88
+ * PathHelper.joinUrl('http://example.com/', 'path') // 'http://example.com/path'
89
+ * PathHelper.joinUrl('/api', '/users') // '/api/users'
90
+ * PathHelper.joinUrl('/api/', 'users') // '/api/users'
91
+ * PathHelper.joinUrl('http://example.com', '') // 'http://example.com'
92
+ */
93
+ static joinUrl(baseUrl: string, path: string): string;
94
+ /**
95
+ * Checks if a request URL matches a base URL pattern
96
+ * Handles trailing slash variations correctly
97
+ * @param requestUrl The incoming request URL
98
+ * @param baseUrl The base URL pattern to match against
99
+ * @returns true if the request URL matches the base URL
100
+ * @example
101
+ * PathHelper.matchesBaseUrl('/api/users', '/api') // true
102
+ * PathHelper.matchesBaseUrl('/api', '/api') // true
103
+ * PathHelper.matchesBaseUrl('/api', '/api/') // true
104
+ * PathHelper.matchesBaseUrl('/other', '/api') // false
105
+ * PathHelper.matchesBaseUrl('/api2', '/api') // false (not a path match)
106
+ */
107
+ static matchesBaseUrl(requestUrl: string, baseUrl: string): boolean;
108
+ /**
109
+ * Extracts the remaining path after a base URL
110
+ * Always returns a path with a leading slash (or empty string for exact matches)
111
+ * @param requestUrl The full request URL
112
+ * @param baseUrl The base URL to remove
113
+ * @returns The remaining path with leading slash, or empty string if exact match
114
+ * @example
115
+ * PathHelper.extractPath('/api/users', '/api') // '/users'
116
+ * PathHelper.extractPath('/api', '/api') // ''
117
+ * PathHelper.extractPath('/api/users?id=1', '/api') // '/users?id=1'
118
+ * PathHelper.extractPath('/api/', '/api') // '/'
119
+ */
120
+ static extractPath(requestUrl: string, baseUrl: string): string;
121
+ /**
122
+ * Normalizes a URL by removing consecutive slashes (except after protocol)
123
+ * @param url The URL to normalize
124
+ * @returns The normalized URL
125
+ * @example
126
+ * PathHelper.normalizeUrl('http://example.com//path') // 'http://example.com/path'
127
+ * PathHelper.normalizeUrl('/api//users///123') // '/api/users/123'
128
+ * PathHelper.normalizeUrl('http://example.com/') // 'http://example.com/'
129
+ */
130
+ static normalizeUrl(url: string): string;
43
131
  }
44
132
  //# sourceMappingURL=path-helper.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"path-helper.d.ts","sourceRoot":"","sources":["../src/path-helper.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,qBAAa,UAAU;IACrB;;;;OAIG;WACW,WAAW,CAAC,IAAI,EAAE,MAAM;IAUtC;;;;;OAKG;WACW,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE;IAIjD;;;;OAIG;WACW,SAAS,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE;IAIzC;;;;;OAKG;WACW,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO;IAIjF;;;;;;;OAOG;WACW,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;WAQnC,SAAS,CAAC,IAAI,EAAE,MAAM;CAGrC"}
1
+ {"version":3,"file":"path-helper.d.ts","sourceRoot":"","sources":["../src/path-helper.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,qBAAa,UAAU;IACrB;;;;OAIG;WACW,WAAW,CAAC,IAAI,EAAE,MAAM;IAUtC;;;;;OAKG;WACW,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE;IAIjD;;;;OAIG;WACW,SAAS,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE;IAIzC;;;;;OAKG;WACW,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO;IAIjF;;;;;;;OAOG;WACW,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;WAQnC,SAAS,CAAC,IAAI,EAAE,MAAM;IAIpC;;;;;;;OAOG;WACW,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAIpD;;;;;;;OAOG;WACW,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAIpD;;;;;;;OAOG;WACW,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAItD;;;;;;;;OAQG;WACW,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAIvD;;;;;;;;;;;;OAYG;WACW,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM;IAS5D;;;;;;;;;;;;OAYG;WACW,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO;IAc1E;;;;;;;;;;;OAWG;WACW,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM;IAiBtE;;;;;;;;OAQG;WACW,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;CAchD"}
@@ -62,5 +62,141 @@ export class PathHelper {
62
62
  static normalize(path) {
63
63
  return this.getSegments(path).join('/');
64
64
  }
65
+ /**
66
+ * Trims only the trailing slash from a URL or path
67
+ * @param url The URL or path to trim
68
+ * @returns The URL/path without trailing slash
69
+ * @example
70
+ * PathHelper.trimTrailingSlash('/api/') // '/api'
71
+ * PathHelper.trimTrailingSlash('http://example.com/') // 'http://example.com'
72
+ */
73
+ static trimTrailingSlash(url) {
74
+ return url.endsWith('/') ? url.slice(0, -1) : url;
75
+ }
76
+ /**
77
+ * Trims only the leading slash from a path
78
+ * @param path The path to trim
79
+ * @returns The path without leading slash
80
+ * @example
81
+ * PathHelper.trimLeadingSlash('/api') // 'api'
82
+ * PathHelper.trimLeadingSlash('api') // 'api'
83
+ */
84
+ static trimLeadingSlash(path) {
85
+ return path.startsWith('/') ? path.slice(1) : path;
86
+ }
87
+ /**
88
+ * Ensures a path has a leading slash
89
+ * @param path The path to check
90
+ * @returns The path with a leading slash
91
+ * @example
92
+ * PathHelper.ensureLeadingSlash('api') // '/api'
93
+ * PathHelper.ensureLeadingSlash('/api') // '/api'
94
+ */
95
+ static ensureLeadingSlash(path) {
96
+ return path.startsWith('/') ? path : `/${path}`;
97
+ }
98
+ /**
99
+ * Normalizes a base URL by ensuring it has no trailing slash
100
+ * @param baseUrl The base URL to normalize (e.g., 'http://example.com/' or '/api/')
101
+ * @returns The normalized base URL without trailing slash
102
+ * @example
103
+ * PathHelper.normalizeBaseUrl('http://example.com/') // 'http://example.com'
104
+ * PathHelper.normalizeBaseUrl('/api/') // '/api'
105
+ * PathHelper.normalizeBaseUrl('/api') // '/api'
106
+ */
107
+ static normalizeBaseUrl(baseUrl) {
108
+ return this.trimTrailingSlash(baseUrl);
109
+ }
110
+ /**
111
+ * Joins a base URL with a path, handling slashes correctly
112
+ * Preserves protocols (http://, https://, etc.)
113
+ * @param baseUrl The base URL (with or without trailing slash)
114
+ * @param path The path to append (with or without leading slash)
115
+ * @returns The combined URL with correct slash handling
116
+ * @example
117
+ * PathHelper.joinUrl('http://example.com', '/path') // 'http://example.com/path'
118
+ * PathHelper.joinUrl('http://example.com/', 'path') // 'http://example.com/path'
119
+ * PathHelper.joinUrl('/api', '/users') // '/api/users'
120
+ * PathHelper.joinUrl('/api/', 'users') // '/api/users'
121
+ * PathHelper.joinUrl('http://example.com', '') // 'http://example.com'
122
+ */
123
+ static joinUrl(baseUrl, path) {
124
+ if (!path) {
125
+ return this.normalizeBaseUrl(baseUrl);
126
+ }
127
+ const normalizedBase = this.normalizeBaseUrl(baseUrl);
128
+ const normalizedPath = this.ensureLeadingSlash(path);
129
+ return `${normalizedBase}${normalizedPath}`;
130
+ }
131
+ /**
132
+ * Checks if a request URL matches a base URL pattern
133
+ * Handles trailing slash variations correctly
134
+ * @param requestUrl The incoming request URL
135
+ * @param baseUrl The base URL pattern to match against
136
+ * @returns true if the request URL matches the base URL
137
+ * @example
138
+ * PathHelper.matchesBaseUrl('/api/users', '/api') // true
139
+ * PathHelper.matchesBaseUrl('/api', '/api') // true
140
+ * PathHelper.matchesBaseUrl('/api', '/api/') // true
141
+ * PathHelper.matchesBaseUrl('/other', '/api') // false
142
+ * PathHelper.matchesBaseUrl('/api2', '/api') // false (not a path match)
143
+ */
144
+ static matchesBaseUrl(requestUrl, baseUrl) {
145
+ const normalizedBase = this.normalizeBaseUrl(baseUrl);
146
+ const normalizedRequest = requestUrl;
147
+ // Exact match
148
+ if (normalizedRequest === normalizedBase) {
149
+ return true;
150
+ }
151
+ // Check if request starts with base followed by a slash
152
+ // This prevents '/api2' from matching '/api'
153
+ return normalizedRequest.startsWith(`${normalizedBase}/`);
154
+ }
155
+ /**
156
+ * Extracts the remaining path after a base URL
157
+ * Always returns a path with a leading slash (or empty string for exact matches)
158
+ * @param requestUrl The full request URL
159
+ * @param baseUrl The base URL to remove
160
+ * @returns The remaining path with leading slash, or empty string if exact match
161
+ * @example
162
+ * PathHelper.extractPath('/api/users', '/api') // '/users'
163
+ * PathHelper.extractPath('/api', '/api') // ''
164
+ * PathHelper.extractPath('/api/users?id=1', '/api') // '/users?id=1'
165
+ * PathHelper.extractPath('/api/', '/api') // '/'
166
+ */
167
+ static extractPath(requestUrl, baseUrl) {
168
+ const normalizedBase = this.normalizeBaseUrl(baseUrl);
169
+ // Exact match
170
+ if (requestUrl === normalizedBase) {
171
+ return '';
172
+ }
173
+ // If request doesn't match base, return the original request URL
174
+ if (!this.matchesBaseUrl(requestUrl, baseUrl)) {
175
+ return requestUrl;
176
+ }
177
+ // Extract the path after the base
178
+ return requestUrl.substring(normalizedBase.length);
179
+ }
180
+ /**
181
+ * Normalizes a URL by removing consecutive slashes (except after protocol)
182
+ * @param url The URL to normalize
183
+ * @returns The normalized URL
184
+ * @example
185
+ * PathHelper.normalizeUrl('http://example.com//path') // 'http://example.com/path'
186
+ * PathHelper.normalizeUrl('/api//users///123') // '/api/users/123'
187
+ * PathHelper.normalizeUrl('http://example.com/') // 'http://example.com/'
188
+ */
189
+ static normalizeUrl(url) {
190
+ // Handle protocol separately to preserve ://
191
+ const protocolMatch = url.match(/^([a-z][a-z0-9+.-]*:\/\/)(.*)$/i);
192
+ if (protocolMatch) {
193
+ const [, protocol, rest] = protocolMatch;
194
+ // Remove consecutive slashes from the rest
195
+ const normalized = rest.replace(/\/+/g, '/');
196
+ return `${protocol}${normalized}`;
197
+ }
198
+ // No protocol, just remove consecutive slashes
199
+ return url.replace(/\/+/g, '/');
200
+ }
65
201
  }
66
202
  //# sourceMappingURL=path-helper.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"path-helper.js","sourceRoot":"","sources":["../src/path-helper.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,OAAO,UAAU;IACrB;;;;OAIG;IACI,MAAM,CAAC,WAAW,CAAC,IAAY;QACpC,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1B,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;QAC3C,CAAC;QACD,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAC5B,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;QACvC,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAED;;;;;OAKG;IACI,MAAM,CAAC,WAAW,CAAC,IAAY;QACpC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;IACvE,CAAC;IAED;;;;OAIG;IACI,MAAM,CAAC,SAAS,CAAC,GAAG,IAAc;QACvC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAC7D,CAAC;IAED;;;;;OAKG;IACI,MAAM,CAAC,YAAY,CAAC,YAAoB,EAAE,cAAsB;QACrE,OAAO,cAAc,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;IACzE,CAAC;IAED;;;;;;;OAOG;IACI,MAAM,CAAC,aAAa,CAAC,IAAY;QACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;QACvC,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,QAAQ,CAAC,GAAG,EAAE,CAAA;QAChB,CAAC;QACD,OAAO,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAC3B,CAAC;IAEM,MAAM,CAAC,SAAS,CAAC,IAAY;QAClC,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACzC,CAAC;CACF"}
1
+ {"version":3,"file":"path-helper.js","sourceRoot":"","sources":["../src/path-helper.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,OAAO,UAAU;IACrB;;;;OAIG;IACI,MAAM,CAAC,WAAW,CAAC,IAAY;QACpC,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1B,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;QAC3C,CAAC;QACD,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAC5B,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;QACvC,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAED;;;;;OAKG;IACI,MAAM,CAAC,WAAW,CAAC,IAAY;QACpC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;IACvE,CAAC;IAED;;;;OAIG;IACI,MAAM,CAAC,SAAS,CAAC,GAAG,IAAc;QACvC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAC7D,CAAC;IAED;;;;;OAKG;IACI,MAAM,CAAC,YAAY,CAAC,YAAoB,EAAE,cAAsB;QACrE,OAAO,cAAc,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;IACzE,CAAC;IAED;;;;;;;OAOG;IACI,MAAM,CAAC,aAAa,CAAC,IAAY;QACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;QACvC,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,QAAQ,CAAC,GAAG,EAAE,CAAA;QAChB,CAAC;QACD,OAAO,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAC3B,CAAC;IAEM,MAAM,CAAC,SAAS,CAAC,IAAY;QAClC,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACzC,CAAC;IAED;;;;;;;OAOG;IACI,MAAM,CAAC,iBAAiB,CAAC,GAAW;QACzC,OAAO,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;IACnD,CAAC;IAED;;;;;;;OAOG;IACI,MAAM,CAAC,gBAAgB,CAAC,IAAY;QACzC,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IACpD,CAAC;IAED;;;;;;;OAOG;IACI,MAAM,CAAC,kBAAkB,CAAC,IAAY;QAC3C,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAA;IACjD,CAAC;IAED;;;;;;;;OAQG;IACI,MAAM,CAAC,gBAAgB,CAAC,OAAe;QAC5C,OAAO,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAA;IACxC,CAAC;IAED;;;;;;;;;;;;OAYG;IACI,MAAM,CAAC,OAAO,CAAC,OAAe,EAAE,IAAY;QACjD,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAA;QACvC,CAAC;QACD,MAAM,cAAc,GAAG,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAA;QACrD,MAAM,cAAc,GAAG,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAA;QACpD,OAAO,GAAG,cAAc,GAAG,cAAc,EAAE,CAAA;IAC7C,CAAC;IAED;;;;;;;;;;;;OAYG;IACI,MAAM,CAAC,cAAc,CAAC,UAAkB,EAAE,OAAe;QAC9D,MAAM,cAAc,GAAG,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAA;QACrD,MAAM,iBAAiB,GAAG,UAAU,CAAA;QAEpC,cAAc;QACd,IAAI,iBAAiB,KAAK,cAAc,EAAE,CAAC;YACzC,OAAO,IAAI,CAAA;QACb,CAAC;QAED,wDAAwD;QACxD,6CAA6C;QAC7C,OAAO,iBAAiB,CAAC,UAAU,CAAC,GAAG,cAAc,GAAG,CAAC,CAAA;IAC3D,CAAC;IAED;;;;;;;;;;;OAWG;IACI,MAAM,CAAC,WAAW,CAAC,UAAkB,EAAE,OAAe;QAC3D,MAAM,cAAc,GAAG,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAA;QAErD,cAAc;QACd,IAAI,UAAU,KAAK,cAAc,EAAE,CAAC;YAClC,OAAO,EAAE,CAAA;QACX,CAAC;QAED,iEAAiE;QACjE,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,UAAU,EAAE,OAAO,CAAC,EAAE,CAAC;YAC9C,OAAO,UAAU,CAAA;QACnB,CAAC;QAED,kCAAkC;QAClC,OAAO,UAAU,CAAC,SAAS,CAAC,cAAc,CAAC,MAAM,CAAC,CAAA;IACpD,CAAC;IAED;;;;;;;;OAQG;IACI,MAAM,CAAC,YAAY,CAAC,GAAW;QACpC,6CAA6C;QAC7C,MAAM,aAAa,GAAG,GAAG,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAA;QAElE,IAAI,aAAa,EAAE,CAAC;YAClB,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,aAAa,CAAA;YACxC,2CAA2C;YAC3C,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;YAC5C,OAAO,GAAG,QAAQ,GAAG,UAAU,EAAE,CAAA;QACnC,CAAC;QAED,+CAA+C;QAC/C,OAAO,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACjC,CAAC;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"path-helper.spec.d.ts","sourceRoot":"","sources":["../src/path-helper.spec.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,eAAO,MAAM,eAAe,yCA0E1B,CAAA"}
1
+ {"version":3,"file":"path-helper.spec.d.ts","sourceRoot":"","sources":["../src/path-helper.spec.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,eAAO,MAAM,eAAe,yCAuT1B,CAAA"}
@@ -64,5 +64,183 @@ export const pathHelperTests = describe('PathHelper', () => {
64
64
  expect(PathHelper.normalize('/Root/Example/Content/')).toBe('Root/Example/Content');
65
65
  });
66
66
  });
67
+ describe('#trimTrailingSlash()', () => {
68
+ it('should remove trailing slash', () => {
69
+ expect(PathHelper.trimTrailingSlash('/api/')).toBe('/api');
70
+ });
71
+ it('should handle URLs with trailing slash', () => {
72
+ expect(PathHelper.trimTrailingSlash('http://example.com/')).toBe('http://example.com');
73
+ });
74
+ it('should not modify paths without trailing slash', () => {
75
+ expect(PathHelper.trimTrailingSlash('/api')).toBe('/api');
76
+ });
77
+ it('should handle empty string', () => {
78
+ expect(PathHelper.trimTrailingSlash('')).toBe('');
79
+ });
80
+ it('should handle root path', () => {
81
+ expect(PathHelper.trimTrailingSlash('/')).toBe('');
82
+ });
83
+ });
84
+ describe('#trimLeadingSlash()', () => {
85
+ it('should remove leading slash', () => {
86
+ expect(PathHelper.trimLeadingSlash('/api')).toBe('api');
87
+ });
88
+ it('should not modify paths without leading slash', () => {
89
+ expect(PathHelper.trimLeadingSlash('api')).toBe('api');
90
+ });
91
+ it('should handle empty string', () => {
92
+ expect(PathHelper.trimLeadingSlash('')).toBe('');
93
+ });
94
+ it('should handle root path', () => {
95
+ expect(PathHelper.trimLeadingSlash('/')).toBe('');
96
+ });
97
+ });
98
+ describe('#ensureLeadingSlash()', () => {
99
+ it('should add leading slash when missing', () => {
100
+ expect(PathHelper.ensureLeadingSlash('api')).toBe('/api');
101
+ });
102
+ it('should not add extra slash when already present', () => {
103
+ expect(PathHelper.ensureLeadingSlash('/api')).toBe('/api');
104
+ });
105
+ it('should handle empty string', () => {
106
+ expect(PathHelper.ensureLeadingSlash('')).toBe('/');
107
+ });
108
+ });
109
+ describe('#normalizeBaseUrl()', () => {
110
+ it('should remove trailing slash from URL', () => {
111
+ expect(PathHelper.normalizeBaseUrl('http://example.com/')).toBe('http://example.com');
112
+ });
113
+ it('should remove trailing slash from path', () => {
114
+ expect(PathHelper.normalizeBaseUrl('/api/')).toBe('/api');
115
+ });
116
+ it('should not modify URL without trailing slash', () => {
117
+ expect(PathHelper.normalizeBaseUrl('/api')).toBe('/api');
118
+ });
119
+ it('should handle HTTPS URLs', () => {
120
+ expect(PathHelper.normalizeBaseUrl('https://example.com/')).toBe('https://example.com');
121
+ });
122
+ });
123
+ describe('#joinUrl()', () => {
124
+ it('should join URL with path (both with slashes)', () => {
125
+ expect(PathHelper.joinUrl('http://example.com/', '/path')).toBe('http://example.com/path');
126
+ });
127
+ it('should join URL with path (URL with slash)', () => {
128
+ expect(PathHelper.joinUrl('http://example.com/', 'path')).toBe('http://example.com/path');
129
+ });
130
+ it('should join URL with path (path with slash)', () => {
131
+ expect(PathHelper.joinUrl('http://example.com', '/path')).toBe('http://example.com/path');
132
+ });
133
+ it('should join URL with path (neither with slash)', () => {
134
+ expect(PathHelper.joinUrl('http://example.com', 'path')).toBe('http://example.com/path');
135
+ });
136
+ it('should join relative paths', () => {
137
+ expect(PathHelper.joinUrl('/api', '/users')).toBe('/api/users');
138
+ expect(PathHelper.joinUrl('/api/', 'users')).toBe('/api/users');
139
+ });
140
+ it('should handle empty path', () => {
141
+ expect(PathHelper.joinUrl('http://example.com', '')).toBe('http://example.com');
142
+ expect(PathHelper.joinUrl('http://example.com/', '')).toBe('http://example.com');
143
+ });
144
+ it('should handle complex paths', () => {
145
+ expect(PathHelper.joinUrl('http://example.com:8080', '/api/v1/users')).toBe('http://example.com:8080/api/v1/users');
146
+ });
147
+ it('should preserve HTTPS protocol', () => {
148
+ expect(PathHelper.joinUrl('https://secure.example.com', '/secure/path')).toBe('https://secure.example.com/secure/path');
149
+ });
150
+ });
151
+ describe('#matchesBaseUrl()', () => {
152
+ it('should match exact URL', () => {
153
+ expect(PathHelper.matchesBaseUrl('/api', '/api')).toBe(true);
154
+ });
155
+ it('should match URL with trailing slash in base', () => {
156
+ expect(PathHelper.matchesBaseUrl('/api', '/api/')).toBe(true);
157
+ });
158
+ it('should match URL starting with base', () => {
159
+ expect(PathHelper.matchesBaseUrl('/api/users', '/api')).toBe(true);
160
+ });
161
+ it('should not match different URL', () => {
162
+ expect(PathHelper.matchesBaseUrl('/other', '/api')).toBe(false);
163
+ });
164
+ it('should not match similar prefix without slash', () => {
165
+ expect(PathHelper.matchesBaseUrl('/api2', '/api')).toBe(false);
166
+ expect(PathHelper.matchesBaseUrl('/api-v2', '/api')).toBe(false);
167
+ });
168
+ it('should match nested paths', () => {
169
+ expect(PathHelper.matchesBaseUrl('/api/v1/users/123', '/api')).toBe(true);
170
+ expect(PathHelper.matchesBaseUrl('/api/v1/users/123', '/api/v1')).toBe(true);
171
+ });
172
+ it('should handle root path', () => {
173
+ expect(PathHelper.matchesBaseUrl('/api', '/')).toBe(true);
174
+ expect(PathHelper.matchesBaseUrl('/', '/')).toBe(true);
175
+ });
176
+ it('should match with query string', () => {
177
+ expect(PathHelper.matchesBaseUrl('/api/users?page=1', '/api')).toBe(true);
178
+ });
179
+ it('should match with trailing slash in request', () => {
180
+ expect(PathHelper.matchesBaseUrl('/api/', '/api')).toBe(true);
181
+ });
182
+ });
183
+ describe('#extractPath()', () => {
184
+ it('should extract path after base URL', () => {
185
+ expect(PathHelper.extractPath('/api/users', '/api')).toBe('/users');
186
+ });
187
+ it('should return empty string for exact match', () => {
188
+ expect(PathHelper.extractPath('/api', '/api')).toBe('');
189
+ });
190
+ it('should preserve query string', () => {
191
+ expect(PathHelper.extractPath('/api/users?id=1', '/api')).toBe('/users?id=1');
192
+ });
193
+ it('should handle trailing slash in request', () => {
194
+ expect(PathHelper.extractPath('/api/', '/api')).toBe('/');
195
+ });
196
+ it('should handle trailing slash in base', () => {
197
+ expect(PathHelper.extractPath('/api/users', '/api/')).toBe('/users');
198
+ });
199
+ it('should extract nested paths', () => {
200
+ expect(PathHelper.extractPath('/api/v1/users/123', '/api')).toBe('/v1/users/123');
201
+ expect(PathHelper.extractPath('/api/v1/users/123', '/api/v1')).toBe('/users/123');
202
+ });
203
+ it('should return original URL if no match', () => {
204
+ expect(PathHelper.extractPath('/other/path', '/api')).toBe('/other/path');
205
+ });
206
+ it('should handle complex query strings', () => {
207
+ expect(PathHelper.extractPath('/api/search?q=test&limit=10&offset=0', '/api')).toBe('/search?q=test&limit=10&offset=0');
208
+ });
209
+ it('should preserve fragments', () => {
210
+ expect(PathHelper.extractPath('/api/page#section', '/api')).toBe('/page#section');
211
+ });
212
+ });
213
+ describe('#normalizeUrl()', () => {
214
+ it('should remove double slashes from path', () => {
215
+ expect(PathHelper.normalizeUrl('/api//users')).toBe('/api/users');
216
+ });
217
+ it('should remove multiple consecutive slashes', () => {
218
+ expect(PathHelper.normalizeUrl('/api///users////123')).toBe('/api/users/123');
219
+ });
220
+ it('should preserve protocol slashes', () => {
221
+ expect(PathHelper.normalizeUrl('http://example.com//path')).toBe('http://example.com/path');
222
+ });
223
+ it('should handle HTTPS protocol', () => {
224
+ expect(PathHelper.normalizeUrl('https://example.com///path//to///resource')).toBe('https://example.com/path/to/resource');
225
+ });
226
+ it('should not modify already normalized URLs', () => {
227
+ expect(PathHelper.normalizeUrl('http://example.com/path')).toBe('http://example.com/path');
228
+ expect(PathHelper.normalizeUrl('/api/users')).toBe('/api/users');
229
+ });
230
+ it('should handle trailing slash', () => {
231
+ expect(PathHelper.normalizeUrl('http://example.com/')).toBe('http://example.com/');
232
+ expect(PathHelper.normalizeUrl('/api/')).toBe('/api/');
233
+ });
234
+ it('should handle custom protocols', () => {
235
+ expect(PathHelper.normalizeUrl('ftp://example.com//path')).toBe('ftp://example.com/path');
236
+ expect(PathHelper.normalizeUrl('ws://example.com//socket')).toBe('ws://example.com/socket');
237
+ });
238
+ it('should handle URLs with port', () => {
239
+ expect(PathHelper.normalizeUrl('http://example.com:8080//path')).toBe('http://example.com:8080/path');
240
+ });
241
+ it('should preserve query strings', () => {
242
+ expect(PathHelper.normalizeUrl('/api//users?page=1')).toBe('/api/users?page=1');
243
+ });
244
+ });
67
245
  });
68
246
  //# sourceMappingURL=path-helper.spec.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"path-helper.spec.js","sourceRoot":"","sources":["../src/path-helper.spec.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"path-helper.spec.js","sourceRoot":"","sources":["../src/path-helper.spec.ts"],"names":[],"mappings":"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"}
package/esm/sort-by.d.ts CHANGED
@@ -4,7 +4,10 @@ declare global {
4
4
  */
5
5
  export interface Array<T> {
6
6
  /**
7
- * Returns a promise with a new array of elements that meets the specified async callback
7
+ * Sorts the array by a specified field and direction
8
+ * @param field The field to sort by
9
+ * @param direction The sort direction, either 'asc' or 'desc' (default: 'asc')
10
+ * @returns A new sorted array
8
11
  */
9
12
  sortBy: (field: keyof T, direction?: 'asc' | 'desc') => T[];
10
13
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sort-by.d.ts","sourceRoot":"","sources":["../src/sort-by.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,MAAM,CAAC;IACb;;OAEG;IACH,MAAM,WAAW,KAAK,CAAC,CAAC;QACtB;;WAEG;QACH,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,SAAS,CAAC,EAAE,KAAK,GAAG,MAAM,KAAK,CAAC,EAAE,CAAA;KAC5D;CACF;AAED,eAAO,MAAM,SAAS,GAAI,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,EAAE,SAAS,CAAC,EAAE,SAAS,CAAC,EAAE,OAAO,CAAC,EAAE,WAAW,KAAK,GAAG,MAAM,eAU1G,CAAA"}
1
+ {"version":3,"file":"sort-by.d.ts","sourceRoot":"","sources":["../src/sort-by.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,MAAM,CAAC;IACb;;OAEG;IACH,MAAM,WAAW,KAAK,CAAC,CAAC;QACtB;;;;;WAKG;QACH,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,SAAS,CAAC,EAAE,KAAK,GAAG,MAAM,KAAK,CAAC,EAAE,CAAA;KAC5D;CACF;AAED,eAAO,MAAM,SAAS,GAAI,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,EAAE,SAAS,CAAC,EAAE,SAAS,CAAC,EAAE,OAAO,CAAC,EAAE,WAAW,KAAK,GAAG,MAAM,eAU1G,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"sort-by.js","sourceRoot":"","sources":["../src/sort-by.ts"],"names":[],"mappings":"AAYA,MAAM,CAAC,MAAM,SAAS,GAAG,CAAuB,OAAU,EAAE,OAAU,EAAE,KAAQ,EAAE,SAAyB,EAAE,EAAE;IAC7G,MAAM,CAAC,GAAG,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAA;IACjD,MAAM,CAAC,GAAG,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAA;IACjD,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,CAAC,CAAA;IACX,CAAC;IACD,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,CAAA;IACV,CAAC;IACD,OAAO,CAAC,CAAA;AACV,CAAC,CAAA;AAED,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,UAAU,GAAG,EAAE,SAAS,GAAG,KAAK;IACvD,+DAA+D;IAC/D,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC,CAAA;AAC7D,CAAC,CAAA"}
1
+ {"version":3,"file":"sort-by.js","sourceRoot":"","sources":["../src/sort-by.ts"],"names":[],"mappings":"AAeA,MAAM,CAAC,MAAM,SAAS,GAAG,CAAuB,OAAU,EAAE,OAAU,EAAE,KAAQ,EAAE,SAAyB,EAAE,EAAE;IAC7G,MAAM,CAAC,GAAG,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAA;IACjD,MAAM,CAAC,GAAG,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAA;IACjD,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,CAAC,CAAA;IACX,CAAC;IACD,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,CAAA;IACV,CAAC;IACD,OAAO,CAAC,CAAA;AACV,CAAC,CAAA;AAED,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,UAAU,GAAG,EAAE,SAAS,GAAG,KAAK;IACvD,+DAA+D;IAC/D,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC,CAAA;AAC7D,CAAC,CAAA"}
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Method that accepts an IDisposable resource that will be disposed after the callback
2
+ * Method that accepts a Disposable or AsyncDisposable resource that will be disposed after the callback
3
3
  * @param resource The resource that is used in the callback and will be disposed afterwards
4
- * @param callback The callback that will be executed asynchrounously before the resource will be disposed
4
+ * @param callback The callback that will be executed asynchronously before the resource will be disposed
5
5
  * @returns A promise that will be resolved with a return value after the resource is disposed
6
6
  */
7
7
  export declare const usingAsync: <T extends Disposable | AsyncDisposable, TReturns>(resource: T, callback: (r: T) => Promise<TReturns>) => Promise<TReturns>;
@@ -1,9 +1,9 @@
1
1
  import { isAsyncDisposable } from './is-async-disposable.js';
2
2
  import { isDisposable } from './is-disposable.js';
3
3
  /**
4
- * Method that accepts an IDisposable resource that will be disposed after the callback
4
+ * Method that accepts a Disposable or AsyncDisposable resource that will be disposed after the callback
5
5
  * @param resource The resource that is used in the callback and will be disposed afterwards
6
- * @param callback The callback that will be executed asynchrounously before the resource will be disposed
6
+ * @param callback The callback that will be executed asynchronously before the resource will be disposed
7
7
  * @returns A promise that will be resolved with a return value after the resource is disposed
8
8
  */
9
9
  export const usingAsync = async (resource, callback) => {
package/esm/using.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Method that accepts an IDisposable resource that will be disposed after the callback
2
+ * Method that accepts a Disposable resource that will be disposed after the callback
3
3
  * @param resource The resource that is used in the callback and will be disposed afterwards
4
- * @param callback The callback that will be executed synchrounously before the resource will be disposed
4
+ * @param callback The callback that will be executed synchronously before the resource will be disposed
5
5
  * @returns the value that will be returned by the callback method
6
6
  */
7
7
  export declare const using: <T extends Disposable, TReturns>(resource: T, callback: (r: T) => TReturns) => TReturns;
package/esm/using.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Method that accepts an IDisposable resource that will be disposed after the callback
2
+ * Method that accepts a Disposable resource that will be disposed after the callback
3
3
  * @param resource The resource that is used in the callback and will be disposed afterwards
4
- * @param callback The callback that will be executed synchrounously before the resource will be disposed
4
+ * @param callback The callback that will be executed synchronously before the resource will be disposed
5
5
  * @returns the value that will be returned by the callback method
6
6
  */
7
7
  export const using = (resource, callback) => {
@@ -33,7 +33,7 @@ export declare class ValueObserver<T> implements Disposable {
33
33
  */
34
34
  [Symbol.dispose](): void;
35
35
  /**
36
- * @constructs ValueObserver<T> the ValueObserver instance
36
+ * Creates a ValueObserver instance
37
37
  * @param observable The related Observable object
38
38
  * @param callback The callback that will be fired on change
39
39
  * @param options Additional options
@@ -31,7 +31,7 @@ export class ValueObserver {
31
31
  this.observable.unsubscribe(this);
32
32
  }
33
33
  /**
34
- * @constructs ValueObserver<T> the ValueObserver instance
34
+ * Creates a ValueObserver instance
35
35
  * @param observable The related Observable object
36
36
  * @param callback The callback that will be fired on change
37
37
  * @param options Additional options
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@furystack/utils",
3
- "version": "8.1.3",
3
+ "version": "8.1.5",
4
4
  "description": "General utilities",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -35,6 +35,6 @@
35
35
  "homepage": "https://github.com/furystack/furystack",
36
36
  "devDependencies": {
37
37
  "typescript": "^5.9.3",
38
- "vitest": "^3.2.4"
38
+ "vitest": "^4.0.10"
39
39
  }
40
40
  }
package/src/deep-merge.ts CHANGED
@@ -5,8 +5,8 @@ export type DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> }
5
5
 
6
6
  /**
7
7
  * Deep merge two objects.
8
- * @param target The source object to be merged
9
- * @param sources The source objects
8
+ * @param target The target object to be merged into
9
+ * @param sources The source objects to merge
10
10
  * @returns A new instance with the merged values
11
11
  */
12
12
  export const deepMerge = <T>(target: T, ...sources: Array<DeepPartial<T> | undefined>) => {
@@ -1,7 +1,6 @@
1
1
  /**
2
- *
3
2
  * @param value The value to check
4
- * @returns if the value is an instance of an async disposable object
3
+ * @returns whether the value is an instance of an async disposable object
5
4
  */
6
5
  export const isAsyncDisposable = (value: unknown): value is AsyncDisposable => {
7
6
  return (value as AsyncDisposable)?.[Symbol.asyncDispose] instanceof Function
@@ -1,7 +1,6 @@
1
1
  /**
2
- *
3
2
  * @param value The value to check
4
- * @returns if the value is an instance of a disposable object
3
+ * @returns whether the value is an instance of a disposable object
5
4
  */
6
5
  export const isDisposable = (value: unknown): value is Disposable => {
7
6
  return (value as Disposable)?.[Symbol.dispose] instanceof Function
@@ -20,7 +20,7 @@ export type ObservableValueOptions<T> = {
20
20
  * Defines a custom compare function to determine if the value should be updated and the observers should be notified
21
21
  * @param lastValue the last value
22
22
  * @param nextValue the next value
23
- * @returns if the value should be updated and the observers should be notified
23
+ * @returns whether the value should be updated and the observers should be notified
24
24
  */
25
25
  compare: (lastValue: T, nextValue: T) => boolean
26
26
  }
@@ -84,7 +84,7 @@ export class ObservableValue<T> implements Disposable {
84
84
  /**
85
85
  * The observer will unsubscribe from the Observable
86
86
  * @param observer The ValueObserver instance
87
- * @returns if unsubscribing was successfull
87
+ * @returns whether unsubscribing was successful
88
88
  */
89
89
  public unsubscribe(observer: ValueObserver<T>) {
90
90
  return this.observers.delete(observer)
@@ -78,4 +78,241 @@ export const pathHelperTests = describe('PathHelper', () => {
78
78
  expect(PathHelper.normalize('/Root/Example/Content/')).toBe('Root/Example/Content')
79
79
  })
80
80
  })
81
+
82
+ describe('#trimTrailingSlash()', () => {
83
+ it('should remove trailing slash', () => {
84
+ expect(PathHelper.trimTrailingSlash('/api/')).toBe('/api')
85
+ })
86
+
87
+ it('should handle URLs with trailing slash', () => {
88
+ expect(PathHelper.trimTrailingSlash('http://example.com/')).toBe('http://example.com')
89
+ })
90
+
91
+ it('should not modify paths without trailing slash', () => {
92
+ expect(PathHelper.trimTrailingSlash('/api')).toBe('/api')
93
+ })
94
+
95
+ it('should handle empty string', () => {
96
+ expect(PathHelper.trimTrailingSlash('')).toBe('')
97
+ })
98
+
99
+ it('should handle root path', () => {
100
+ expect(PathHelper.trimTrailingSlash('/')).toBe('')
101
+ })
102
+ })
103
+
104
+ describe('#trimLeadingSlash()', () => {
105
+ it('should remove leading slash', () => {
106
+ expect(PathHelper.trimLeadingSlash('/api')).toBe('api')
107
+ })
108
+
109
+ it('should not modify paths without leading slash', () => {
110
+ expect(PathHelper.trimLeadingSlash('api')).toBe('api')
111
+ })
112
+
113
+ it('should handle empty string', () => {
114
+ expect(PathHelper.trimLeadingSlash('')).toBe('')
115
+ })
116
+
117
+ it('should handle root path', () => {
118
+ expect(PathHelper.trimLeadingSlash('/')).toBe('')
119
+ })
120
+ })
121
+
122
+ describe('#ensureLeadingSlash()', () => {
123
+ it('should add leading slash when missing', () => {
124
+ expect(PathHelper.ensureLeadingSlash('api')).toBe('/api')
125
+ })
126
+
127
+ it('should not add extra slash when already present', () => {
128
+ expect(PathHelper.ensureLeadingSlash('/api')).toBe('/api')
129
+ })
130
+
131
+ it('should handle empty string', () => {
132
+ expect(PathHelper.ensureLeadingSlash('')).toBe('/')
133
+ })
134
+ })
135
+
136
+ describe('#normalizeBaseUrl()', () => {
137
+ it('should remove trailing slash from URL', () => {
138
+ expect(PathHelper.normalizeBaseUrl('http://example.com/')).toBe('http://example.com')
139
+ })
140
+
141
+ it('should remove trailing slash from path', () => {
142
+ expect(PathHelper.normalizeBaseUrl('/api/')).toBe('/api')
143
+ })
144
+
145
+ it('should not modify URL without trailing slash', () => {
146
+ expect(PathHelper.normalizeBaseUrl('/api')).toBe('/api')
147
+ })
148
+
149
+ it('should handle HTTPS URLs', () => {
150
+ expect(PathHelper.normalizeBaseUrl('https://example.com/')).toBe('https://example.com')
151
+ })
152
+ })
153
+
154
+ describe('#joinUrl()', () => {
155
+ it('should join URL with path (both with slashes)', () => {
156
+ expect(PathHelper.joinUrl('http://example.com/', '/path')).toBe('http://example.com/path')
157
+ })
158
+
159
+ it('should join URL with path (URL with slash)', () => {
160
+ expect(PathHelper.joinUrl('http://example.com/', 'path')).toBe('http://example.com/path')
161
+ })
162
+
163
+ it('should join URL with path (path with slash)', () => {
164
+ expect(PathHelper.joinUrl('http://example.com', '/path')).toBe('http://example.com/path')
165
+ })
166
+
167
+ it('should join URL with path (neither with slash)', () => {
168
+ expect(PathHelper.joinUrl('http://example.com', 'path')).toBe('http://example.com/path')
169
+ })
170
+
171
+ it('should join relative paths', () => {
172
+ expect(PathHelper.joinUrl('/api', '/users')).toBe('/api/users')
173
+ expect(PathHelper.joinUrl('/api/', 'users')).toBe('/api/users')
174
+ })
175
+
176
+ it('should handle empty path', () => {
177
+ expect(PathHelper.joinUrl('http://example.com', '')).toBe('http://example.com')
178
+ expect(PathHelper.joinUrl('http://example.com/', '')).toBe('http://example.com')
179
+ })
180
+
181
+ it('should handle complex paths', () => {
182
+ expect(PathHelper.joinUrl('http://example.com:8080', '/api/v1/users')).toBe(
183
+ 'http://example.com:8080/api/v1/users',
184
+ )
185
+ })
186
+
187
+ it('should preserve HTTPS protocol', () => {
188
+ expect(PathHelper.joinUrl('https://secure.example.com', '/secure/path')).toBe(
189
+ 'https://secure.example.com/secure/path',
190
+ )
191
+ })
192
+ })
193
+
194
+ describe('#matchesBaseUrl()', () => {
195
+ it('should match exact URL', () => {
196
+ expect(PathHelper.matchesBaseUrl('/api', '/api')).toBe(true)
197
+ })
198
+
199
+ it('should match URL with trailing slash in base', () => {
200
+ expect(PathHelper.matchesBaseUrl('/api', '/api/')).toBe(true)
201
+ })
202
+
203
+ it('should match URL starting with base', () => {
204
+ expect(PathHelper.matchesBaseUrl('/api/users', '/api')).toBe(true)
205
+ })
206
+
207
+ it('should not match different URL', () => {
208
+ expect(PathHelper.matchesBaseUrl('/other', '/api')).toBe(false)
209
+ })
210
+
211
+ it('should not match similar prefix without slash', () => {
212
+ expect(PathHelper.matchesBaseUrl('/api2', '/api')).toBe(false)
213
+ expect(PathHelper.matchesBaseUrl('/api-v2', '/api')).toBe(false)
214
+ })
215
+
216
+ it('should match nested paths', () => {
217
+ expect(PathHelper.matchesBaseUrl('/api/v1/users/123', '/api')).toBe(true)
218
+ expect(PathHelper.matchesBaseUrl('/api/v1/users/123', '/api/v1')).toBe(true)
219
+ })
220
+
221
+ it('should handle root path', () => {
222
+ expect(PathHelper.matchesBaseUrl('/api', '/')).toBe(true)
223
+ expect(PathHelper.matchesBaseUrl('/', '/')).toBe(true)
224
+ })
225
+
226
+ it('should match with query string', () => {
227
+ expect(PathHelper.matchesBaseUrl('/api/users?page=1', '/api')).toBe(true)
228
+ })
229
+
230
+ it('should match with trailing slash in request', () => {
231
+ expect(PathHelper.matchesBaseUrl('/api/', '/api')).toBe(true)
232
+ })
233
+ })
234
+
235
+ describe('#extractPath()', () => {
236
+ it('should extract path after base URL', () => {
237
+ expect(PathHelper.extractPath('/api/users', '/api')).toBe('/users')
238
+ })
239
+
240
+ it('should return empty string for exact match', () => {
241
+ expect(PathHelper.extractPath('/api', '/api')).toBe('')
242
+ })
243
+
244
+ it('should preserve query string', () => {
245
+ expect(PathHelper.extractPath('/api/users?id=1', '/api')).toBe('/users?id=1')
246
+ })
247
+
248
+ it('should handle trailing slash in request', () => {
249
+ expect(PathHelper.extractPath('/api/', '/api')).toBe('/')
250
+ })
251
+
252
+ it('should handle trailing slash in base', () => {
253
+ expect(PathHelper.extractPath('/api/users', '/api/')).toBe('/users')
254
+ })
255
+
256
+ it('should extract nested paths', () => {
257
+ expect(PathHelper.extractPath('/api/v1/users/123', '/api')).toBe('/v1/users/123')
258
+ expect(PathHelper.extractPath('/api/v1/users/123', '/api/v1')).toBe('/users/123')
259
+ })
260
+
261
+ it('should return original URL if no match', () => {
262
+ expect(PathHelper.extractPath('/other/path', '/api')).toBe('/other/path')
263
+ })
264
+
265
+ it('should handle complex query strings', () => {
266
+ expect(PathHelper.extractPath('/api/search?q=test&limit=10&offset=0', '/api')).toBe(
267
+ '/search?q=test&limit=10&offset=0',
268
+ )
269
+ })
270
+
271
+ it('should preserve fragments', () => {
272
+ expect(PathHelper.extractPath('/api/page#section', '/api')).toBe('/page#section')
273
+ })
274
+ })
275
+
276
+ describe('#normalizeUrl()', () => {
277
+ it('should remove double slashes from path', () => {
278
+ expect(PathHelper.normalizeUrl('/api//users')).toBe('/api/users')
279
+ })
280
+
281
+ it('should remove multiple consecutive slashes', () => {
282
+ expect(PathHelper.normalizeUrl('/api///users////123')).toBe('/api/users/123')
283
+ })
284
+
285
+ it('should preserve protocol slashes', () => {
286
+ expect(PathHelper.normalizeUrl('http://example.com//path')).toBe('http://example.com/path')
287
+ })
288
+
289
+ it('should handle HTTPS protocol', () => {
290
+ expect(PathHelper.normalizeUrl('https://example.com///path//to///resource')).toBe(
291
+ 'https://example.com/path/to/resource',
292
+ )
293
+ })
294
+
295
+ it('should not modify already normalized URLs', () => {
296
+ expect(PathHelper.normalizeUrl('http://example.com/path')).toBe('http://example.com/path')
297
+ expect(PathHelper.normalizeUrl('/api/users')).toBe('/api/users')
298
+ })
299
+
300
+ it('should handle trailing slash', () => {
301
+ expect(PathHelper.normalizeUrl('http://example.com/')).toBe('http://example.com/')
302
+ expect(PathHelper.normalizeUrl('/api/')).toBe('/api/')
303
+ })
304
+
305
+ it('should handle custom protocols', () => {
306
+ expect(PathHelper.normalizeUrl('ftp://example.com//path')).toBe('ftp://example.com/path')
307
+ expect(PathHelper.normalizeUrl('ws://example.com//socket')).toBe('ws://example.com/socket')
308
+ })
309
+
310
+ it('should handle URLs with port', () => {
311
+ expect(PathHelper.normalizeUrl('http://example.com:8080//path')).toBe('http://example.com:8080/path')
312
+ })
313
+
314
+ it('should preserve query strings', () => {
315
+ expect(PathHelper.normalizeUrl('/api//users?page=1')).toBe('/api/users?page=1')
316
+ })
317
+ })
81
318
  })
@@ -67,4 +67,155 @@ export class PathHelper {
67
67
  public static normalize(path: string) {
68
68
  return this.getSegments(path).join('/')
69
69
  }
70
+
71
+ /**
72
+ * Trims only the trailing slash from a URL or path
73
+ * @param url The URL or path to trim
74
+ * @returns The URL/path without trailing slash
75
+ * @example
76
+ * PathHelper.trimTrailingSlash('/api/') // '/api'
77
+ * PathHelper.trimTrailingSlash('http://example.com/') // 'http://example.com'
78
+ */
79
+ public static trimTrailingSlash(url: string): string {
80
+ return url.endsWith('/') ? url.slice(0, -1) : url
81
+ }
82
+
83
+ /**
84
+ * Trims only the leading slash from a path
85
+ * @param path The path to trim
86
+ * @returns The path without leading slash
87
+ * @example
88
+ * PathHelper.trimLeadingSlash('/api') // 'api'
89
+ * PathHelper.trimLeadingSlash('api') // 'api'
90
+ */
91
+ public static trimLeadingSlash(path: string): string {
92
+ return path.startsWith('/') ? path.slice(1) : path
93
+ }
94
+
95
+ /**
96
+ * Ensures a path has a leading slash
97
+ * @param path The path to check
98
+ * @returns The path with a leading slash
99
+ * @example
100
+ * PathHelper.ensureLeadingSlash('api') // '/api'
101
+ * PathHelper.ensureLeadingSlash('/api') // '/api'
102
+ */
103
+ public static ensureLeadingSlash(path: string): string {
104
+ return path.startsWith('/') ? path : `/${path}`
105
+ }
106
+
107
+ /**
108
+ * Normalizes a base URL by ensuring it has no trailing slash
109
+ * @param baseUrl The base URL to normalize (e.g., 'http://example.com/' or '/api/')
110
+ * @returns The normalized base URL without trailing slash
111
+ * @example
112
+ * PathHelper.normalizeBaseUrl('http://example.com/') // 'http://example.com'
113
+ * PathHelper.normalizeBaseUrl('/api/') // '/api'
114
+ * PathHelper.normalizeBaseUrl('/api') // '/api'
115
+ */
116
+ public static normalizeBaseUrl(baseUrl: string): string {
117
+ return this.trimTrailingSlash(baseUrl)
118
+ }
119
+
120
+ /**
121
+ * Joins a base URL with a path, handling slashes correctly
122
+ * Preserves protocols (http://, https://, etc.)
123
+ * @param baseUrl The base URL (with or without trailing slash)
124
+ * @param path The path to append (with or without leading slash)
125
+ * @returns The combined URL with correct slash handling
126
+ * @example
127
+ * PathHelper.joinUrl('http://example.com', '/path') // 'http://example.com/path'
128
+ * PathHelper.joinUrl('http://example.com/', 'path') // 'http://example.com/path'
129
+ * PathHelper.joinUrl('/api', '/users') // '/api/users'
130
+ * PathHelper.joinUrl('/api/', 'users') // '/api/users'
131
+ * PathHelper.joinUrl('http://example.com', '') // 'http://example.com'
132
+ */
133
+ public static joinUrl(baseUrl: string, path: string): string {
134
+ if (!path) {
135
+ return this.normalizeBaseUrl(baseUrl)
136
+ }
137
+ const normalizedBase = this.normalizeBaseUrl(baseUrl)
138
+ const normalizedPath = this.ensureLeadingSlash(path)
139
+ return `${normalizedBase}${normalizedPath}`
140
+ }
141
+
142
+ /**
143
+ * Checks if a request URL matches a base URL pattern
144
+ * Handles trailing slash variations correctly
145
+ * @param requestUrl The incoming request URL
146
+ * @param baseUrl The base URL pattern to match against
147
+ * @returns true if the request URL matches the base URL
148
+ * @example
149
+ * PathHelper.matchesBaseUrl('/api/users', '/api') // true
150
+ * PathHelper.matchesBaseUrl('/api', '/api') // true
151
+ * PathHelper.matchesBaseUrl('/api', '/api/') // true
152
+ * PathHelper.matchesBaseUrl('/other', '/api') // false
153
+ * PathHelper.matchesBaseUrl('/api2', '/api') // false (not a path match)
154
+ */
155
+ public static matchesBaseUrl(requestUrl: string, baseUrl: string): boolean {
156
+ const normalizedBase = this.normalizeBaseUrl(baseUrl)
157
+ const normalizedRequest = requestUrl
158
+
159
+ // Exact match
160
+ if (normalizedRequest === normalizedBase) {
161
+ return true
162
+ }
163
+
164
+ // Check if request starts with base followed by a slash
165
+ // This prevents '/api2' from matching '/api'
166
+ return normalizedRequest.startsWith(`${normalizedBase}/`)
167
+ }
168
+
169
+ /**
170
+ * Extracts the remaining path after a base URL
171
+ * Always returns a path with a leading slash (or empty string for exact matches)
172
+ * @param requestUrl The full request URL
173
+ * @param baseUrl The base URL to remove
174
+ * @returns The remaining path with leading slash, or empty string if exact match
175
+ * @example
176
+ * PathHelper.extractPath('/api/users', '/api') // '/users'
177
+ * PathHelper.extractPath('/api', '/api') // ''
178
+ * PathHelper.extractPath('/api/users?id=1', '/api') // '/users?id=1'
179
+ * PathHelper.extractPath('/api/', '/api') // '/'
180
+ */
181
+ public static extractPath(requestUrl: string, baseUrl: string): string {
182
+ const normalizedBase = this.normalizeBaseUrl(baseUrl)
183
+
184
+ // Exact match
185
+ if (requestUrl === normalizedBase) {
186
+ return ''
187
+ }
188
+
189
+ // If request doesn't match base, return the original request URL
190
+ if (!this.matchesBaseUrl(requestUrl, baseUrl)) {
191
+ return requestUrl
192
+ }
193
+
194
+ // Extract the path after the base
195
+ return requestUrl.substring(normalizedBase.length)
196
+ }
197
+
198
+ /**
199
+ * Normalizes a URL by removing consecutive slashes (except after protocol)
200
+ * @param url The URL to normalize
201
+ * @returns The normalized URL
202
+ * @example
203
+ * PathHelper.normalizeUrl('http://example.com//path') // 'http://example.com/path'
204
+ * PathHelper.normalizeUrl('/api//users///123') // '/api/users/123'
205
+ * PathHelper.normalizeUrl('http://example.com/') // 'http://example.com/'
206
+ */
207
+ public static normalizeUrl(url: string): string {
208
+ // Handle protocol separately to preserve ://
209
+ const protocolMatch = url.match(/^([a-z][a-z0-9+.-]*:\/\/)(.*)$/i)
210
+
211
+ if (protocolMatch) {
212
+ const [, protocol, rest] = protocolMatch
213
+ // Remove consecutive slashes from the rest
214
+ const normalized = rest.replace(/\/+/g, '/')
215
+ return `${protocol}${normalized}`
216
+ }
217
+
218
+ // No protocol, just remove consecutive slashes
219
+ return url.replace(/\/+/g, '/')
220
+ }
70
221
  }
package/src/sort-by.ts CHANGED
@@ -4,7 +4,10 @@ declare global {
4
4
  */
5
5
  export interface Array<T> {
6
6
  /**
7
- * Returns a promise with a new array of elements that meets the specified async callback
7
+ * Sorts the array by a specified field and direction
8
+ * @param field The field to sort by
9
+ * @param direction The sort direction, either 'asc' or 'desc' (default: 'asc')
10
+ * @returns A new sorted array
8
11
  */
9
12
  sortBy: (field: keyof T, direction?: 'asc' | 'desc') => T[]
10
13
  }
@@ -2,9 +2,9 @@ import { isAsyncDisposable } from './is-async-disposable.js'
2
2
  import { isDisposable } from './is-disposable.js'
3
3
 
4
4
  /**
5
- * Method that accepts an IDisposable resource that will be disposed after the callback
5
+ * Method that accepts a Disposable or AsyncDisposable resource that will be disposed after the callback
6
6
  * @param resource The resource that is used in the callback and will be disposed afterwards
7
- * @param callback The callback that will be executed asynchrounously before the resource will be disposed
7
+ * @param callback The callback that will be executed asynchronously before the resource will be disposed
8
8
  * @returns A promise that will be resolved with a return value after the resource is disposed
9
9
  */
10
10
  export const usingAsync = async <T extends Disposable | AsyncDisposable, TReturns>(
package/src/using.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Method that accepts an IDisposable resource that will be disposed after the callback
2
+ * Method that accepts a Disposable resource that will be disposed after the callback
3
3
  * @param resource The resource that is used in the callback and will be disposed afterwards
4
- * @param callback The callback that will be executed synchrounously before the resource will be disposed
4
+ * @param callback The callback that will be executed synchronously before the resource will be disposed
5
5
  * @returns the value that will be returned by the callback method
6
6
  */
7
7
  export const using = <T extends Disposable, TReturns>(resource: T, callback: (r: T) => TReturns) => {
@@ -35,7 +35,7 @@ export class ValueObserver<T> implements Disposable {
35
35
  }
36
36
 
37
37
  /**
38
- * @constructs ValueObserver<T> the ValueObserver instance
38
+ * Creates a ValueObserver instance
39
39
  * @param observable The related Observable object
40
40
  * @param callback The callback that will be fired on change
41
41
  * @param options Additional options