@itshackers/utilix 0.0.0-stage → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +304 -3
  3. package/dist/common/index.cjs +102 -0
  4. package/dist/common/index.cjs.map +1 -0
  5. package/dist/common/index.d.cts +58 -0
  6. package/dist/common/index.d.ts +58 -0
  7. package/dist/common/index.js +93 -0
  8. package/dist/common/index.js.map +1 -0
  9. package/dist/date/index.cjs +154 -0
  10. package/dist/date/index.cjs.map +1 -0
  11. package/dist/date/index.d.cts +65 -0
  12. package/dist/date/index.d.ts +65 -0
  13. package/dist/date/index.js +146 -0
  14. package/dist/date/index.js.map +1 -0
  15. package/dist/format/index.cjs +136 -0
  16. package/dist/format/index.cjs.map +1 -0
  17. package/dist/format/index.d.cts +56 -0
  18. package/dist/format/index.d.ts +56 -0
  19. package/dist/format/index.js +130 -0
  20. package/dist/format/index.js.map +1 -0
  21. package/dist/id/index.cjs +66 -0
  22. package/dist/id/index.cjs.map +1 -0
  23. package/dist/id/index.d.cts +19 -0
  24. package/dist/id/index.d.ts +19 -0
  25. package/dist/id/index.js +64 -0
  26. package/dist/id/index.js.map +1 -0
  27. package/dist/index.cjs +906 -0
  28. package/dist/index.cjs.map +1 -0
  29. package/dist/index.d.cts +12 -0
  30. package/dist/index.d.ts +12 -0
  31. package/dist/index.js +853 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/number/index.cjs +107 -0
  34. package/dist/number/index.cjs.map +1 -0
  35. package/dist/number/index.d.cts +90 -0
  36. package/dist/number/index.d.ts +90 -0
  37. package/dist/number/index.js +99 -0
  38. package/dist/number/index.js.map +1 -0
  39. package/dist/password/index.cjs +71 -0
  40. package/dist/password/index.cjs.map +1 -0
  41. package/dist/password/index.d.cts +15 -0
  42. package/dist/password/index.d.ts +15 -0
  43. package/dist/password/index.js +69 -0
  44. package/dist/password/index.js.map +1 -0
  45. package/dist/phone/index.cjs +19 -0
  46. package/dist/phone/index.cjs.map +1 -0
  47. package/dist/phone/index.d.cts +12 -0
  48. package/dist/phone/index.d.ts +12 -0
  49. package/dist/phone/index.js +17 -0
  50. package/dist/phone/index.js.map +1 -0
  51. package/dist/regex/index.cjs +73 -0
  52. package/dist/regex/index.cjs.map +1 -0
  53. package/dist/regex/index.d.cts +65 -0
  54. package/dist/regex/index.d.ts +65 -0
  55. package/dist/regex/index.js +67 -0
  56. package/dist/regex/index.js.map +1 -0
  57. package/dist/response/index.cjs +43 -0
  58. package/dist/response/index.cjs.map +1 -0
  59. package/dist/response/index.d.cts +41 -0
  60. package/dist/response/index.d.ts +41 -0
  61. package/dist/response/index.js +40 -0
  62. package/dist/response/index.js.map +1 -0
  63. package/dist/sanitize/index.cjs +83 -0
  64. package/dist/sanitize/index.cjs.map +1 -0
  65. package/dist/sanitize/index.d.cts +20 -0
  66. package/dist/sanitize/index.d.ts +20 -0
  67. package/dist/sanitize/index.js +80 -0
  68. package/dist/sanitize/index.js.map +1 -0
  69. package/dist/slug/index.cjs +33 -0
  70. package/dist/slug/index.cjs.map +1 -0
  71. package/dist/slug/index.d.cts +38 -0
  72. package/dist/slug/index.d.ts +38 -0
  73. package/dist/slug/index.js +31 -0
  74. package/dist/slug/index.js.map +1 -0
  75. package/dist/string/index.cjs +130 -0
  76. package/dist/string/index.cjs.map +1 -0
  77. package/dist/string/index.d.cts +112 -0
  78. package/dist/string/index.d.ts +112 -0
  79. package/dist/string/index.js +115 -0
  80. package/dist/string/index.js.map +1 -0
  81. package/package.json +187 -3
