@catbee/utils 2.0.0-next.1 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +55 -38
  3. package/array/index.cjs +37 -5
  4. package/array/index.d.ts +59 -8
  5. package/array/index.mjs +32 -4
  6. package/async/index.cjs +24 -3
  7. package/async/index.d.ts +18 -2
  8. package/async/index.mjs +24 -4
  9. package/cache/index.cjs +1 -1
  10. package/cache/index.d.ts +1 -1
  11. package/cache/index.mjs +1 -1
  12. package/config/index.cjs +8 -8
  13. package/config/index.d.ts +1 -1
  14. package/config/index.mjs +4 -4
  15. package/context-store/index.cjs +2 -3
  16. package/context-store/index.d.ts +1 -1
  17. package/context-store/index.mjs +2 -3
  18. package/crypto/index.cjs +55 -5
  19. package/crypto/index.d.ts +63 -2
  20. package/crypto/index.mjs +52 -7
  21. package/date/index.cjs +630 -1
  22. package/date/index.d.ts +487 -2
  23. package/date/index.mjs +621 -2
  24. package/{decorators → decorator}/index.cjs +425 -304
  25. package/{decorators → decorator}/index.d.ts +3 -2
  26. package/{decorators → decorator}/index.mjs +425 -304
  27. package/{dir → directory}/index.cjs +1 -1
  28. package/{dir → directory}/index.d.ts +1 -1
  29. package/{dir → directory}/index.mjs +1 -1
  30. package/env/index.cjs +90 -42
  31. package/env/index.d.ts +12 -1
  32. package/env/index.mjs +90 -42
  33. package/exception/index.cjs +1 -1
  34. package/exception/index.d.ts +1 -1
  35. package/exception/index.mjs +1 -1
  36. package/fs/index.cjs +1 -1
  37. package/fs/index.d.ts +1 -1
  38. package/fs/index.mjs +1 -1
  39. package/http-status-codes/index.cjs +1 -1
  40. package/http-status-codes/index.d.ts +1 -1
  41. package/http-status-codes/index.mjs +1 -1
  42. package/id/index.cjs +1 -1
  43. package/id/index.d.ts +1 -1
  44. package/id/index.mjs +1 -1
  45. package/index.cjs +10 -10
  46. package/index.d.ts +4 -4
  47. package/index.mjs +4 -4
  48. package/logger/index.cjs +2 -4
  49. package/logger/index.d.ts +2 -2
  50. package/logger/index.mjs +2 -4
  51. package/middleware/index.cjs +1 -1
  52. package/middleware/index.d.ts +1 -1
  53. package/middleware/index.mjs +1 -1
  54. package/{obj → object}/index.cjs +182 -108
  55. package/{obj → object}/index.d.ts +38 -2
  56. package/{obj → object}/index.mjs +180 -109
  57. package/package.json +31 -13
  58. package/performance/index.cjs +2 -2
  59. package/performance/index.d.ts +1 -1
  60. package/performance/index.mjs +2 -2
  61. package/request/index.cjs +36 -24
  62. package/request/index.d.ts +1 -1
  63. package/request/index.mjs +36 -24
  64. package/response/index.cjs +1 -1
  65. package/response/index.d.ts +1 -1
  66. package/response/index.mjs +1 -1
  67. package/server/index.cjs +233 -131
  68. package/server/index.d.ts +81 -1
  69. package/server/index.mjs +229 -127
  70. package/stream/index.cjs +1 -1
  71. package/stream/index.d.ts +1 -1
  72. package/stream/index.mjs +1 -1
  73. package/string/index.cjs +34 -1
  74. package/string/index.d.ts +45 -2
  75. package/string/index.mjs +31 -2
  76. package/type/index.cjs +18 -1
  77. package/type/index.d.ts +38 -2
  78. package/type/index.mjs +16 -2
  79. package/types/index.cjs +1 -1
  80. package/types/index.d.ts +10 -3
  81. package/types/index.mjs +1 -1
  82. package/url/index.cjs +61 -1
  83. package/url/index.d.ts +59 -2
  84. package/url/index.mjs +57 -2
  85. package/validation/index.cjs +3 -3
  86. package/validation/index.d.ts +1 -1
  87. package/validation/index.mjs +3 -3
package/string/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * The MIT License
3
3
  *
4
- * Copyright (c) 2025 Catbee Technologies. https://catbee.in/license
4
+ * Copyright (c) 2026 Catbee Technologies. https://catbee.in/license
5
5
  *