@@ -0,0 +1,41 @@
1
+ interface SuccessResponse<T = unknown> {
2
+ success: true;
3
+ message?: string;
4
+ data?: T;
5
+ meta?: unknown;
6
+ request_id?: string;
7
+ }
8
+ interface ErrorResponse {
9
+ success: false;
10
+ message?: string;
11
+ code?: string | number;
12
+ errors?: unknown;
13
+ details?: unknown;
14
+ request_id?: string;
15
+ }
16
+ interface CreateSuccessResponseOptions<T> {
17
+ data?: T;
18
+ message?: string;
19
+ meta?: unknown;
20
+ requestId?: string;
21
+ }
22
+ interface CreateErrorResponseOptions {
23
+ message?: string;
24
+ code?: string | number;
25
+ errors?: unknown;
26
+ details?: unknown;
27
+ requestId?: string;
28
+ }
29
+ /**
30
+ * Builds a framework-independent success payload.
31
+ * This does not write HTTP headers or call Express `res.json`.
32
+ */
33
+ declare function createSuccessResponse<T>(options?: CreateSuccessResponseOptions<T>): SuccessResponse<T>;
34
+ /**
35
+ * Builds a framework-independent error payload.
36
+ * This does not write HTTP headers or call Express `res.json`.
37
+ */
38
+ declare function createErrorResponse(options?: CreateErrorResponseOptions): ErrorResponse;
39
+
40
+ export { createErrorResponse, createSuccessResponse };
41
+ export type { CreateErrorResponseOptions, CreateSuccessResponseOptions, ErrorResponse, SuccessResponse };
@@ -0,0 +1,41 @@
1
+ interface SuccessResponse<T = unknown> {
2
+ success: true;
3
+ message?: string;
4
+ data?: T;
5
+ meta?: unknown;
6
+ request_id?: string;
7
+ }
8
+ interface ErrorResponse {
9
+ success: false;
10
+ message?: string;
11
+ code?: string | number;
12
+ errors?: unknown;
13
+ details?: unknown;
14
+ request_id?: string;
15
+ }
16
+ interface CreateSuccessResponseOptions<T> {
17
+ data?: T;
18
+ message?: string;
19
+ meta?: unknown;
20
+ requestId?: string;
21
+ }
22
+ interface CreateErrorResponseOptions {
23
+ message?: string;
24
+ code?: string | number;
25
+ errors?: unknown;
26
+ details?: unknown;
27
+ requestId?: string;
28
+ }
29
+ /**
30
+ * Builds a framework-independent success payload.
31
+ * This does not write HTTP headers or call Express `res.json`.
32
+ */
33
+ declare function createSuccessResponse<T>(options?: CreateSuccessResponseOptions<T>): SuccessResponse<T>;
34
+ /**
35
+ * Builds a framework-independent error payload.
36
+ * This does not write HTTP headers or call Express `res.json`.
37
+ */
38
+ declare function createErrorResponse(options?: CreateErrorResponseOptions): ErrorResponse;
39
+
40
+ export { createErrorResponse, createSuccessResponse };
41
+ export type { CreateErrorResponseOptions, CreateSuccessResponseOptions, ErrorResponse, SuccessResponse };
@@ -0,0 +1,40 @@
1
+ // src/response/index.ts
2
+ function createSuccessResponse(options = {}) {
3
+ const response = { success: true };
4
+ if (options.message) {
5
+ response.message = options.message;
6
+ }
7
+ if (options.data !== void 0) {
8
+ response.data = options.data;
9
+ }
10
+ if (options.meta !== void 0) {
11
+ response.meta = options.meta;
12
+ }
13
+ if (options.requestId) {
14
+ response.request_id = options.requestId;
15
+ }
16
+ return response;
17
+ }
18
+ function createErrorResponse(options = {}) {
19
+ const response = { success: false };
20
+ if (options.message) {
21
+ response.message = options.message;
22
+ }
23
+ if (options.code !== void 0) {
24
+ response.code = options.code;
25
+ }
26
+ if (options.errors !== void 0) {
27
+ response.errors = options.errors;
28
+ }
29
+ if (options.details !== void 0) {
30
+ response.details = options.details;
31
+ }
32
+ if (options.requestId) {
33
+ response.request_id = options.requestId;
34
+ }
35
+ return response;
36
+ }
37
+
38
+ export { createErrorResponse, createSuccessResponse };
39
+ //# sourceMappingURL=index.js.map
40
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/response/index.ts"],"names":[],"mappings":";AAoCO,SAAS,qBAAA,CACd,OAAA,GAA2C,EAAC,EACxB;AACpB,EAAA,MAAM,QAAA,GAA+B,EAAE,OAAA,EAAS,IAAA,EAAK;AAErD,EAAA,IAAI,QAAQ,OAAA,EAAS;AACnB,IAAA,QAAA,CAAS,UAAU,OAAA,CAAQ,OAAA;AAAA,EAC7B;AACA,EAAA,IAAI,OAAA,CAAQ,SAAS,MAAA,EAAW;AAC9B,IAAA,QAAA,CAAS,OAAO,OAAA,CAAQ,IAAA;AAAA,EAC1B;AACA,EAAA,IAAI,OAAA,CAAQ,SAAS,MAAA,EAAW;AAC9B,IAAA,QAAA,CAAS,OAAO,OAAA,CAAQ,IAAA;AAAA,EAC1B;AACA,EAAA,IAAI,QAAQ,SAAA,EAAW;AACrB,IAAA,QAAA,CAAS,aAAa,OAAA,CAAQ,SAAA;AAAA,EAChC;AAEA,EAAA,OAAO,QAAA;AACT;AAMO,SAAS,mBAAA,CAAoB,OAAA,GAAsC,EAAC,EAAkB;AAC3F,EAAA,MAAM,QAAA,GAA0B,EAAE,OAAA,EAAS,KAAA,EAAM;AAEjD,EAAA,IAAI,QAAQ,OAAA,EAAS;AACnB,IAAA,QAAA,CAAS,UAAU,OAAA,CAAQ,OAAA;AAAA,EAC7B;AACA,EAAA,IAAI,OAAA,CAAQ,SAAS,MAAA,EAAW;AAC9B,IAAA,QAAA,CAAS,OAAO,OAAA,CAAQ,IAAA;AAAA,EAC1B;AACA,EAAA,IAAI,OAAA,CAAQ,WAAW,MAAA,EAAW;AAChC,IAAA,QAAA,CAAS,SAAS,OAAA,CAAQ,MAAA;AAAA,EAC5B;AACA,EAAA,IAAI,OAAA,CAAQ,YAAY,MAAA,EAAW;AACjC,IAAA,QAAA,CAAS,UAAU,OAAA,CAAQ,OAAA;AAAA,EAC7B;AACA,EAAA,IAAI,QAAQ,SAAA,EAAW;AACrB,IAAA,QAAA,CAAS,aAAa,OAAA,CAAQ,SAAA;AAAA,EAChC;AAEA,EAAA,OAAO,QAAA;AACT","file":"index.js","sourcesContent":["export interface SuccessResponse<T = unknown> {\r\n success: true;\r\n message?: string;\r\n data?: T;\r\n meta?: unknown;\r\n request_id?: string;\r\n}\r\n\r\nexport interface ErrorResponse {\r\n success: false;\r\n message?: string;\r\n code?: string | number;\r\n errors?: unknown;\r\n details?: unknown;\r\n request_id?: string;\r\n}\r\n\r\nexport interface CreateSuccessResponseOptions<T> {\r\n data?: T;\r\n message?: string;\r\n meta?: unknown;\r\n requestId?: string;\r\n}\r\n\r\nexport interface CreateErrorResponseOptions {\r\n message?: string;\r\n code?: string | number;\r\n errors?: unknown;\r\n details?: unknown;\r\n requestId?: string;\r\n}\r\n\r\n/**\r\n * Builds a framework-independent success payload.\r\n * This does not write HTTP headers or call Express `res.json`.\r\n */\r\nexport function createSuccessResponse<T>(\r\n options: CreateSuccessResponseOptions<T> = {},\r\n): SuccessResponse<T> {\r\n const response: SuccessResponse<T> = { success: true };\r\n\r\n if (options.message) {\r\n response.message = options.message;\r\n }\r\n if (options.data !== undefined) {\r\n response.data = options.data;\r\n }\r\n if (options.meta !== undefined) {\r\n response.meta = options.meta;\r\n }\r\n if (options.requestId) {\r\n response.request_id = options.requestId;\r\n }\r\n\r\n return response;\r\n}\r\n\r\n/**\r\n * Builds a framework-independent error payload.\r\n * This does not write HTTP headers or call Express `res.json`.\r\n */\r\nexport function createErrorResponse(options: CreateErrorResponseOptions = {}): ErrorResponse {\r\n const response: ErrorResponse = { success: false };\r\n\r\n if (options.message) {\r\n response.message = options.message;\r\n }\r\n if (options.code !== undefined) {\r\n response.code = options.code;\r\n }\r\n if (options.errors !== undefined) {\r\n response.errors = options.errors;\r\n }\r\n if (options.details !== undefined) {\r\n response.details = options.details;\r\n }\r\n if (options.requestId) {\r\n response.request_id = options.requestId;\r\n }\r\n\r\n return response;\r\n}\r\n"]}
@@ -0,0 +1,83 @@
1
+ 'use strict';
2
+
3
+ // src/common/index.ts
4
+ function isPlainObject(value) {
5
+ if (value === null || typeof value !== "object") {
6
+ return false;
7
+ }
8
+ const prototype = Object.getPrototypeOf(value);
9
+ return prototype === null || prototype === Object.prototype;
10
+ }
11
+
12
+ // src/sanitize/index.ts
13
+ var DEFAULT_SENSITIVE_KEYS = [
14
+ "authorization",
15
+ "password",
16
+ "client_secret",
17
+ "otp",
18
+ "otp_hash",
19
+ "api_key",
20
+ "x-api-key",
21
+ "token",
22
+ "access_token",
23
+ "refresh_token",
24
+ "jwt",
25
+ "secret"
26
+ ];
27
+ function resolveOptions(options = {}) {
28
+ return {
29
+ sensitiveKeys: new Set(
30
+ (options.sensitiveKeys ?? DEFAULT_SENSITIVE_KEYS).map((key) => key.toLowerCase())
31
+ ),
32
+ maxDepth: options.maxDepth ?? 6,
33
+ maxString: options.maxString ?? 2e3,
34
+ maxArray: options.maxArray ?? 50,
35
+ redactedValue: options.redactedValue ?? "[REDACTED]",
36
+ truncatedValue: options.truncatedValue ?? "[truncated]"
37
+ };
38
+ }
39
+ function sanitizeForLog(value, options = {}, depth = 0) {
40
+ const config = resolveOptions(options);
41
+ if (depth > config.maxDepth) {
42
+ return config.truncatedValue;
43
+ }
44
+ if (value == null || typeof value === "number" || typeof value === "boolean") {
45
+ return value;
46
+ }
47
+ if (typeof value === "string") {
48
+ return value.length > config.maxString ? `${value.slice(0, config.maxString)}\u2026` : value;
49
+ }
50
+ if (Array.isArray(value)) {
51
+ return value.slice(0, config.maxArray).map((item) => sanitizeForLog(item, options, depth + 1));
52
+ }
53
+ if (isPlainObject(value)) {
54
+ const output = {};
55
+ for (const [key, nested] of Object.entries(value)) {
56
+ output[key] = config.sensitiveKeys.has(key.toLowerCase()) ? config.redactedValue : sanitizeForLog(nested, options, depth + 1);
57
+ }
58
+ return output;
59
+ }
60
+ if (value instanceof Date) {
61
+ return value.toISOString();
62
+ }
63
+ return String(value);
64
+ }
65
+ function sanitizeHeaders(headers, options = {}) {
66
+ const config = resolveOptions(options);
67
+ const output = {};
68
+ for (const [key, value] of Object.entries(headers)) {
69
+ if (config.sensitiveKeys.has(key.toLowerCase())) {
70
+ output[key] = config.redactedValue;
71
+ } else if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
72
+ output[key] = value;
73
+ } else if (Array.isArray(value)) {
74
+ output[key] = value.map(String);
75
+ }
76
+ }
77
+ return output;
78
+ }
79
+
80
+ exports.sanitizeForLog = sanitizeForLog;
81
+ exports.sanitizeHeaders = sanitizeHeaders;
82
+ //# sourceMappingURL=index.cjs.map
83
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/common/index.ts","../../src/sanitize/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import { assertFiniteNumber } from \"../internal/validation.js\";\r\n\r\n/**\r\n * Returns `true` when the value is a plain object created by `{}`,\r\n * `Object.create(null)`, or `new Object()`. Arrays, `null`, and class\r\n * instances are rejected.\r\n */\r\nexport function isPlainObject(value: unknown): value is Record<string, unknown> {\r\n if (value === null || typeof value !== \"object\") {\r\n return false;\r\n }\r\n\r\n const prototype = Object.getPrototypeOf(value);\r\n return prototype === null || prototype === Object.prototype;\r\n}\r\n\r\n/**\r\n * Returns `true` for `null`, `undefined`, and whitespace-only strings.\r\n * `0` and `false` are not treated as empty.\r\n */\r\nexport function isNilOrEmpty(value: unknown): boolean {\r\n if (value == null) {\r\n return true;\r\n }\r\n\r\n if (typeof value === \"string\") {\r\n return value.trim().length === 0;\r\n }\r\n\r\n return false;\r\n}\r\n\r\n/**\r\n * Returns `value` unless it is `null` or `undefined`, in which case\r\n * `fallback` is returned.\r\n */\r\nexport function coalesce<T>(value: T | null | undefined, fallback: T): T {\r\n return value ?? fallback;\r\n}\r\n\r\n/**\r\n * Coerces common truthy/falsey inputs to a boolean.\r\n * `null`, `undefined`, and empty or whitespace-only strings return `fallback`.\r\n *\r\n * @example\r\n * toBoolean(\"true\") // true\r\n * toBoolean(\"0\") // false\r\n * toBoolean(null, true) // true\r\n */\r\nexport function toBoolean(value: unknown, fallback = false): boolean {\r\n if (value === true || value === 1 || value === \"1\") {\r\n return true;\r\n }\r\n\r\n if (value === false || value === 0 || value === \"0\") {\r\n return false;\r\n }\r\n\r\n if (typeof value === \"string\") {\r\n const normalized = value.trim().toLowerCase();\r\n if (normalized.length === 0) {\r\n return fallback;\r\n }\r\n if (normalized === \"true\") {\r\n return true;\r\n }\r\n if (normalized === \"false\") {\r\n return false;\r\n }\r\n }\r\n\r\n if (value == null) {\r\n return fallback;\r\n }\r\n\r\n return fallback;\r\n}\r\n\r\n/**\r\n * Recursively trims strings in arrays and plain objects.\r\n * Dates, class instances, and other non-plain objects are left unchanged.\r\n *\r\n * @example\r\n * deepTrim({ name: \" Gold \", tags: [\" rsa \"] })\r\n * // { name: \"Gold\", tags: [\"rsa\"] }\r\n */\r\nexport function deepTrim<T>(value: T): T {\r\n if (typeof value === \"string\") {\r\n return value.trim() as T;\r\n }\r\n\r\n if (Array.isArray(value)) {\r\n return value.map((item) => deepTrim(item)) as T;\r\n }\r\n\r\n if (isPlainObject(value)) {\r\n return Object.fromEntries(\r\n Object.entries(value).map(([key, nested]) => [key, deepTrim(nested)]),\r\n ) as T;\r\n }\r\n\r\n return value;\r\n}\r\n\r\n/**\r\n * Returns a shallow copy of a plain object without `null` or empty-string values.\r\n * Non-plain values are returned unchanged.\r\n *\r\n * @example\r\n * omitEmpty({ name: \"Gold\", note: \"\", city: null })\r\n * // { name: \"Gold\" }\r\n */\r\nexport function omitEmpty<T>(value: T): T {\r\n if (!isPlainObject(value)) {\r\n return value;\r\n }\r\n\r\n const cleaned = { ...value };\r\n for (const key of Object.keys(cleaned)) {\r\n if (cleaned[key] === \"\" || cleaned[key] === null) {\r\n delete cleaned[key];\r\n }\r\n }\r\n\r\n return cleaned as T;\r\n}\r\n\r\n/**\r\n * Returns `value` when it is one of the allowed values, otherwise `fallback`.\r\n *\r\n * @example\r\n * pickAllowed(\"hi\", [\"en\", \"hi\"] as const, \"en\") // \"hi\"\r\n * pickAllowed(\"fr\", [\"en\", \"hi\"] as const, \"en\") // \"en\"\r\n */\r\nexport function pickAllowed<T>(\r\n value: unknown,\r\n allowed: readonly T[],\r\n fallback: T,\r\n): T {\r\n return allowed.includes(value as T) ? (value as T) : fallback;\r\n}\r\n\r\n/**\r\n * Resolves after the given number of milliseconds.\r\n */\r\nexport function sleep(milliseconds: number): Promise<void> {\r\n assertFiniteNumber(milliseconds, \"milliseconds\");\r\n\r\n if (milliseconds < 0) {\r\n throw new RangeError(\"milliseconds must be greater than or equal to 0\");\r\n }\r\n\r\n return new Promise((resolve) => {\r\n setTimeout(resolve, milliseconds);\r\n });\r\n}\r\n","import { isPlainObject } from \"../common/index.js\";\r\n\r\nconst DEFAULT_SENSITIVE_KEYS = [\r\n \"authorization\",\r\n \"password\",\r\n \"client_secret\",\r\n \"otp\",\r\n \"otp_hash\",\r\n \"api_key\",\r\n \"x-api-key\",\r\n \"token\",\r\n \"access_token\",\r\n \"refresh_token\",\r\n \"jwt\",\r\n \"secret\",\r\n] as const;\r\n\r\nexport interface SanitizeOptions {\r\n sensitiveKeys?: readonly string[];\r\n maxDepth?: number;\r\n maxString?: number;\r\n maxArray?: number;\r\n redactedValue?: string;\r\n truncatedValue?: string;\r\n}\r\n\r\nfunction resolveOptions(options: SanitizeOptions = {}) {\r\n return {\r\n sensitiveKeys: new Set(\r\n (options.sensitiveKeys ?? DEFAULT_SENSITIVE_KEYS).map((key) => key.toLowerCase()),\r\n ),\r\n maxDepth: options.maxDepth ?? 6,\r\n maxString: options.maxString ?? 2_000,\r\n maxArray: options.maxArray ?? 50,\r\n redactedValue: options.redactedValue ?? \"[REDACTED]\",\r\n truncatedValue: options.truncatedValue ?? \"[truncated]\",\r\n };\r\n}\r\n\r\n/**\r\n * Deep-clones a value and redacts configured secret keys for safe logging.\r\n * Arrays, string length, and recursion depth are truncated.\r\n */\r\nexport function sanitizeForLog(value: unknown, options: SanitizeOptions = {}, depth = 0): unknown {\r\n const config = resolveOptions(options);\r\n if (depth > config.maxDepth) {\r\n return config.truncatedValue;\r\n }\r\n\r\n if (value == null || typeof value === \"number\" || typeof value === \"boolean\") {\r\n return value;\r\n }\r\n\r\n if (typeof value === \"string\") {\r\n return value.length > config.maxString ? `${value.slice(0, config.maxString)}…` : value;\r\n }\r\n\r\n if (Array.isArray(value)) {\r\n return value\r\n .slice(0, config.maxArray)\r\n .map((item) => sanitizeForLog(item, options, depth + 1));\r\n }\r\n\r\n if (isPlainObject(value)) {\r\n const output: Record<string, unknown> = {};\r\n for (const [key, nested] of Object.entries(value)) {\r\n output[key] = config.sensitiveKeys.has(key.toLowerCase())\r\n ? config.redactedValue\r\n : sanitizeForLog(nested, options, depth + 1);\r\n }\r\n return output;\r\n }\r\n\r\n if (value instanceof Date) {\r\n return value.toISOString();\r\n }\r\n\r\n return String(value);\r\n}\r\n\r\n/**\r\n * Returns a shallow copy of headers with secret keys redacted.\r\n */\r\nexport function sanitizeHeaders(\r\n headers: Record<string, unknown>,\r\n options: SanitizeOptions = {},\r\n): Record<string, unknown> {\r\n const config = resolveOptions(options);\r\n const output: Record<string, unknown> = {};\r\n\r\n for (const [key, value] of Object.entries(headers)) {\r\n if (config.sensitiveKeys.has(key.toLowerCase())) {\r\n output[key] = config.redactedValue;\r\n } else if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\r\n output[key] = value;\r\n } else if (Array.isArray(value)) {\r\n output[key] = value.map(String);\r\n }\r\n }\r\n\r\n return output;\r\n}\r\n"]}
@@ -0,0 +1,20 @@
1
+ interface SanitizeOptions {
2
+ sensitiveKeys?: readonly string[];
3
+ maxDepth?: number;
4
+ maxString?: number;
5
+ maxArray?: number;
6
+ redactedValue?: string;
7
+ truncatedValue?: string;
8
+ }
9
+ /**
10
+ * Deep-clones a value and redacts configured secret keys for safe logging.
11
+ * Arrays, string length, and recursion depth are truncated.
12
+ */
13
+ declare function sanitizeForLog(value: unknown, options?: SanitizeOptions, depth?: number): unknown;
14
+ /**
15
+ * Returns a shallow copy of headers with secret keys redacted.
16
+ */
17
+ declare function sanitizeHeaders(headers: Record<string, unknown>, options?: SanitizeOptions): Record<string, unknown>;
18
+
19
+ export { sanitizeForLog, sanitizeHeaders };
20
+ export type { SanitizeOptions };
@@ -0,0 +1,20 @@
1
+ interface SanitizeOptions {
2
+ sensitiveKeys?: readonly string[];
3
+ maxDepth?: number;
4
+ maxString?: number;
5
+ maxArray?: number;
6
+ redactedValue?: string;
7
+ truncatedValue?: string;
8
+ }
9
+ /**
10
+ * Deep-clones a value and redacts configured secret keys for safe logging.
11
+ * Arrays, string length, and recursion depth are truncated.
12
+ */
13
+ declare function sanitizeForLog(value: unknown, options?: SanitizeOptions, depth?: number): unknown;
14
+ /**
15
+ * Returns a shallow copy of headers with secret keys redacted.
16
+ */
17
+ declare function sanitizeHeaders(headers: Record<string, unknown>, options?: SanitizeOptions): Record<string, unknown>;
18
+
19
+ export { sanitizeForLog, sanitizeHeaders };
20
+ export type { SanitizeOptions };
@@ -0,0 +1,80 @@
1
+ // src/common/index.ts
2
+ function isPlainObject(value) {
3
+ if (value === null || typeof value !== "object") {
4
+ return false;
5
+ }
6
+ const prototype = Object.getPrototypeOf(value);
7
+ return prototype === null || prototype === Object.prototype;
8
+ }
9
+
10
+ // src/sanitize/index.ts
11
+ var DEFAULT_SENSITIVE_KEYS = [
12
+ "authorization",
13
+ "password",
14
+ "client_secret",
15
+ "otp",
16
+ "otp_hash",
17
+ "api_key",
18
+ "x-api-key",
19
+ "token",
20
+ "access_token",
21
+ "refresh_token",
22
+ "jwt",
23
+ "secret"
24
+ ];
25
+ function resolveOptions(options = {}) {
26
+ return {
27
+ sensitiveKeys: new Set(
28
+ (options.sensitiveKeys ?? DEFAULT_SENSITIVE_KEYS).map((key) => key.toLowerCase())
29
+ ),
30
+ maxDepth: options.maxDepth ?? 6,
31
+ maxString: options.maxString ?? 2e3,
32
+ maxArray: options.maxArray ?? 50,
33
+ redactedValue: options.redactedValue ?? "[REDACTED]",
34
+ truncatedValue: options.truncatedValue ?? "[truncated]"
35
+ };
36
+ }
37
+ function sanitizeForLog(value, options = {}, depth = 0) {
38
+ const config = resolveOptions(options);
39
+ if (depth > config.maxDepth) {
40
+ return config.truncatedValue;
41
+ }
42
+ if (value == null || typeof value === "number" || typeof value === "boolean") {
43
+ return value;
44
+ }
45
+ if (typeof value === "string") {
46
+ return value.length > config.maxString ? `${value.slice(0, config.maxString)}\u2026` : value;
47
+ }
48
+ if (Array.isArray(value)) {
49
+ return value.slice(0, config.maxArray).map((item) => sanitizeForLog(item, options, depth + 1));
50
+ }
51
+ if (isPlainObject(value)) {
52
+ const output = {};
53
+ for (const [key, nested] of Object.entries(value)) {
54
+ output[key] = config.sensitiveKeys.has(key.toLowerCase()) ? config.redactedValue : sanitizeForLog(nested, options, depth + 1);
55
+ }
56
+ return output;
57
+ }
58
+ if (value instanceof Date) {
59
+ return value.toISOString();
60
+ }
61
+ return String(value);
62
+ }
63
+ function sanitizeHeaders(headers, options = {}) {
64
+ const config = resolveOptions(options);
65
+ const output = {};
66
+ for (const [key, value] of Object.entries(headers)) {
67
+ if (config.sensitiveKeys.has(key.toLowerCase())) {
68
+ output[key] = config.redactedValue;
69
+ } else if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
70
+ output[key] = value;
71
+ } else if (Array.isArray(value)) {
72
+ output[key] = value.map(String);
73
+ }
74
+ }
75
+ return output;
76
+ }
77
+
78
+ export { sanitizeForLog, sanitizeHeaders };
79
+ //# sourceMappingURL=index.js.map
80
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/common/index.ts","../../src/sanitize/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { assertFiniteNumber } from \"../internal/validation.js\";\r\n\r\n/**\r\n * Returns `true` when the value is a plain object created by `{}`,\r\n * `Object.create(null)`, or `new Object()`. Arrays, `null`, and class\r\n * instances are rejected.\r\n */\r\nexport function isPlainObject(value: unknown): value is Record<string, unknown> {\r\n if (value === null || typeof value !== \"object\") {\r\n return false;\r\n }\r\n\r\n const prototype = Object.getPrototypeOf(value);\r\n return prototype === null || prototype === Object.prototype;\r\n}\r\n\r\n/**\r\n * Returns `true` for `null`, `undefined`, and whitespace-only strings.\r\n * `0` and `false` are not treated as empty.\r\n */\r\nexport function isNilOrEmpty(value: unknown): boolean {\r\n if (value == null) {\r\n return true;\r\n }\r\n\r\n if (typeof value === \"string\") {\r\n return value.trim().length === 0;\r\n }\r\n\r\n return false;\r\n}\r\n\r\n/**\r\n * Returns `value` unless it is `null` or `undefined`, in which case\r\n * `fallback` is returned.\r\n */\r\nexport function coalesce<T>(value: T | null | undefined, fallback: T): T {\r\n return value ?? fallback;\r\n}\r\n\r\n/**\r\n * Coerces common truthy/falsey inputs to a boolean.\r\n * `null`, `undefined`, and empty or whitespace-only strings return `fallback`.\r\n *\r\n * @example\r\n * toBoolean(\"true\") // true\r\n * toBoolean(\"0\") // false\r\n * toBoolean(null, true) // true\r\n */\r\nexport function toBoolean(value: unknown, fallback = false): boolean {\r\n if (value === true || value === 1 || value === \"1\") {\r\n return true;\r\n }\r\n\r\n if (value === false || value === 0 || value === \"0\") {\r\n return false;\r\n }\r\n\r\n if (typeof value === \"string\") {\r\n const normalized = value.trim().toLowerCase();\r\n if (normalized.length === 0) {\r\n return fallback;\r\n }\r\n if (normalized === \"true\") {\r\n return true;\r\n }\r\n if (normalized === \"false\") {\r\n return false;\r\n }\r\n }\r\n\r\n if (value == null) {\r\n return fallback;\r\n }\r\n\r\n return fallback;\r\n}\r\n\r\n/**\r\n * Recursively trims strings in arrays and plain objects.\r\n * Dates, class instances, and other non-plain objects are left unchanged.\r\n *\r\n * @example\r\n * deepTrim({ name: \" Gold \", tags: [\" rsa \"] })\r\n * // { name: \"Gold\", tags: [\"rsa\"] }\r\n */\r\nexport function deepTrim<T>(value: T): T {\r\n if (typeof value === \"string\") {\r\n return value.trim() as T;\r\n }\r\n\r\n if (Array.isArray(value)) {\r\n return value.map((item) => deepTrim(item)) as T;\r\n }\r\n\r\n if (isPlainObject(value)) {\r\n return Object.fromEntries(\r\n Object.entries(value).map(([key, nested]) => [key, deepTrim(nested)]),\r\n ) as T;\r\n }\r\n\r\n return value;\r\n}\r\n\r\n/**\r\n * Returns a shallow copy of a plain object without `null` or empty-string values.\r\n * Non-plain values are returned unchanged.\r\n *\r\n * @example\r\n * omitEmpty({ name: \"Gold\", note: \"\", city: null })\r\n * // { name: \"Gold\" }\r\n */\r\nexport function omitEmpty<T>(value: T): T {\r\n if (!isPlainObject(value)) {\r\n return value;\r\n }\r\n\r\n const cleaned = { ...value };\r\n for (const key of Object.keys(cleaned)) {\r\n if (cleaned[key] === \"\" || cleaned[key] === null) {\r\n delete cleaned[key];\r\n }\r\n }\r\n\r\n return cleaned as T;\r\n}\r\n\r\n/**\r\n * Returns `value` when it is one of the allowed values, otherwise `fallback`.\r\n *\r\n * @example\r\n * pickAllowed(\"hi\", [\"en\", \"hi\"] as const, \"en\") // \"hi\"\r\n * pickAllowed(\"fr\", [\"en\", \"hi\"] as const, \"en\") // \"en\"\r\n */\r\nexport function pickAllowed<T>(\r\n value: unknown,\r\n allowed: readonly T[],\r\n fallback: T,\r\n): T {\r\n return allowed.includes(value as T) ? (value as T) : fallback;\r\n}\r\n\r\n/**\r\n * Resolves after the given number of milliseconds.\r\n */\r\nexport function sleep(milliseconds: number): Promise<void> {\r\n assertFiniteNumber(milliseconds, \"milliseconds\");\r\n\r\n if (milliseconds < 0) {\r\n throw new RangeError(\"milliseconds must be greater than or equal to 0\");\r\n }\r\n\r\n return new Promise((resolve) => {\r\n setTimeout(resolve, milliseconds);\r\n });\r\n}\r\n","import { isPlainObject } from \"../common/index.js\";\r\n\r\nconst DEFAULT_SENSITIVE_KEYS = [\r\n \"authorization\",\r\n \"password\",\r\n \"client_secret\",\r\n \"otp\",\r\n \"otp_hash\",\r\n \"api_key\",\r\n \"x-api-key\",\r\n \"token\",\r\n \"access_token\",\r\n \"refresh_token\",\r\n \"jwt\",\r\n \"secret\",\r\n] as const;\r\n\r\nexport interface SanitizeOptions {\r\n sensitiveKeys?: readonly string[];\r\n maxDepth?: number;\r\n maxString?: number;\r\n maxArray?: number;\r\n redactedValue?: string;\r\n truncatedValue?: string;\r\n}\r\n\r\nfunction resolveOptions(options: SanitizeOptions = {}) {\r\n return {\r\n sensitiveKeys: new Set(\r\n (options.sensitiveKeys ?? DEFAULT_SENSITIVE_KEYS).map((key) => key.toLowerCase()),\r\n ),\r\n maxDepth: options.maxDepth ?? 6,\r\n maxString: options.maxString ?? 2_000,\r\n maxArray: options.maxArray ?? 50,\r\n redactedValue: options.redactedValue ?? \"[REDACTED]\",\r\n truncatedValue: options.truncatedValue ?? \"[truncated]\",\r\n };\r\n}\r\n\r\n/**\r\n * Deep-clones a value and redacts configured secret keys for safe logging.\r\n * Arrays, string length, and recursion depth are truncated.\r\n */\r\nexport function sanitizeForLog(value: unknown, options: SanitizeOptions = {}, depth = 0): unknown {\r\n const config = resolveOptions(options);\r\n if (depth > config.maxDepth) {\r\n return config.truncatedValue;\r\n }\r\n\r\n if (value == null || typeof value === \"number\" || typeof value === \"boolean\") {\r\n return value;\r\n }\r\n\r\n if (typeof value === \"string\") {\r\n return value.length > config.maxString ? `${value.slice(0, config.maxString)}…` : value;\r\n }\r\n\r\n if (Array.isArray(value)) {\r\n return value\r\n .slice(0, config.maxArray)\r\n .map((item) => sanitizeForLog(item, options, depth + 1));\r\n }\r\n\r\n if (isPlainObject(value)) {\r\n const output: Record<string, unknown> = {};\r\n for (const [key, nested] of Object.entries(value)) {\r\n output[key] = config.sensitiveKeys.has(key.toLowerCase())\r\n ? config.redactedValue\r\n : sanitizeForLog(nested, options, depth + 1);\r\n }\r\n return output;\r\n }\r\n\r\n if (value instanceof Date) {\r\n return value.toISOString();\r\n }\r\n\r\n return String(value);\r\n}\r\n\r\n/**\r\n * Returns a shallow copy of headers with secret keys redacted.\r\n */\r\nexport function sanitizeHeaders(\r\n headers: Record<string, unknown>,\r\n options: SanitizeOptions = {},\r\n): Record<string, unknown> {\r\n const config = resolveOptions(options);\r\n const output: Record<string, unknown> = {};\r\n\r\n for (const [key, value] of Object.entries(headers)) {\r\n if (config.sensitiveKeys.has(key.toLowerCase())) {\r\n output[key] = config.redactedValue;\r\n } else if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\r\n output[key] = value;\r\n } else if (Array.isArray(value)) {\r\n output[key] = value.map(String);\r\n }\r\n }\r\n\r\n return output;\r\n}\r\n"]}
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ // src/internal/validation.ts
4
+ function escapeRegExp(value) {
5
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6
+ }
7
+
8
+ // src/slug/index.ts
9
+ function slugify(value, options = {}) {
10
+ const separator = options.separator ?? "-";
11
+ const preserveUnicode = options.preserveUnicode ?? true;
12
+ const { maxLength } = options;
13
+ if (typeof separator !== "string" || separator.length === 0 || /\s/u.test(separator)) {
14
+ throw new TypeError("separator must be a non-empty string without whitespace");
15
+ }
16
+ if (options.preserveUnicode !== void 0 && typeof options.preserveUnicode !== "boolean") {
17
+ throw new TypeError("preserveUnicode must be a boolean");
18
+ }
19
+ if (maxLength !== void 0 && (!Number.isInteger(maxLength) || maxLength <= 0)) {
20
+ throw new RangeError("maxLength must be a positive integer");
21
+ }
22
+ const escapedSeparator = escapeRegExp(separator);
23
+ const disallowedPattern = preserveUnicode ? /[^\p{L}\p{N}]+/gu : /[^a-z0-9]+/g;
24
+ let slug = value.normalize("NFD").replace(/\p{M}/gu, "").toLowerCase().replace(disallowedPattern, separator).replace(new RegExp(`${escapedSeparator}+`, "g"), separator).replace(new RegExp(`^${escapedSeparator}|${escapedSeparator}$`, "g"), "");
25
+ if (maxLength !== void 0 && slug.length > maxLength) {
26
+ slug = slug.slice(0, maxLength).replace(new RegExp(`${escapedSeparator}+$`, "g"), "");
27
+ }
28
+ return slug;
29
+ }
30
+
31
+ exports.slugify = slugify;
32
+ //# sourceMappingURL=index.cjs.map
33
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/internal/validation.ts","../../src/slug/index.ts"],"names":[],"mappings":";;;AASO,SAAS,aAAa,KAAA,EAAuB;AAClD,EAAA,OAAO,KAAA,CAAM,OAAA,CAAQ,qBAAA,EAAuB,MAAM,CAAA;AACpD;;;AC0BO,SAAS,OAAA,CAAQ,KAAA,EAAe,OAAA,GAA0B,EAAC,EAAW;AAC3E,EAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,GAAA;AACvC,EAAA,MAAM,eAAA,GAAkB,QAAQ,eAAA,IAAmB,IAAA;AACnD,EAAA,MAAM,EAAE,WAAU,GAAI,OAAA;AAEtB,EAAA,IAAI,OAAO,cAAc,QAAA,IAAY,SAAA,CAAU,WAAW,CAAA,IAAK,KAAA,CAAM,IAAA,CAAK,SAAS,CAAA,EAAG;AACpF,IAAA,MAAM,IAAI,UAAU,yDAAyD,CAAA;AAAA,EAC/E;AAEA,EAAA,IAAI,QAAQ,eAAA,KAAoB,MAAA,IAAa,OAAO,OAAA,CAAQ,oBAAoB,SAAA,EAAW;AACzF,IAAA,MAAM,IAAI,UAAU,mCAAmC,CAAA;AAAA,EACzD;AAEA,EAAA,IAAI,SAAA,KAAc,WAAc,CAAC,MAAA,CAAO,UAAU,SAAS,CAAA,IAAK,aAAa,CAAA,CAAA,EAAI;AAC/E,IAAA,MAAM,IAAI,WAAW,sCAAsC,CAAA;AAAA,EAC7D;AAEA,EAAA,MAAM,gBAAA,GAAmB,aAAa,SAAS,CAAA;AAC/C,EAAA,MAAM,iBAAA,GAAoB,kBAAkB,kBAAA,GAAqB,aAAA;AAEjE,EAAA,IAAI,IAAA,GAAO,KAAA,CACR,SAAA,CAAU,KAAK,EACf,OAAA,CAAQ,SAAA,EAAW,EAAE,CAAA,CACrB,aAAY,CACZ,OAAA,CAAQ,iBAAA,EAAmB,SAAS,EACpC,OAAA,CAAQ,IAAI,MAAA,CAAO,CAAA,EAAG,gBAAgB,CAAA,CAAA,CAAA,EAAK,GAAG,CAAA,EAAG,SAAS,EAC1D,OAAA,CAAQ,IAAI,MAAA,CAAO,CAAA,CAAA,EAAI,gBAAgB,CAAA,CAAA,EAAI,gBAAgB,CAAA,CAAA,CAAA,EAAK,GAAG,GAAG,EAAE,CAAA;AAE3E,EAAA,IAAI,SAAA,KAAc,MAAA,IAAa,IAAA,CAAK,MAAA,GAAS,SAAA,EAAW;AACtD,IAAA,IAAA,GAAO,IAAA,CACJ,KAAA,CAAM,CAAA,EAAG,SAAS,CAAA,CAClB,OAAA,CAAQ,IAAI,MAAA,CAAO,CAAA,EAAG,gBAAgB,CAAA,EAAA,CAAA,EAAM,GAAG,GAAG,EAAE,CAAA;AAAA,EACzD;AAEA,EAAA,OAAO,IAAA;AACT","file":"index.cjs","sourcesContent":["export function assertFiniteNumber(\r\n value: unknown,\r\n name: string,\r\n): asserts value is number {\r\n if (typeof value !== \"number\" || !Number.isFinite(value)) {\r\n throw new TypeError(`${name} must be a finite number`);\r\n }\r\n}\r\n\r\nexport function escapeRegExp(value: string): string {\r\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\r\n}\r\n","import { escapeRegExp } from \"../internal/validation.js\";\r\n\r\nexport interface SlugifyOptions {\r\n /**\r\n * Word separator used in the generated slug.\r\n * Must be a non-empty string that contains no whitespace.\r\n * @default \"-\"\r\n */\r\n separator?: string;\r\n /**\r\n * Maximum slug length. Trailing separators are removed after truncation.\r\n */\r\n maxLength?: number;\r\n /**\r\n * When `true`, Unicode letters and numbers are kept after diacritic stripping.\r\n * When `false`, only ASCII `[a-z0-9]` characters remain.\r\n * @default true\r\n */\r\n preserveUnicode?: boolean;\r\n}\r\n\r\n/**\r\n * Converts text into a URL-friendly slug.\r\n *\r\n * The result is lowercased. Unicode is normalized with NFD and combining\r\n * marks are removed, so `\"Café\"` becomes `\"cafe\"`. This is not full\r\n * language transliteration, and generated slugs are not guaranteed unique.\r\n *\r\n * @throws {TypeError} When `separator` is empty or contains whitespace.\r\n * @throws {TypeError} When `preserveUnicode` is not a boolean.\r\n * @throws {RangeError} When `maxLength` is not a positive integer.\r\n *\r\n * @example\r\n * slugify(\"Cavalo Care — RSA Plans\") // \"cavalo-care-rsa-plans\"\r\n * slugify(\"Hello World\", { separator: \"_\" }) // \"hello_world\"\r\n * slugify(\"Café Products\") // \"cafe-products\"\r\n */\r\nexport function slugify(value: string, options: SlugifyOptions = {}): string {\r\n const separator = options.separator ?? \"-\";\r\n const preserveUnicode = options.preserveUnicode ?? true;\r\n const { maxLength } = options;\r\n\r\n if (typeof separator !== \"string\" || separator.length === 0 || /\\s/u.test(separator)) {\r\n throw new TypeError(\"separator must be a non-empty string without whitespace\");\r\n }\r\n\r\n if (options.preserveUnicode !== undefined && typeof options.preserveUnicode !== \"boolean\") {\r\n throw new TypeError(\"preserveUnicode must be a boolean\");\r\n }\r\n\r\n if (maxLength !== undefined && (!Number.isInteger(maxLength) || maxLength <= 0)) {\r\n throw new RangeError(\"maxLength must be a positive integer\");\r\n }\r\n\r\n const escapedSeparator = escapeRegExp(separator);\r\n const disallowedPattern = preserveUnicode ? /[^\\p{L}\\p{N}]+/gu : /[^a-z0-9]+/g;\r\n\r\n let slug = value\r\n .normalize(\"NFD\")\r\n .replace(/\\p{M}/gu, \"\")\r\n .toLowerCase()\r\n .replace(disallowedPattern, separator)\r\n .replace(new RegExp(`${escapedSeparator}+`, \"g\"), separator)\r\n .replace(new RegExp(`^${escapedSeparator}|${escapedSeparator}$`, \"g\"), \"\");\r\n\r\n if (maxLength !== undefined && slug.length > maxLength) {\r\n slug = slug\r\n .slice(0, maxLength)\r\n .replace(new RegExp(`${escapedSeparator}+$`, \"g\"), \"\");\r\n }\r\n\r\n return slug;\r\n}\r\n"]}
@@ -0,0 +1,38 @@
1
+ interface SlugifyOptions {
2
+ /**
3
+ * Word separator used in the generated slug.
4
+ * Must be a non-empty string that contains no whitespace.
5
+ * @default "-"
6
+ */
7
+ separator?: string;
8
+ /**
9
+ * Maximum slug length. Trailing separators are removed after truncation.
10
+ */
11
+ maxLength?: number;
12
+ /**
13
+ * When `true`, Unicode letters and numbers are kept after diacritic stripping.
14
+ * When `false`, only ASCII `[a-z0-9]` characters remain.
15
+ * @default true
16
+ */
17
+ preserveUnicode?: boolean;
18
+ }
19
+ /**
20
+ * Converts text into a URL-friendly slug.
21
+ *
22
+ * The result is lowercased. Unicode is normalized with NFD and combining
23
+ * marks are removed, so `"Café"` becomes `"cafe"`. This is not full
24
+ * language transliteration, and generated slugs are not guaranteed unique.
25
+ *
26
+ * @throws {TypeError} When `separator` is empty or contains whitespace.
27
+ * @throws {TypeError} When `preserveUnicode` is not a boolean.
28
+ * @throws {RangeError} When `maxLength` is not a positive integer.
29
+ *
30
+ * @example
31
+ * slugify("Cavalo Care — RSA Plans") // "cavalo-care-rsa-plans"
32
+ * slugify("Hello World", { separator: "_" }) // "hello_world"
33
+ * slugify("Café Products") // "cafe-products"
34
+ */
35
+ declare function slugify(value: string, options?: SlugifyOptions): string;
36
+
37
+ export { slugify };
38
+ export type { SlugifyOptions };
@@ -0,0 +1,38 @@
1
+ interface SlugifyOptions {
2
+ /**
3
+ * Word separator used in the generated slug.
4
+ * Must be a non-empty string that contains no whitespace.
5
+ * @default "-"
6
+ */
7
+ separator?: string;
8
+ /**
9
+ * Maximum slug length. Trailing separators are removed after truncation.
10
+ */
11
+ maxLength?: number;
12
+ /**
13
+ * When `true`, Unicode letters and numbers are kept after diacritic stripping.
14
+ * When `false`, only ASCII `[a-z0-9]` characters remain.
15
+ * @default true
16
+ */
17
+ preserveUnicode?: boolean;
18
+ }
19
+ /**
20
+ * Converts text into a URL-friendly slug.
21
+ *
22
+ * The result is lowercased. Unicode is normalized with NFD and combining
23
+ * marks are removed, so `"Café"` becomes `"cafe"`. This is not full
24
+ * language transliteration, and generated slugs are not guaranteed unique.
25
+ *
26
+ * @throws {TypeError} When `separator` is empty or contains whitespace.
27
+ * @throws {TypeError} When `preserveUnicode` is not a boolean.
28
+ * @throws {RangeError} When `maxLength` is not a positive integer.
29
+ *
30
+ * @example
31
+ * slugify("Cavalo Care — RSA Plans") // "cavalo-care-rsa-plans"
32
+ * slugify("Hello World", { separator: "_" }) // "hello_world"
33
+ * slugify("Café Products") // "cafe-products"
34
+ */
35
+ declare function slugify(value: string, options?: SlugifyOptions): string;
36
+
37
+ export { slugify };
38
+ export type { SlugifyOptions };
@@ -0,0 +1,31 @@
1
+ // src/internal/validation.ts
2
+ function escapeRegExp(value) {
3
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4
+ }
5
+
6
+ // src/slug/index.ts
7
+ function slugify(value, options = {}) {
8
+ const separator = options.separator ?? "-";
9
+ const preserveUnicode = options.preserveUnicode ?? true;
10
+ const { maxLength } = options;
11
+ if (typeof separator !== "string" || separator.length === 0 || /\s/u.test(separator)) {
12
+ throw new TypeError("separator must be a non-empty string without whitespace");
13
+ }
14
+ if (options.preserveUnicode !== void 0 && typeof options.preserveUnicode !== "boolean") {
15
+ throw new TypeError("preserveUnicode must be a boolean");
16
+ }
17
+ if (maxLength !== void 0 && (!Number.isInteger(maxLength) || maxLength <= 0)) {
18
+ throw new RangeError("maxLength must be a positive integer");
19
+ }
20
+ const escapedSeparator = escapeRegExp(separator);
21
+ const disallowedPattern = preserveUnicode ? /[^\p{L}\p{N}]+/gu : /[^a-z0-9]+/g;
22
+ let slug = value.normalize("NFD").replace(/\p{M}/gu, "").toLowerCase().replace(disallowedPattern, separator).replace(new RegExp(`${escapedSeparator}+`, "g"), separator).replace(new RegExp(`^${escapedSeparator}|${escapedSeparator}$`, "g"), "");
23
+ if (maxLength !== void 0 && slug.length > maxLength) {
24
+ slug = slug.slice(0, maxLength).replace(new RegExp(`${escapedSeparator}+$`, "g"), "");
25
+ }
26
+ return slug;
27
+ }
28
+
29
+ export { slugify };
30
+ //# sourceMappingURL=index.js.map
31
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/internal/validation.ts","../../src/slug/index.ts"],"names":[],"mappings":";AASO,SAAS,aAAa,KAAA,EAAuB;AAClD,EAAA,OAAO,KAAA,CAAM,OAAA,CAAQ,qBAAA,EAAuB,MAAM,CAAA;AACpD;;;AC0BO,SAAS,OAAA,CAAQ,KAAA,EAAe,OAAA,GAA0B,EAAC,EAAW;AAC3E,EAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,GAAA;AACvC,EAAA,MAAM,eAAA,GAAkB,QAAQ,eAAA,IAAmB,IAAA;AACnD,EAAA,MAAM,EAAE,WAAU,GAAI,OAAA;AAEtB,EAAA,IAAI,OAAO,cAAc,QAAA,IAAY,SAAA,CAAU,WAAW,CAAA,IAAK,KAAA,CAAM,IAAA,CAAK,SAAS,CAAA,EAAG;AACpF,IAAA,MAAM,IAAI,UAAU,yDAAyD,CAAA;AAAA,EAC/E;AAEA,EAAA,IAAI,QAAQ,eAAA,KAAoB,MAAA,IAAa,OAAO,OAAA,CAAQ,oBAAoB,SAAA,EAAW;AACzF,IAAA,MAAM,IAAI,UAAU,mCAAmC,CAAA;AAAA,EACzD;AAEA,EAAA,IAAI,SAAA,KAAc,WAAc,CAAC,MAAA,CAAO,UAAU,SAAS,CAAA,IAAK,aAAa,CAAA,CAAA,EAAI;AAC/E,IAAA,MAAM,IAAI,WAAW,sCAAsC,CAAA;AAAA,EAC7D;AAEA,EAAA,MAAM,gBAAA,GAAmB,aAAa,SAAS,CAAA;AAC/C,EAAA,MAAM,iBAAA,GAAoB,kBAAkB,kBAAA,GAAqB,aAAA;AAEjE,EAAA,IAAI,IAAA,GAAO,KAAA,CACR,SAAA,CAAU,KAAK,EACf,OAAA,CAAQ,SAAA,EAAW,EAAE,CAAA,CACrB,aAAY,CACZ,OAAA,CAAQ,iBAAA,EAAmB,SAAS,EACpC,OAAA,CAAQ,IAAI,MAAA,CAAO,CAAA,EAAG,gBAAgB,CAAA,CAAA,CAAA,EAAK,GAAG,CAAA,EAAG,SAAS,EAC1D,OAAA,CAAQ,IAAI,MAAA,CAAO,CAAA,CAAA,EAAI,gBAAgB,CAAA,CAAA,EAAI,gBAAgB,CAAA,CAAA,CAAA,EAAK,GAAG,GAAG,EAAE,CAAA;AAE3E,EAAA,IAAI,SAAA,KAAc,MAAA,IAAa,IAAA,CAAK,MAAA,GAAS,SAAA,EAAW;AACtD,IAAA,IAAA,GAAO,IAAA,CACJ,KAAA,CAAM,CAAA,EAAG,SAAS,CAAA,CAClB,OAAA,CAAQ,IAAI,MAAA,CAAO,CAAA,EAAG,gBAAgB,CAAA,EAAA,CAAA,EAAM,GAAG,GAAG,EAAE,CAAA;AAAA,EACzD;AAEA,EAAA,OAAO,IAAA;AACT","file":"index.js","sourcesContent":["export function assertFiniteNumber(\r\n value: unknown,\r\n name: string,\r\n): asserts value is number {\r\n if (typeof value !== \"number\" || !Number.isFinite(value)) {\r\n throw new TypeError(`${name} must be a finite number`);\r\n }\r\n}\r\n\r\nexport function escapeRegExp(value: string): string {\r\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\r\n}\r\n","import { escapeRegExp } from \"../internal/validation.js\";\r\n\r\nexport interface SlugifyOptions {\r\n /**\r\n * Word separator used in the generated slug.\r\n * Must be a non-empty string that contains no whitespace.\r\n * @default \"-\"\r\n */\r\n separator?: string;\r\n /**\r\n * Maximum slug length. Trailing separators are removed after truncation.\r\n */\r\n maxLength?: number;\r\n /**\r\n * When `true`, Unicode letters and numbers are kept after diacritic stripping.\r\n * When `false`, only ASCII `[a-z0-9]` characters remain.\r\n * @default true\r\n */\r\n preserveUnicode?: boolean;\r\n}\r\n\r\n/**\r\n * Converts text into a URL-friendly slug.\r\n *\r\n * The result is lowercased. Unicode is normalized with NFD and combining\r\n * marks are removed, so `\"Café\"` becomes `\"cafe\"`. This is not full\r\n * language transliteration, and generated slugs are not guaranteed unique.\r\n *\r\n * @throws {TypeError} When `separator` is empty or contains whitespace.\r\n * @throws {TypeError} When `preserveUnicode` is not a boolean.\r\n * @throws {RangeError} When `maxLength` is not a positive integer.\r\n *\r\n * @example\r\n * slugify(\"Cavalo Care — RSA Plans\") // \"cavalo-care-rsa-plans\"\r\n * slugify(\"Hello World\", { separator: \"_\" }) // \"hello_world\"\r\n * slugify(\"Café Products\") // \"cafe-products\"\r\n */\r\nexport function slugify(value: string, options: SlugifyOptions = {}): string {\r\n const separator = options.separator ?? \"-\";\r\n const preserveUnicode = options.preserveUnicode ?? true;\r\n const { maxLength } = options;\r\n\r\n if (typeof separator !== \"string\" || separator.length === 0 || /\\s/u.test(separator)) {\r\n throw new TypeError(\"separator must be a non-empty string without whitespace\");\r\n }\r\n\r\n if (options.preserveUnicode !== undefined && typeof options.preserveUnicode !== \"boolean\") {\r\n throw new TypeError(\"preserveUnicode must be a boolean\");\r\n }\r\n\r\n if (maxLength !== undefined && (!Number.isInteger(maxLength) || maxLength <= 0)) {\r\n throw new RangeError(\"maxLength must be a positive integer\");\r\n }\r\n\r\n const escapedSeparator = escapeRegExp(separator);\r\n const disallowedPattern = preserveUnicode ? /[^\\p{L}\\p{N}]+/gu : /[^a-z0-9]+/g;\r\n\r\n let slug = value\r\n .normalize(\"NFD\")\r\n .replace(/\\p{M}/gu, \"\")\r\n .toLowerCase()\r\n .replace(disallowedPattern, separator)\r\n .replace(new RegExp(`${escapedSeparator}+`, \"g\"), separator)\r\n .replace(new RegExp(`^${escapedSeparator}|${escapedSeparator}$`, \"g\"), \"\");\r\n\r\n if (maxLength !== undefined && slug.length > maxLength) {\r\n slug = slug\r\n .slice(0, maxLength)\r\n .replace(new RegExp(`${escapedSeparator}+$`, \"g\"), \"\");\r\n }\r\n\r\n return slug;\r\n}\r\n"]}