6
6
  * Permission is hereby granted, free of charge, to any person obtaining a copy
7
7
  * of this software and associated documentation files (the "Software"), to deal
@@ -91,5 +91,34 @@ function toTitleCase(str) {
91
91
  return str.split(/(\s+)/).map((part) => part.trim().length === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join("");
92
92
  }
93
93
  __name(toTitleCase, "toTitleCase");
94
+ function escapeRegex(str) {
95
+ if (typeof str !== "string") throw new TypeError("Expected a string");
96
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
97
+ }
98
+ __name(escapeRegex, "escapeRegex");
99
+ function unescapeHtml(str) {
100
+ if (typeof str !== "string") return "";
101
+ const entities = {
102
+ "&": "&",
103
+ "&lt;": "<",
104
+ "&gt;": ">",
105
+ "&quot;": '"',
106
+ "&#39;": "'",
107
+ "&#x27;": "'"
108
+ };
109
+ return str.replace(/&(?:amp|lt|gt|quot|#39|#x27);/g, (match) => entities[match] || match);
110
+ }
111
+ __name(unescapeHtml, "unescapeHtml");
112
+ function isBlank(str) {
113
+ return typeof str === "string" && str.trim().length === 0;
114
+ }
115
+ __name(isBlank, "isBlank");
116
+ function ellipsis(str, maxLength, suffix = "...") {
117
+ if (typeof str !== "string" || str.length <= maxLength) return str;
118
+ const truncated = str.slice(0, maxLength - suffix.length);
119
+ const lastSpace = truncated.lastIndexOf(" ");
120
+ return (lastSpace > 0 ? truncated.slice(0, lastSpace) : truncated) + suffix;
121
+ }
122
+ __name(ellipsis, "ellipsis");
94
123
 
95
- export { capitalize, countOccurrences, equalsIgnoreCase, mask, reverse, slugify, stripHtml, toCamelCase, toKebabCase, toPascalCase, toSnakeCase, toTitleCase, truncate };
124
+ export { capitalize, countOccurrences, ellipsis, equalsIgnoreCase, escapeRegex, isBlank, mask, reverse, slugify, stripHtml, toCamelCase, toKebabCase, toPascalCase, toSnakeCase, toTitleCase, truncate, unescapeHtml };
package/type/index.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * The MIT License
3
3
  *
4
- * Copyright (c) 2025 Catbee Technologies. https://catbee.in/license
4
+ * Copyright (c) 2026 Catbee Technologies. https://catbee.in/license
5
5
  *
6
6
  * Permission is hereby granted, free of charge, to any person obtaining a copy
7
7
  * of this software and associated documentation files (the "Software"), to deal
@@ -117,12 +117,29 @@ function isEmpty(value) {
117
117
  return false;
118
118
  }
119
119
  __name(isEmpty, "isEmpty");
120
+ function isIterable(value) {
121
+ return value !== null && value !== void 0 && typeof value[Symbol.iterator] === "function";
122
+ }
123
+ __name(isIterable, "isIterable");
124
+ function isAsyncIterable(value) {
125
+ return value !== null && value !== void 0 && typeof value[Symbol.asyncIterator] === "function";
126
+ }
127
+ __name(isAsyncIterable, "isAsyncIterable");
128
+ function assertType(value, guard, message) {
129
+ if (!guard(value)) {
130
+ throw new TypeError(message || `Type assertion failed`);
131
+ }
132
+ }
133
+ __name(assertType, "assertType");
120
134
 
135
+ exports.assertType = assertType;
121
136
  exports.ensureType = ensureType;
122
137
  exports.getTypeOf = getTypeOf;
123
138
  exports.isArrayOf = isArrayOf;
139
+ exports.isAsyncIterable = isAsyncIterable;
124
140
  exports.isDefined = isDefined;
125
141
  exports.isEmpty = isEmpty;
142
+ exports.isIterable = isIterable;
126
143
  exports.isPrimitiveType = isPrimitiveType;
127
144
  exports.toBool = toBool;
128
145
  exports.toNum = toNum;
package/type/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * The MIT License
3
3
  *
4
- * Copyright (c) 2025 Catbee Technologies. https://catbee.in/license
4
+ * Copyright (c) 2026 Catbee Technologies. https://catbee.in/license
5
5
  *
6
6
  * Permission is hereby granted, free of charge, to any person obtaining a copy
7
7
  * of this software and associated documentation files (the "Software"), to deal
@@ -127,5 +127,41 @@ declare function isDefined<T>(value: T | null | undefined): value is T;
127
127
  * @returns True when value is considered empty
128
128
  */
129
129
  declare function isEmpty(value: any): boolean;
130
+ /**
131
+ * Type guard to check if a value is iterable.
132
+ *
133
+ * @param {unknown} value - Value to check.
134
+ * @returns {boolean} True if value is iterable.
135
+ *
136
+ * @example
137
+ * isIterable([1, 2, 3]); // true
138
+ * isIterable('hello'); // true
139
+ * isIterable(new Set([1, 2])); // true
140
+ */
141
+ declare function isIterable<T = any>(value: unknown): value is Iterable<T>;
142
+ /**
143
+ * Type guard to check if a value is an async iterable.
144
+ *
145
+ * @param {unknown} value - Value to check.
146
+ * @returns {boolean} True if value is async iterable.
147
+ *
148
+ * @example
149
+ * async function* gen() { yield 1; }
150
+ * isAsyncIterable(gen()); // true
151
+ */
152
+ declare function isAsyncIterable<T = any>(value: unknown): value is AsyncIterable<T>;
153
+ /**
154
+ * Asserts that a value is of a specific type, throwing an error if not.
155
+ *
156
+ * @template T
157
+ * @param {unknown} value - Value to assert.
158
+ * @param {(value: unknown) => value is T} guard - Type guard function.
159
+ * @param {string} [message] - Error message if assertion fails.
160
+ * @returns {asserts value is T}
161
+ *
162
+ * @example
163
+ * assertType(value, (v): v is string => typeof v === 'string', 'Expected string');
164
+ */
165
+ declare function assertType<T>(value: unknown, guard: (value: unknown) => value is T, message?: string): asserts value is T;
130
166
 
131
- export { ensureType, getTypeOf, isArrayOf, isDefined, isEmpty, isPrimitiveType, toBool, toNum, toStr };
167
+ export { assertType, ensureType, getTypeOf, isArrayOf, isAsyncIterable, isDefined, isEmpty, isIterable, isPrimitiveType, toBool, toNum, toStr };
package/type/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * The MIT License
3
3
  *
4
- * Copyright (c) 2025 Catbee Technologies. https://catbee.in/license
4
+ * Copyright (c) 2026 Catbee Technologies. https://catbee.in/license
5
5
  *
6
6
  * Permission is hereby granted, free of charge, to any person obtaining a copy
7
7
  * of this software and associated documentation files (the "Software"), to deal
@@ -115,5 +115,19 @@ function isEmpty(value) {
115
115
  return false;
116
116
  }
117
117
  __name(isEmpty, "isEmpty");
118
+ function isIterable(value) {
119
+ return value !== null && value !== void 0 && typeof value[Symbol.iterator] === "function";
120
+ }
121
+ __name(isIterable, "isIterable");
122
+ function isAsyncIterable(value) {
123
+ return value !== null && value !== void 0 && typeof value[Symbol.asyncIterator] === "function";
124
+ }
125
+ __name(isAsyncIterable, "isAsyncIterable");
126
+ function assertType(value, guard, message) {
127
+ if (!guard(value)) {
128
+ throw new TypeError(message || `Type assertion failed`);
129
+ }
130
+ }
131
+ __name(assertType, "assertType");
118
132
 
119
- export { ensureType, getTypeOf, isArrayOf, isDefined, isEmpty, isPrimitiveType, toBool, toNum, toStr };
133
+ export { assertType, ensureType, getTypeOf, isArrayOf, isAsyncIterable, isDefined, isEmpty, isIterable, isPrimitiveType, toBool, toNum, toStr };
package/types/index.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * The MIT License
3
3
  *
4
- * Copyright (c) 2025 Catbee Technologies. https://catbee.in/license
4
+ * Copyright (c) 2026 Catbee Technologies. https://catbee.in/license
5
5
  *
6
6
  * Permission is hereby granted, free of charge, to any person obtaining a copy
7
7
  * of this software and associated documentation files (the "Software"), to deal
package/types/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * The MIT License
3
3
  *
4
- * Copyright (c) 2025 Catbee Technologies. https://catbee.in/license
4
+ * Copyright (c) 2026 Catbee Technologies. https://catbee.in/license
5
5
  *
6
6
  * Permission is hereby granted, free of charge, to any person obtaining a copy
7
7
  * of this software and associated documentation files (the "Software"), to deal
@@ -24,6 +24,7 @@
24
24
 
25
25
  import { json, urlencoded, Request, Response, Express, NextFunction } from 'express';
26
26
  import http from 'node:http';
27
+ import https from 'node:https';
27
28
  import { ExpressServer } from '@catbee/utils/server';
28
29
  import { HelmetOptions } from 'helmet';
29
30
  import { CompressionOptions } from 'compression';
@@ -554,8 +555,14 @@ interface CatbeeServerConfig {
554
555
  interface CatbeeServerHooks {
555
556
  /** Called before middleware & routes initialize */
556
557
  beforeInit?: (server: ExpressServer) => Promise<void> | void;
558
+ /** Called before any routes are registered */
559
+ beforeRoutes?: (app: Express) => Promise<void> | void;
560
+ /** Called after all routes are registered, before error handling middleware */
561
+ afterRoutes?: (app: Express) => Promise<void> | void;
557
562
  /** Called after middleware & routes initialize */
558
563
  afterInit?: (server: ExpressServer) => Promise<void> | void;
564
+ /** Called when the underlying HTTP/HTTPS server instance is created */
565
+ onServerCreated?: (server: http.Server | https.Server) => Promise<void> | void;
559
566
  /** Called before server starts listening */
560
567
  beforeStart?: (app: Express) => Promise<void> | void;
561
568
  /** Called after server is ready */
@@ -575,7 +582,7 @@ interface GlobalServerAddons {
575
582
  /**
576
583
  * Skip healthz endpoint even if health checks are configured
577
584
  * - **default**: `false`
578
- * - **env**: `SERVER_SKIP_HEALTHZ`
585
+ * - **env**: `SERVER_SKIP_HEALTHZ_CHECKS_VALIDATION`
579
586
  *
580
587
  * @additionalInfo
581
588
  * Set to true to return `200 OK` for `/healthz` without checks
@@ -586,7 +593,7 @@ interface GlobalServerAddons {
586
593
  * Health checks can still be performed programmatically
587
594
  * or via other endpoints if needed
588
595
  */
589
- skipHealthz: boolean;
596
+ skipHealthzChecksValidation: boolean;
590
597
  }
591
598
  /** Combined global server configuration type */
592
599
  type CatbeeGlobalServerConfig = CatbeeServerConfig & GlobalServerAddons;
package/types/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * The MIT License
3
3
  *
4
- * Copyright (c) 2025 Catbee Technologies. https://catbee.in/license
4
+ * Copyright (c) 2026 Catbee Technologies. https://catbee.in/license
5
5
  *
6
6
  * Permission is hereby granted, free of charge, to any person obtaining a copy
7
7
  * of this software and associated documentation files (the "Software"), to deal
package/url/index.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * The MIT License
3
3
  *
4
- * Copyright (c) 2025 Catbee Technologies. https://catbee.in/license
4
+ * Copyright (c) 2026 Catbee Technologies. https://catbee.in/license
5
5
  *
6
6
  * Permission is hereby granted, free of charge, to any person obtaining a copy
7
7
  * of this software and associated documentation files (the "Software"), to deal
@@ -101,6 +101,16 @@ function normalizeUrl(url$1, base) {
101
101
  }
102
102
  }
103
103
  __name(normalizeUrl, "normalizeUrl");
104
+ function updateQueryParam(url$1, key, value) {
105
+ try {
106
+ const urlObj = new url.URL(url$1);
107
+ urlObj.searchParams.set(key, String(value));
108
+ return urlObj.toString();
109
+ } catch {
110
+ return url$1;
111
+ }
112
+ }
113
+ __name(updateQueryParam, "updateQueryParam");
104
114
  function createUrlBuilder(baseUrl) {
105
115
  return {
106
116
  /**
@@ -185,15 +195,65 @@ function parseTypedQueryParams(url$1, converters) {
185
195
  }
186
196
  }
187
197
  __name(parseTypedQueryParams, "parseTypedQueryParams");
198
+ function getSubdomain(url$1) {
199
+ try {
200
+ const { hostname } = new url.URL(url$1);
201
+ const parts = hostname.split(".");
202
+ if (parts.length <= 2) return "";
203
+ return parts.slice(0, -2).join(".");
204
+ } catch {
205
+ return "";
206
+ }
207
+ }
208
+ __name(getSubdomain, "getSubdomain");
209
+ function isRelativeUrl(url$1) {
210
+ if (!url$1 || typeof url$1 !== "string") return false;
211
+ try {
212
+ new url.URL(url$1);
213
+ return false;
214
+ } catch {
215
+ return url$1.startsWith("/") || url$1.startsWith("./") || url$1.startsWith("../");
216
+ }
217
+ }
218
+ __name(isRelativeUrl, "isRelativeUrl");
219
+ function toAbsoluteUrl(relativeUrl, baseUrl) {
220
+ try {
221
+ return new url.URL(relativeUrl, baseUrl).toString();
222
+ } catch {
223
+ return relativeUrl;
224
+ }
225
+ }
226
+ __name(toAbsoluteUrl, "toAbsoluteUrl");
227
+ function sanitizeUrl(url$1, allowedProtocols = [
228
+ "http",
229
+ "https"
230
+ ]) {
231
+ try {
232
+ const parsedUrl = new url.URL(url$1);
233
+ const protocol = parsedUrl.protocol.replace(":", "");
234
+ if (!allowedProtocols.includes(protocol)) {
235
+ return null;
236
+ }
237
+ return parsedUrl.toString();
238
+ } catch {
239
+ return null;
240
+ }
241
+ }
242
+ __name(sanitizeUrl, "sanitizeUrl");
188
243
 
189
244
  exports.appendQueryParams = appendQueryParams;
190
245
  exports.createUrlBuilder = createUrlBuilder;
191
246
  exports.extractQueryParams = extractQueryParams;
192
247
  exports.getDomain = getDomain;
193
248
  exports.getExtension = getExtension;
249
+ exports.getSubdomain = getSubdomain;
250
+ exports.isRelativeUrl = isRelativeUrl;
194
251
  exports.isValidUrl = isValidUrl;
195
252
  exports.joinPaths = joinPaths;
196
253
  exports.normalizeUrl = normalizeUrl;
197
254
  exports.parseQueryString = parseQueryString;
198
255
  exports.parseTypedQueryParams = parseTypedQueryParams;
199
256
  exports.removeQueryParams = removeQueryParams;
257
+ exports.sanitizeUrl = sanitizeUrl;
258
+ exports.toAbsoluteUrl = toAbsoluteUrl;
259
+ exports.updateQueryParam = updateQueryParam;
package/url/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * The MIT License
3
3
  *
4
- * Copyright (c) 2025 Catbee Technologies. https://catbee.in/license
4
+ * Copyright (c) 2026 Catbee Technologies. https://catbee.in/license
5
5
  *
6
6
  * Permission is hereby granted, free of charge, to any person obtaining a copy
7
7
  * of this software and associated documentation files (the "Software"), to deal
@@ -91,6 +91,18 @@ declare function joinPaths(...segments: string[]): string;
91
91
  * // -> 'http://example.com/bar'
92
92
  */
93
93
  declare function normalizeUrl(url: string, base?: string): string;
94
+ /**
95
+ * Updates or sets a single query parameter in a URL.
96
+ *
97
+ * @param {string} url - The source URL.
98
+ * @param {string} key - Parameter key.
99
+ * @param {string | number} value - Parameter value.
100
+ * @returns {string} URL with updated parameter.
101
+ *
102
+ * @example
103
+ * updateQueryParam('https://example.com?page=1', 'page', 2); // 'https://example.com?page=2'
104
+ */
105
+ declare function updateQueryParam(url: string, key: string, value: string | number): string;
94
106
  /**
95
107
  * Creates a URL builder for constructing URLs with a base URL.
96
108
  *
@@ -162,5 +174,50 @@ declare function getExtension(url: string): string;
162
174
  * // -> { page: 2, q: 'test' }
163
175
  */
164
176
  declare function parseTypedQueryParams<T extends Record<string, any>>(url: string, converters?: Record<keyof T, (val: string) => any>): Partial<T>;
177
+ /**
178
+ * Extracts the subdomain from a URL.
179
+ *
180
+ * @param {string} url - The URL.
181
+ * @returns {string} Subdomain or empty string.
182
+ *
183
+ * @example
184
+ * getSubdomain('https://api.example.com'); // 'api'
185
+ * getSubdomain('https://www.blog.example.com'); // 'www.blog'
186
+ */
187
+ declare function getSubdomain(url: string): string;
188
+ /**
189
+ * Checks if a URL is relative (not absolute).
190
+ *
191
+ * @param {string} url - The URL to check.
192
+ * @returns {boolean} True if relative.
193
+ *
194
+ * @example
195
+ * isRelativeUrl('/path/to/page'); // true
196
+ * isRelativeUrl('https://example.com/page'); // false
197
+ */
198
+ declare function isRelativeUrl(url: string): boolean;
199
+ /**
200
+ * Converts a relative URL to an absolute URL using a base URL.
201
+ *
202
+ * @param {string} relativeUrl - The relative URL.
203
+ * @param {string} baseUrl - The base URL.
204
+ * @returns {string} Absolute URL.
205
+ *
206
+ * @example
207
+ * toAbsoluteUrl('/api/users', 'https://example.com'); // 'https://example.com/api/users'
208
+ */
209
+ declare function toAbsoluteUrl(relativeUrl: string, baseUrl: string): string;
210
+ /**
211
+ * Sanitizes a URL by removing dangerous protocols and normalizing.
212
+ *
213
+ * @param {string} url - The URL to sanitize.
214
+ * @param {string[]} [allowedProtocols=['http', 'https']] - Allowed protocols.
215
+ * @returns {string | null} Sanitized URL or null if unsafe.
216
+ *
217
+ * @example
218
+ * sanitizeUrl('javascript:alert(1)'); // null
219
+ * sanitizeUrl('https://example.com'); // 'https://example.com/'
220
+ */
221
+ declare function sanitizeUrl(url: string, allowedProtocols?: string[]): string | null;
165
222
 
166
- export { appendQueryParams, createUrlBuilder, extractQueryParams, getDomain, getExtension, isValidUrl, joinPaths, normalizeUrl, parseQueryString, parseTypedQueryParams, removeQueryParams };
223
+ export { appendQueryParams, createUrlBuilder, extractQueryParams, getDomain, getExtension, getSubdomain, isRelativeUrl, isValidUrl, joinPaths, normalizeUrl, parseQueryString, parseTypedQueryParams, removeQueryParams, sanitizeUrl, toAbsoluteUrl, updateQueryParam };
package/url/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * The MIT License
3
3
  *
4
- * Copyright (c) 2025 Catbee Technologies. https://catbee.in/license
4
+ * Copyright (c) 2026 Catbee Technologies. https://catbee.in/license
5
5
  *
6
6
  * Permission is hereby granted, free of charge, to any person obtaining a copy
7
7
  * of this software and associated documentation files (the "Software"), to deal
@@ -99,6 +99,16 @@ function normalizeUrl(url, base) {
99
99
  }
100
100
  }
101
101
  __name(normalizeUrl, "normalizeUrl");
102
+ function updateQueryParam(url, key, value) {
103
+ try {
104
+ const urlObj = new URL(url);
105
+ urlObj.searchParams.set(key, String(value));
106
+ return urlObj.toString();
107
+ } catch {
108
+ return url;
109
+ }
110
+ }
111
+ __name(updateQueryParam, "updateQueryParam");
102
112
  function createUrlBuilder(baseUrl) {
103
113
  return {
104
114
  /**
@@ -183,5 +193,50 @@ function parseTypedQueryParams(url, converters) {
183
193
  }
184
194
  }
185
195
  __name(parseTypedQueryParams, "parseTypedQueryParams");
196
+ function getSubdomain(url) {
197
+ try {
198
+ const { hostname } = new URL(url);
199
+ const parts = hostname.split(".");
200
+ if (parts.length <= 2) return "";
201
+ return parts.slice(0, -2).join(".");
202
+ } catch {
203
+ return "";
204
+ }
205
+ }
206
+ __name(getSubdomain, "getSubdomain");
207
+ function isRelativeUrl(url) {
208
+ if (!url || typeof url !== "string") return false;
209
+ try {
210
+ new URL(url);
211
+ return false;
212
+ } catch {
213
+ return url.startsWith("/") || url.startsWith("./") || url.startsWith("../");
214
+ }
215
+ }
216
+ __name(isRelativeUrl, "isRelativeUrl");
217
+ function toAbsoluteUrl(relativeUrl, baseUrl) {
218
+ try {
219
+ return new URL(relativeUrl, baseUrl).toString();
220
+ } catch {
221
+ return relativeUrl;
222
+ }
223
+ }
224
+ __name(toAbsoluteUrl, "toAbsoluteUrl");
225
+ function sanitizeUrl(url, allowedProtocols = [
226
+ "http",
227
+ "https"
228
+ ]) {
229
+ try {
230
+ const parsedUrl = new URL(url);
231
+ const protocol = parsedUrl.protocol.replace(":", "");
232
+ if (!allowedProtocols.includes(protocol)) {
233
+ return null;
234
+ }
235
+ return parsedUrl.toString();
236
+ } catch {
237
+ return null;
238
+ }
239
+ }
240
+ __name(sanitizeUrl, "sanitizeUrl");
186
241
 
187
- export { appendQueryParams, createUrlBuilder, extractQueryParams, getDomain, getExtension, isValidUrl, joinPaths, normalizeUrl, parseQueryString, parseTypedQueryParams, removeQueryParams };
242
+ export { appendQueryParams, createUrlBuilder, extractQueryParams, getDomain, getExtension, getSubdomain, isRelativeUrl, isValidUrl, joinPaths, normalizeUrl, parseQueryString, parseTypedQueryParams, removeQueryParams, sanitizeUrl, toAbsoluteUrl, updateQueryParam };
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * The MIT License
3
3
  *
4
- * Copyright (c) 2025 Catbee Technologies. https://catbee.in/license
4
+ * Copyright (c) 2026 Catbee Technologies. https://catbee.in/license
5
5
  *
6
6
  * Permission is hereby granted, free of charge, to any person obtaining a copy
7
7
  * of this software and associated documentation files (the "Software"), to deal
@@ -68,7 +68,7 @@ function isEmail(str) {
68
68
  if (!labelRegex.test(label)) return false;
69
69
  if (label.startsWith("-") || label.endsWith("-")) return false;
70
70
  }
71
- const tld = labels[labels.length - 1];
71
+ const tld = labels.at(-1) || "";
72
72
  if (!TLD_REGEX.test(tld)) return false;
73
73
  return true;
74
74
  }
@@ -207,7 +207,7 @@ function isBase64(str) {
207
207
  __name(isBase64, "isBase64");
208
208
  function hasRequiredProps(obj, requiredProps) {
209
209
  if (!obj || typeof obj !== "object") return false;
210
- return requiredProps.every((prop) => Object.prototype.hasOwnProperty.call(obj, prop) && obj[prop] != null);
210
+ return requiredProps.every((prop) => Object.hasOwn(obj, prop) && obj[prop] != null);
211
211
  }
212
212
  __name(hasRequiredProps, "hasRequiredProps");
213
213
  function isDateInRange(date, minDate, maxDate) {
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * The MIT License
3
3
  *
4
- * Copyright (c) 2025 Catbee Technologies. https://catbee.in/license
4
+ * Copyright (c) 2026 Catbee Technologies. https://catbee.in/license
5
5
  *
6
6
  * Permission is hereby granted, free of charge, to any person obtaining a copy
7
7
  * of this software and associated documentation files (the "Software"), to deal
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * The MIT License
3
3
  *
4
- * Copyright (c) 2025 Catbee Technologies. https://catbee.in/license
4
+ * Copyright (c) 2026 Catbee Technologies. https://catbee.in/license
5
5
  *
6
6
  * Permission is hereby granted, free of charge, to any person obtaining a copy
7
7
  * of this software and associated documentation files (the "Software"), to deal
@@ -62,7 +62,7 @@ function isEmail(str) {
62
62
  if (!labelRegex.test(label)) return false;
63
63
  if (label.startsWith("-") || label.endsWith("-")) return false;
64
64
  }
65
- const tld = labels[labels.length - 1];
65
+ const tld = labels.at(-1) || "";
66
66
  if (!TLD_REGEX.test(tld)) return false;
67
67
  return true;
68
68
  }
@@ -201,7 +201,7 @@ function isBase64(str) {
201
201
  __name(isBase64, "isBase64");
202
202
  function hasRequiredProps(obj, requiredProps) {
203
203
  if (!obj || typeof obj !== "object") return false;
204
- return requiredProps.every((prop) => Object.prototype.hasOwnProperty.call(obj, prop) && obj[prop] != null);
204
+ return requiredProps.every((prop) => Object.hasOwn(obj, prop) && obj[prop] != null);
205
205
  }
206
206
  __name(hasRequiredProps, "hasRequiredProps");
207
207
  function isDateInRange(date, minDate, maxDate) {