@digicroz/js-kit 1.0.8 → 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/base64/index.cjs +26 -0
- package/dist/base64/index.cjs.map +1 -0
- package/dist/base64/index.d.cts +4 -0
- package/dist/base64/index.d.ts +4 -0
- package/dist/base64/index.js +23 -0
- package/dist/base64/index.js.map +1 -0
- package/dist/file/index.cjs +82 -0
- package/dist/file/index.cjs.map +1 -0
- package/dist/file/index.d.cts +9 -0
- package/dist/file/index.d.ts +9 -0
- package/dist/file/index.js +77 -0
- package/dist/file/index.js.map +1 -0
- package/dist/index.cjs +177 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +166 -9
- package/dist/index.js.map +1 -1
- package/dist/name/index.cjs +61 -0
- package/dist/name/index.cjs.map +1 -0
- package/dist/name/index.d.cts +60 -0
- package/dist/name/index.d.ts +60 -0
- package/dist/name/index.js +55 -0
- package/dist/name/index.js.map +1 -0
- package/dist/slug/index.cjs +30 -8
- package/dist/slug/index.cjs.map +1 -1
- package/dist/slug/index.d.cts +23 -4
- package/dist/slug/index.d.ts +23 -4
- package/dist/slug/index.js +30 -8
- package/dist/slug/index.js.map +1 -1
- package/dist/std-response/index.cjs +17 -0
- package/dist/std-response/index.cjs.map +1 -0
- package/dist/std-response/index.d.cts +18 -0
- package/dist/std-response/index.d.ts +18 -0
- package/dist/std-response/index.js +15 -0
- package/dist/std-response/index.js.map +1 -0
- package/package.json +12 -4
package/README.md
CHANGED
|
@@ -185,6 +185,7 @@ import {
|
|
|
185
185
|
| `sleep` | `sleep`, `sleepMs`, `sleepSeconds`, `sleepMinutes`, `sleepUntil` | Promise-based sleep with flexible time options |
|
|
186
186
|
| `time` | `convertToSeconds`, `getUnixTimestamp`, `getUnixTimestampMs` | Time conversion and timestamp utilities |
|
|
187
187
|
| `utils` | `isNodeEnvironment`, `isBrowserEnvironment`, `isWebWorkerEnvironment`, `getEnvironment`, `assertNodeEnvironment`, `assertBrowserEnvironment` | Environment detection and assertions |
|
|
188
|
+
| `std-response` | `stdResponse.success`, `stdResponse.error`, `StdSuccess`, `StdError`, `StdResponse` | Standardized API response types and utilities |
|
|
188
189
|
| `types` | `Prettify` | Utility types for TypeScript development |
|
|
189
190
|
|
|
190
191
|
## TypeScript Configuration
|
|
@@ -244,6 +245,7 @@ For comprehensive documentation with examples, advanced usage patterns, and best
|
|
|
244
245
|
- **[📝 String Utilities](./src/string/string.md)** - String manipulation, formatting, and case conversion
|
|
245
246
|
- **[⏰ Time Utilities](./src/time/time.md)** - Time conversion and duration utilities
|
|
246
247
|
- **[🌐 Environment Utilities](./src/utils/utils.md)** - Environment detection and cross-platform utilities
|
|
248
|
+
- **[📦 Standard Response Utilities](./src/std-response/std-response.md)** - Standardized API response types and helper functions
|
|
247
249
|
|
|
248
250
|
### Quick Reference
|
|
249
251
|
|
|
@@ -306,8 +308,14 @@ For comprehensive documentation with examples, advanced usage patterns, and best
|
|
|
306
308
|
- `isWebWorkerEnvironment(): boolean` - Checks if running in web worker
|
|
307
309
|
- `getEnvironment(): string` - Gets current environment type
|
|
308
310
|
- `assertNodeEnvironment(): void` - Asserts Node.js environment
|
|
311
|
+
- `assertNodeEnvironment(): void` - Asserts Node.js environment
|
|
309
312
|
- `assertBrowserEnvironment(): void` - Asserts browser environment
|
|
310
313
|
|
|
314
|
+
#### Standard Response Utilities
|
|
315
|
+
|
|
316
|
+
- `stdResponse.success<T>(result: T, message?: string): StdSuccess<T>` - Creates a success response
|
|
317
|
+
- `stdResponse.error<E>(code: E, message?: string): StdError<E>` - Creates an error response
|
|
318
|
+
|
|
311
319
|
#### Type Utilities
|
|
312
320
|
|
|
313
321
|
- `Prettify<T>` - Utility type for better TypeScript intellisense
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/base64/index.ts
|
|
4
|
+
function encodeToBase64(obj) {
|
|
5
|
+
if (obj === null || obj === void 0) {
|
|
6
|
+
throw new Error("Cannot encode null or undefined");
|
|
7
|
+
}
|
|
8
|
+
const json = JSON.stringify(obj);
|
|
9
|
+
return Buffer.from(json, "utf8").toString("base64");
|
|
10
|
+
}
|
|
11
|
+
function decodeFromBase64(base64) {
|
|
12
|
+
if (!base64 || typeof base64 !== "string") {
|
|
13
|
+
throw new Error("Invalid base64 input");
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const json = Buffer.from(base64, "base64").toString("utf8");
|
|
17
|
+
return JSON.parse(json);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
throw new Error("Failed to decode base64 string");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
exports.decodeFromBase64 = decodeFromBase64;
|
|
24
|
+
exports.encodeToBase64 = encodeToBase64;
|
|
25
|
+
//# sourceMappingURL=index.cjs.map
|
|
26
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/base64/index.ts"],"names":[],"mappings":";;;AACO,SAAS,eAAe,GAAA,EAAsB;AACjD,EAAA,IAAI,GAAA,KAAQ,IAAA,IAAQ,GAAA,KAAQ,MAAA,EAAW;AACnC,IAAA,MAAM,IAAI,MAAM,iCAAiC,CAAA;AAAA;AAGrD,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,SAAA,CAAU,GAAG,CAAA;AAC/B,EAAA,OAAO,OAAO,IAAA,CAAK,IAAA,EAAM,MAAM,CAAA,CAAE,SAAS,QAAQ,CAAA;AACtD;AAEO,SAAS,iBAA8B,MAAA,EAAmB;AAC7D,EAAA,IAAI,CAAC,MAAA,IAAU,OAAO,MAAA,KAAW,QAAA,EAAU;AACvC,IAAA,MAAM,IAAI,MAAM,sBAAsB,CAAA;AAAA;AAG1C,EAAA,IAAI;AACA,IAAA,MAAM,OAAO,MAAA,CAAO,IAAA,CAAK,QAAQ,QAAQ,CAAA,CAAE,SAAS,MAAM,CAAA;AAC1D,IAAA,OAAO,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA,WACjB,KAAA,EAAO;AACZ,IAAA,MAAM,IAAI,MAAM,gCAAgC,CAAA;AAAA;AAExD","file":"index.cjs","sourcesContent":["\r\nexport function encodeToBase64(obj: unknown): string {\r\n if (obj === null || obj === undefined) {\r\n throw new Error(\"Cannot encode null or undefined\");\r\n }\r\n\r\n const json = JSON.stringify(obj);\r\n return Buffer.from(json, \"utf8\").toString(\"base64\");\r\n}\r\n\r\nexport function decodeFromBase64<T = unknown>(base64: string): T {\r\n if (!base64 || typeof base64 !== \"string\") {\r\n throw new Error(\"Invalid base64 input\");\r\n }\r\n\r\n try {\r\n const json = Buffer.from(base64, \"base64\").toString(\"utf8\");\r\n return JSON.parse(json);\r\n } catch (error) {\r\n throw new Error(\"Failed to decode base64 string\");\r\n }\r\n}\r\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// src/base64/index.ts
|
|
2
|
+
function encodeToBase64(obj) {
|
|
3
|
+
if (obj === null || obj === void 0) {
|
|
4
|
+
throw new Error("Cannot encode null or undefined");
|
|
5
|
+
}
|
|
6
|
+
const json = JSON.stringify(obj);
|
|
7
|
+
return Buffer.from(json, "utf8").toString("base64");
|
|
8
|
+
}
|
|
9
|
+
function decodeFromBase64(base64) {
|
|
10
|
+
if (!base64 || typeof base64 !== "string") {
|
|
11
|
+
throw new Error("Invalid base64 input");
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const json = Buffer.from(base64, "base64").toString("utf8");
|
|
15
|
+
return JSON.parse(json);
|
|
16
|
+
} catch (error) {
|
|
17
|
+
throw new Error("Failed to decode base64 string");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { decodeFromBase64, encodeToBase64 };
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/base64/index.ts"],"names":[],"mappings":";AACO,SAAS,eAAe,GAAA,EAAsB;AACjD,EAAA,IAAI,GAAA,KAAQ,IAAA,IAAQ,GAAA,KAAQ,MAAA,EAAW;AACnC,IAAA,MAAM,IAAI,MAAM,iCAAiC,CAAA;AAAA;AAGrD,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,SAAA,CAAU,GAAG,CAAA;AAC/B,EAAA,OAAO,OAAO,IAAA,CAAK,IAAA,EAAM,MAAM,CAAA,CAAE,SAAS,QAAQ,CAAA;AACtD;AAEO,SAAS,iBAA8B,MAAA,EAAmB;AAC7D,EAAA,IAAI,CAAC,MAAA,IAAU,OAAO,MAAA,KAAW,QAAA,EAAU;AACvC,IAAA,MAAM,IAAI,MAAM,sBAAsB,CAAA;AAAA;AAG1C,EAAA,IAAI;AACA,IAAA,MAAM,OAAO,MAAA,CAAO,IAAA,CAAK,QAAQ,QAAQ,CAAA,CAAE,SAAS,MAAM,CAAA;AAC1D,IAAA,OAAO,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA,WACjB,KAAA,EAAO;AACZ,IAAA,MAAM,IAAI,MAAM,gCAAgC,CAAA;AAAA;AAExD","file":"index.js","sourcesContent":["\r\nexport function encodeToBase64(obj: unknown): string {\r\n if (obj === null || obj === undefined) {\r\n throw new Error(\"Cannot encode null or undefined\");\r\n }\r\n\r\n const json = JSON.stringify(obj);\r\n return Buffer.from(json, \"utf8\").toString(\"base64\");\r\n}\r\n\r\nexport function decodeFromBase64<T = unknown>(base64: string): T {\r\n if (!base64 || typeof base64 !== \"string\") {\r\n throw new Error(\"Invalid base64 input\");\r\n }\r\n\r\n try {\r\n const json = Buffer.from(base64, \"base64\").toString(\"utf8\");\r\n return JSON.parse(json);\r\n } catch (error) {\r\n throw new Error(\"Failed to decode base64 string\");\r\n }\r\n}\r\n"]}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
4
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
5
|
+
}) : x)(function(x) {
|
|
6
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
7
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
// src/slug/index.ts
|
|
11
|
+
function convertToSlug(text, options = {}) {
|
|
12
|
+
const { separator = "-", allowDots = false } = options;
|
|
13
|
+
if (!text || typeof text !== "string") {
|
|
14
|
+
return "";
|
|
15
|
+
}
|
|
16
|
+
const escapedSeparator = separator.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
17
|
+
let slug = text.toString().toLowerCase().trim().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
18
|
+
if (!allowDots) {
|
|
19
|
+
slug = slug.replace(/\./g, separator);
|
|
20
|
+
}
|
|
21
|
+
slug = slug.replace(/[\s_]+/g, separator).replace(new RegExp(`[^a-z0-9${escapedSeparator}${allowDots ? "\\." : ""}]`, "g"), "");
|
|
22
|
+
if (allowDots) {
|
|
23
|
+
slug = slug.replace(new RegExp(`[${escapedSeparator}.]+`, "g"), (match) => {
|
|
24
|
+
return match.includes(".") ? "." : separator;
|
|
25
|
+
});
|
|
26
|
+
} else {
|
|
27
|
+
slug = slug.replace(new RegExp(`${escapedSeparator}+`, "g"), separator);
|
|
28
|
+
}
|
|
29
|
+
const trimRegex = new RegExp(`^[${escapedSeparator}${allowDots ? "\\." : ""}]+|[${escapedSeparator}${allowDots ? "\\." : ""}]+$`, "g");
|
|
30
|
+
return slug.replace(trimRegex, "");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/file/index.ts
|
|
34
|
+
async function getArrayBuffer(input) {
|
|
35
|
+
if (input && typeof input.arrayBuffer === "function") {
|
|
36
|
+
return await input.arrayBuffer();
|
|
37
|
+
}
|
|
38
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(input)) {
|
|
39
|
+
return input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength);
|
|
40
|
+
}
|
|
41
|
+
if (input instanceof Uint8Array) {
|
|
42
|
+
return input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength);
|
|
43
|
+
}
|
|
44
|
+
throw new Error("Unsupported input type for hashing");
|
|
45
|
+
}
|
|
46
|
+
async function hashContent(input, hashLength = 8) {
|
|
47
|
+
const buffer = await getArrayBuffer(input);
|
|
48
|
+
const cryptoAPI = typeof crypto !== "undefined" ? crypto : typeof window !== "undefined" ? window.crypto : void 0;
|
|
49
|
+
if (cryptoAPI?.subtle?.digest) {
|
|
50
|
+
const hashBuffer = await cryptoAPI.subtle.digest("SHA-256", buffer);
|
|
51
|
+
return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, hashLength);
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const nodeCrypto = __require("crypto");
|
|
55
|
+
const hash = nodeCrypto.createHash("sha256").update(Buffer.from(buffer)).digest("hex");
|
|
56
|
+
return hash.slice(0, hashLength);
|
|
57
|
+
} catch (e) {
|
|
58
|
+
console.warn("Crypto fallback failed", e);
|
|
59
|
+
throw new Error("No crypto implementation found");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function sanitizeFilename(name) {
|
|
63
|
+
return convertToSlug(name, {
|
|
64
|
+
allowDots: true
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
async function getHashedFilename(input, originalName, options = {}) {
|
|
68
|
+
const { hashLength = 8 } = options;
|
|
69
|
+
const hash = await hashContent(input, hashLength);
|
|
70
|
+
const dotIndex = originalName.lastIndexOf(".");
|
|
71
|
+
const base = dotIndex > 0 ? originalName.slice(0, dotIndex) : originalName;
|
|
72
|
+
const ext = dotIndex > 0 ? originalName.slice(dotIndex + 1) : "";
|
|
73
|
+
const safeBase = sanitizeFilename(base);
|
|
74
|
+
return ext ? `${safeBase}.${hash}.${ext}` : `${safeBase}.${hash}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
exports.getArrayBuffer = getArrayBuffer;
|
|
78
|
+
exports.getHashedFilename = getHashedFilename;
|
|
79
|
+
exports.hashContent = hashContent;
|
|
80
|
+
exports.sanitizeFilename = sanitizeFilename;
|
|
81
|
+
//# sourceMappingURL=index.cjs.map
|
|
82
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/slug/index.ts","../../src/file/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\r\n * Checks if a string is a valid slug\r\n * A valid slug contains only lowercase alphanumeric characters and hyphens,\r\n * with no consecutive hyphens and no leading/trailing hyphens\r\n * \r\n * @param slug - The string to validate\r\n * @returns True if the string is a valid slug, false otherwise\r\n * \r\n * @example\r\n * ```ts\r\n * isValidSlug('hello-world') // true\r\n * isValidSlug('hello--world') // false (consecutive hyphens)\r\n * isValidSlug('Hello-World') // false (uppercase)\r\n * isValidSlug('-hello-world') // false (leading hyphen)\r\n * isValidSlug('hello_world') // false (underscore not allowed)\r\n * ```\r\n */\r\nexport function isValidSlug(\r\n slug: string,\r\n options: { allowDots?: boolean } = {}\r\n): boolean {\r\n if (!slug || typeof slug !== 'string') {\r\n return false;\r\n }\r\n\r\n const { allowDots = false } = options;\r\n\r\n // Check if slug matches the pattern:\r\n // - starts with alphanumeric\r\n // - ends with alphanumeric\r\n // - contains only lowercase alphanumeric and single hyphens (and dots if allowed)\r\n // - no consecutive separators (hyphens or dots)\r\n \r\n if (allowDots) {\r\n // pattern allowing dots:\r\n // starts with alphanumeric\r\n // middle: alphanumeric or single hyphen/dot followed by alphanumeric\r\n // ends with alphanumeric\r\n const slugWithDotsPattern = /^[a-z0-9]+(?:[-.][a-z0-9]+)*$/;\r\n return slugWithDotsPattern.test(slug);\r\n }\r\n\r\n const slugPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\r\n \r\n return slugPattern.test(slug);\r\n}\r\n\r\n/**\r\n * Converts a string to a URL-safe slug\r\n * - Converts to lowercase\r\n * - Removes special characters\r\n * - Replaces spaces and underscores with hyphens\r\n * - Removes consecutive hyphens\r\n * - Trims leading and trailing hyphens\r\n * \r\n * @param text - The string to convert to a slug\r\n * @param options - Optional configuration\r\n * @param options.separator - Character to use as separator (default: '-')\r\n * @returns A URL-safe slug\r\n * \r\n * @example\r\n * ```ts\r\n * convertToSlug('Hello World') // 'hello-world'\r\n * convertToSlug('Hello World!!!') // 'hello-world'\r\n * convertToSlug('Hello_World') // 'hello-world'\r\n * convertToSlug(' Hello World ') // 'hello-world'\r\n * convertToSlug('Hello---World') // 'hello-world'\r\n * convertToSlug('Café & Restaurant') // 'cafe-restaurant'\r\n * convertToSlug('Product #123') // 'product-123'\r\n * ```\r\n */\r\nexport function convertToSlug(\r\n text: string,\r\n options: { separator?: string; allowDots?: boolean } = {}\r\n): string {\r\n const { separator = '-', allowDots = false } = options;\r\n\r\n if (!text || typeof text !== 'string') {\r\n return '';\r\n }\r\n\r\n // Escape separator for use in regex char class\r\n const escapedSeparator = separator.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\r\n\r\n let slug = text\r\n .toString()\r\n .toLowerCase()\r\n .trim()\r\n // Remove accents and diacritics\r\n .normalize('NFD')\r\n .replace(/[\\u0300-\\u036f]/g, '');\r\n\r\n if (!allowDots) {\r\n // Replace dots with separator\r\n slug = slug.replace(/\\./g, separator);\r\n }\r\n\r\n slug = slug\r\n // Replace spaces, underscores with separator\r\n .replace(/[\\s_]+/g, separator)\r\n // Remove all non-alphanumeric characters except the separator (and dot if allowed)\r\n .replace(new RegExp(`[^a-z0-9${escapedSeparator}${allowDots ? '\\\\.' : ''}]`, 'g'), '');\r\n\r\n if (allowDots) {\r\n // Collapse consecutive separators/dots\r\n // If sequence contains a dot, we generally want to keep it as a dot (e.g. file.-extension -> file.extension)\r\n slug = slug.replace(new RegExp(`[${escapedSeparator}.]+`, 'g'), (match) => {\r\n return match.includes('.') ? '.' : separator;\r\n });\r\n } else {\r\n // Replace multiple consecutive separators with single separator\r\n slug = slug.replace(new RegExp(`${escapedSeparator}+`, 'g'), separator);\r\n }\r\n \r\n // Remove leading and trailing separators (and dots)\r\n const trimRegex = new RegExp(`^[${escapedSeparator}${allowDots ? '\\\\.' : ''}]+|[${escapedSeparator}${allowDots ? '\\\\.' : ''}]+$`, 'g');\r\n return slug.replace(trimRegex, '');\r\n}\r\n\r\n/**\r\n * Generates a unique slug by appending a number if the slug already exists\r\n * \r\n * @param baseSlug - The base slug to make unique\r\n * @param existingSlugs - Array of existing slugs to check against\r\n * @param options - Optional configuration\r\n * @param options.separator - Character to use before the number (default: '-')\r\n * @returns A unique slug\r\n * \r\n * @example\r\n * ```ts\r\n * generateUniqueSlug('hello-world', ['hello-world']) // 'hello-world-1'\r\n * generateUniqueSlug('hello-world', ['hello-world', 'hello-world-1']) // 'hello-world-2'\r\n * generateUniqueSlug('hello-world', []) // 'hello-world'\r\n * ```\r\n */\r\nexport function generateUniqueSlug(\r\n baseSlug: string,\r\n existingSlugs: string[],\r\n options: { separator?: string } = {}\r\n): string {\r\n const { separator = '-' } = options;\r\n\r\n if (!existingSlugs.includes(baseSlug)) {\r\n return baseSlug;\r\n }\r\n\r\n let counter = 1;\r\n let uniqueSlug = `${baseSlug}${separator}${counter}`;\r\n\r\n while (existingSlugs.includes(uniqueSlug)) {\r\n counter++;\r\n uniqueSlug = `${baseSlug}${separator}${counter}`;\r\n }\r\n\r\n return uniqueSlug;\r\n}\r\n\r\n/**\r\n * Creates a Zod refinement function for slug validation\r\n * Use with z.string().refine() or z.string().superRefine()\r\n * \r\n * @param optionsOrMessage - Custom error message or options object\r\n * @returns Refinement function for Zod\r\n * \r\n * @example\r\n * ```ts\r\n * import { z } from 'zod';\r\n * import { zodSlugValidation } from '@digicroz/js-kit';\r\n * \r\n * // Basic usage\r\n * const schema = z.object({\r\n * slug: z.string().refine(zodSlugValidation(), {\r\n * message: 'Invalid slug format'\r\n * })\r\n * });\r\n * \r\n * // Allow dots (e.g. for filenames)\r\n * const fileSchema = z.object({\r\n * filename: z.string().refine(zodSlugValidation({ allowDots: true }), {\r\n * message: 'Invalid filename format'\r\n * })\r\n * });\r\n * ```\r\n */\r\nexport function zodSlugValidation(\r\n optionsOrMessage?: string | { message?: string; allowDots?: boolean }\r\n) {\r\n const options =\r\n typeof optionsOrMessage === 'string'\r\n ? { message: optionsOrMessage }\r\n : optionsOrMessage || {};\r\n \r\n return (val: string) => isValidSlug(val, { allowDots: options.allowDots });\r\n}\r\n\r\n/**\r\n * Creates a Zod transform function that converts strings to slugs\r\n * Use with z.string().transform()\r\n * \r\n * @param options - Optional configuration\r\n * @param options.separator - Character to use as separator (default: '-')\r\n * @param options.allowDots - Whether to allow dots in the slug (default: false)\r\n * @returns Transform function for Zod\r\n * \r\n * @example\r\n * ```ts\r\n * import { z } from 'zod';\r\n * import { zodSlugTransform } from '@digicroz/js-kit';\r\n * \r\n * const schema = z.object({\r\n * title: z.string(),\r\n * slug: z.string().transform(zodSlugTransform())\r\n * });\r\n * \r\n * schema.parse({ title: 'Hello', slug: 'Hello World!!!' })\r\n * // { title: 'Hello', slug: 'hello-world' }\r\n * ```\r\n */\r\nexport function zodSlugTransform(options?: { separator?: string; allowDots?: boolean }) {\r\n return (val: string) => convertToSlug(val, options);\r\n}\r\n\r\n/**\r\n * Pre-configured Zod schema for slug validation\r\n * Validates that the string is a valid slug format\r\n * \r\n * @example\r\n * ```ts\r\n * import { z } from 'zod';\r\n * import { slugSchema } from '@digicroz/js-kit';\r\n * \r\n * const postSchema = z.object({\r\n * slug: slugSchema\r\n * });\r\n * \r\n * postSchema.parse({ slug: 'hello-world' }); // ✓ Valid\r\n * postSchema.parse({ slug: 'Hello World' }); // ✗ Invalid\r\n * ```\r\n */\r\nexport const slugSchema = {\r\n /**\r\n * Get a Zod string schema that validates slug format\r\n * Requires zod to be installed: npm install zod\r\n */\r\n create: (\r\n customMessageOrOptions?: string | { message?: string; allowDots?: boolean }\r\n ) => {\r\n // Dynamic import to avoid making zod a required dependency\r\n const options =\r\n typeof customMessageOrOptions === 'string'\r\n ? { message: customMessageOrOptions }\r\n : customMessageOrOptions || {};\r\n \r\n const message =\r\n options.message ||\r\n 'Must be a valid slug (lowercase, alphanumeric, and hyphens only, no consecutive hyphens)';\r\n\r\n return {\r\n _type: 'slug-validator' as const,\r\n validate: zodSlugValidation(options),\r\n message\r\n };\r\n }\r\n};\r\n\r\n/**\r\n * Pre-configured Zod schema that auto-converts strings to slugs\r\n * Automatically transforms any string input into a valid slug\r\n * \r\n * @example\r\n * ```ts\r\n * import { z } from 'zod';\r\n * import { autoSlugSchema } from '@digicroz/js-kit';\r\n * \r\n * const postSchema = z.object({\r\n * title: z.string(),\r\n * slug: z.string().transform(autoSlugSchema.transform())\r\n * });\r\n * \r\n * postSchema.parse({ title: 'My Post', slug: 'Hello World!!!' })\r\n * // { title: 'My Post', slug: 'hello-world' }\r\n * ```\r\n */\r\nexport const autoSlugSchema = {\r\n /**\r\n * Get a transform function for Zod\r\n */\r\n transform: (options?: { separator?: string }) => zodSlugTransform(options)\r\n};\r\n","\r\n// Universal File/Buffer types mapping for TS\r\n// \"File\" and \"Blob\" are DOM types. \"Buffer\" is a Node type.\r\n// We use 'any' or check existence to avoid build errors in pure Node/Browser setups without full libs.\r\n\r\nimport { convertToSlug } from \"../slug\";\r\n\r\nexport async function getArrayBuffer(input: any): Promise<ArrayBuffer> {\r\n // Browser: File or Blob\r\n if (input && typeof input.arrayBuffer === 'function') {\r\n return await input.arrayBuffer();\r\n }\r\n\r\n // Node: Buffer or Uint8Array\r\n if (typeof Buffer !== \"undefined\" && Buffer.isBuffer(input)) {\r\n // Buffer shares memory with the underlying ArrayBuffer\r\n // We must slice it to get the correct view if it's a subarray\r\n return input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength) as ArrayBuffer;\r\n }\r\n\r\n if (input instanceof Uint8Array) {\r\n return input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength) as ArrayBuffer;\r\n }\r\n\r\n throw new Error(\"Unsupported input type for hashing\");\r\n}\r\n\r\nexport async function hashContent(input: any, hashLength = 8): Promise<string> {\r\n const buffer = await getArrayBuffer(input);\r\n\r\n // Browser + Node 15+ (if global crypto is available or polyfilled)\r\n // We check for globalThis.crypto or window.crypto\r\n const cryptoAPI = (typeof crypto !== \"undefined\" ? crypto : (typeof window !== \"undefined\" ? window.crypto : undefined));\r\n \r\n if (cryptoAPI?.subtle?.digest) {\r\n const hashBuffer = await cryptoAPI.subtle.digest(\"SHA-256\", buffer);\r\n return Array.from(new Uint8Array(hashBuffer))\r\n .map(b => b.toString(16).padStart(2, \"0\"))\r\n .join(\"\")\r\n .slice(0, hashLength);\r\n }\r\n\r\n // Node fallback (using require for CommonJS/Node compatibility)\r\n // We use logic to avoid bundler errors if possible, but user provided require('crypto')\r\n try {\r\n // Dynamic require to avoid static analysis issues in some bundlers if targeting browser only\r\n // However, in a general js-kit, we can just try it.\r\n const nodeCrypto = require(\"crypto\");\r\n const hash = nodeCrypto.createHash(\"sha256\").update(Buffer.from(buffer)).digest(\"hex\");\r\n return hash.slice(0, hashLength);\r\n } catch (e) {\r\n console.warn(\"Crypto fallback failed\", e);\r\n throw new Error(\"No crypto implementation found\");\r\n }\r\n}\r\n\r\nexport function sanitizeFilename(name: string): string {\r\n return convertToSlug(name,{\r\n allowDots: true,\r\n });\r\n}\r\n\r\nexport interface HashedFilenameOptions {\r\n hashLength?: number;\r\n}\r\n\r\nexport async function getHashedFilename(input: any, originalName: string, options: HashedFilenameOptions = {}): Promise<string> {\r\n const { hashLength = 8 } = options;\r\n const hash = await hashContent(input, hashLength);\r\n\r\n const dotIndex = originalName.lastIndexOf(\".\");\r\n const base = dotIndex > 0 ? originalName.slice(0, dotIndex) : originalName;\r\n const ext = dotIndex > 0 ? originalName.slice(dotIndex + 1) : \"\";\r\n\r\n const safeBase = sanitizeFilename(base);\r\n return ext\r\n ? `${safeBase}.${hash}.${ext}`\r\n : `${safeBase}.${hash}`;\r\n}\r\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
declare function getArrayBuffer(input: any): Promise<ArrayBuffer>;
|
|
2
|
+
declare function hashContent(input: any, hashLength?: number): Promise<string>;
|
|
3
|
+
declare function sanitizeFilename(name: string): string;
|
|
4
|
+
interface HashedFilenameOptions {
|
|
5
|
+
hashLength?: number;
|
|
6
|
+
}
|
|
7
|
+
declare function getHashedFilename(input: any, originalName: string, options?: HashedFilenameOptions): Promise<string>;
|
|
8
|
+
|
|
9
|
+
export { type HashedFilenameOptions, getArrayBuffer, getHashedFilename, hashContent, sanitizeFilename };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
declare function getArrayBuffer(input: any): Promise<ArrayBuffer>;
|
|
2
|
+
declare function hashContent(input: any, hashLength?: number): Promise<string>;
|
|
3
|
+
declare function sanitizeFilename(name: string): string;
|
|
4
|
+
interface HashedFilenameOptions {
|
|
5
|
+
hashLength?: number;
|
|
6
|
+
}
|
|
7
|
+
declare function getHashedFilename(input: any, originalName: string, options?: HashedFilenameOptions): Promise<string>;
|
|
8
|
+
|
|
9
|
+
export { type HashedFilenameOptions, getArrayBuffer, getHashedFilename, hashContent, sanitizeFilename };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/slug/index.ts
|
|
9
|
+
function convertToSlug(text, options = {}) {
|
|
10
|
+
const { separator = "-", allowDots = false } = options;
|
|
11
|
+
if (!text || typeof text !== "string") {
|
|
12
|
+
return "";
|
|
13
|
+
}
|
|
14
|
+
const escapedSeparator = separator.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
15
|
+
let slug = text.toString().toLowerCase().trim().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
16
|
+
if (!allowDots) {
|
|
17
|
+
slug = slug.replace(/\./g, separator);
|
|
18
|
+
}
|
|
19
|
+
slug = slug.replace(/[\s_]+/g, separator).replace(new RegExp(`[^a-z0-9${escapedSeparator}${allowDots ? "\\." : ""}]`, "g"), "");
|
|
20
|
+
if (allowDots) {
|
|
21
|
+
slug = slug.replace(new RegExp(`[${escapedSeparator}.]+`, "g"), (match) => {
|
|
22
|
+
return match.includes(".") ? "." : separator;
|
|
23
|
+
});
|
|
24
|
+
} else {
|
|
25
|
+
slug = slug.replace(new RegExp(`${escapedSeparator}+`, "g"), separator);
|
|
26
|
+
}
|
|
27
|
+
const trimRegex = new RegExp(`^[${escapedSeparator}${allowDots ? "\\." : ""}]+|[${escapedSeparator}${allowDots ? "\\." : ""}]+$`, "g");
|
|
28
|
+
return slug.replace(trimRegex, "");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/file/index.ts
|
|
32
|
+
async function getArrayBuffer(input) {
|
|
33
|
+
if (input && typeof input.arrayBuffer === "function") {
|
|
34
|
+
return await input.arrayBuffer();
|
|
35
|
+
}
|
|
36
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(input)) {
|
|
37
|
+
return input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength);
|
|
38
|
+
}
|
|
39
|
+
if (input instanceof Uint8Array) {
|
|
40
|
+
return input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength);
|
|
41
|
+
}
|
|
42
|
+
throw new Error("Unsupported input type for hashing");
|
|
43
|
+
}
|
|
44
|
+
async function hashContent(input, hashLength = 8) {
|
|
45
|
+
const buffer = await getArrayBuffer(input);
|
|
46
|
+
const cryptoAPI = typeof crypto !== "undefined" ? crypto : typeof window !== "undefined" ? window.crypto : void 0;
|
|
47
|
+
if (cryptoAPI?.subtle?.digest) {
|
|
48
|
+
const hashBuffer = await cryptoAPI.subtle.digest("SHA-256", buffer);
|
|
49
|
+
return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, hashLength);
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const nodeCrypto = __require("crypto");
|
|
53
|
+
const hash = nodeCrypto.createHash("sha256").update(Buffer.from(buffer)).digest("hex");
|
|
54
|
+
return hash.slice(0, hashLength);
|
|
55
|
+
} catch (e) {
|
|
56
|
+
console.warn("Crypto fallback failed", e);
|
|
57
|
+
throw new Error("No crypto implementation found");
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function sanitizeFilename(name) {
|
|
61
|
+
return convertToSlug(name, {
|
|
62
|
+
allowDots: true
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
async function getHashedFilename(input, originalName, options = {}) {
|
|
66
|
+
const { hashLength = 8 } = options;
|
|
67
|
+
const hash = await hashContent(input, hashLength);
|
|
68
|
+
const dotIndex = originalName.lastIndexOf(".");
|
|
69
|
+
const base = dotIndex > 0 ? originalName.slice(0, dotIndex) : originalName;
|
|
70
|
+
const ext = dotIndex > 0 ? originalName.slice(dotIndex + 1) : "";
|
|
71
|
+
const safeBase = sanitizeFilename(base);
|
|
72
|
+
return ext ? `${safeBase}.${hash}.${ext}` : `${safeBase}.${hash}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { getArrayBuffer, getHashedFilename, hashContent, sanitizeFilename };
|
|
76
|
+
//# sourceMappingURL=index.js.map
|
|
77
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/slug/index.ts","../../src/file/index.ts"],"names":[],"mappings":";;;;;;;;AAuEO,SAAS,aAAA,CACd,IAAA,EACA,OAAA,GAAuD,EAAC,EAChD;AACR,EAAA,MAAM,EAAE,SAAA,GAAY,GAAA,EAAK,SAAA,GAAY,OAAM,GAAI,OAAA;AAE/C,EAAA,IAAI,CAAC,IAAA,IAAQ,OAAO,IAAA,KAAS,QAAA,EAAU;AACrC,IAAA,OAAO,EAAA;AAAA;AAIT,EAAA,MAAM,gBAAA,GAAmB,SAAA,CAAU,OAAA,CAAQ,qBAAA,EAAuB,MAAM,CAAA;AAExE,EAAA,IAAI,IAAA,GAAO,IAAA,CACR,QAAA,EAAS,CACT,WAAA,EAAY,CACZ,IAAA,EAAK,CAEL,SAAA,CAAU,KAAK,CAAA,CACf,OAAA,CAAQ,oBAAoB,EAAE,CAAA;AAEjC,EAAA,IAAI,CAAC,SAAA,EAAW;AAEd,IAAA,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,KAAA,EAAO,SAAS,CAAA;AAAA;AAGtC,EAAA,IAAA,GAAO,KAEJ,OAAA,CAAQ,SAAA,EAAW,SAAS,CAAA,CAE5B,QAAQ,IAAI,MAAA,CAAO,CAAA,QAAA,EAAW,gBAAgB,GAAG,SAAA,GAAY,KAAA,GAAQ,EAAE,CAAA,CAAA,CAAA,EAAK,GAAG,GAAG,EAAE,CAAA;AAEvF,EAAA,IAAI,SAAA,EAAW;AAGb,IAAA,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,IAAI,MAAA,CAAO,CAAA,CAAA,EAAI,gBAAgB,CAAA,GAAA,CAAA,EAAO,GAAG,CAAA,EAAG,CAAC,KAAA,KAAU;AACzE,MAAA,OAAO,KAAA,CAAM,QAAA,CAAS,GAAG,CAAA,GAAI,GAAA,GAAM,SAAA;AAAA,KACpC,CAAA;AAAA,GACH,MAAO;AAEL,IAAA,IAAA,GAAO,IAAA,CAAK,QAAQ,IAAI,MAAA,CAAO,GAAG,gBAAgB,CAAA,CAAA,CAAA,EAAK,GAAG,CAAA,EAAG,SAAS,CAAA;AAAA;AAIxE,EAAA,MAAM,YAAY,IAAI,MAAA,CAAO,CAAA,EAAA,EAAK,gBAAgB,GAAG,SAAA,GAAY,KAAA,GAAQ,EAAE,CAAA,IAAA,EAAO,gBAAgB,CAAA,EAAG,SAAA,GAAY,KAAA,GAAQ,EAAE,OAAO,GAAG,CAAA;AACrI,EAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,SAAA,EAAW,EAAE,CAAA;AACnC;;;AC9GA,eAAsB,eAAe,KAAA,EAAkC;AAErE,EAAA,IAAI,KAAA,IAAS,OAAO,KAAA,CAAM,WAAA,KAAgB,UAAA,EAAY;AACpD,IAAA,OAAO,MAAM,MAAM,WAAA,EAAY;AAAA;AAIjC,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,MAAA,CAAO,QAAA,CAAS,KAAK,CAAA,EAAG;AAG3D,IAAA,OAAO,KAAA,CAAM,OAAO,KAAA,CAAM,KAAA,CAAM,YAAY,KAAA,CAAM,UAAA,GAAa,MAAM,UAAU,CAAA;AAAA;AAGjF,EAAA,IAAI,iBAAiB,UAAA,EAAY;AAC9B,IAAA,OAAO,KAAA,CAAM,OAAO,KAAA,CAAM,KAAA,CAAM,YAAY,KAAA,CAAM,UAAA,GAAa,MAAM,UAAU,CAAA;AAAA;AAGlF,EAAA,MAAM,IAAI,MAAM,oCAAoC,CAAA;AACtD;AAEA,eAAsB,WAAA,CAAY,KAAA,EAAY,UAAA,GAAa,CAAA,EAAoB;AAC7E,EAAA,MAAM,MAAA,GAAS,MAAM,cAAA,CAAe,KAAK,CAAA;AAIzC,EAAA,MAAM,SAAA,GAAa,OAAO,MAAA,KAAW,WAAA,GAAc,SAAU,OAAO,MAAA,KAAW,WAAA,GAAc,MAAA,CAAO,MAAA,GAAS,MAAA;AAE7G,EAAA,IAAI,SAAA,EAAW,QAAQ,MAAA,EAAQ;AAC7B,IAAA,MAAM,aAAa,MAAM,SAAA,CAAU,MAAA,CAAO,MAAA,CAAO,WAAW,MAAM,CAAA;AAClE,IAAA,OAAO,KAAA,CAAM,KAAK,IAAI,UAAA,CAAW,UAAU,CAAC,CAAA,CACzC,GAAA,CAAI,CAAA,CAAA,KAAK,CAAA,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,QAAA,CAAS,CAAA,EAAG,GAAG,CAAC,CAAA,CACxC,KAAK,EAAE,CAAA,CACP,KAAA,CAAM,CAAA,EAAG,UAAU,CAAA;AAAA;AAKxB,EAAA,IAAI;AAGD,IAAA,MAAM,UAAA,GAAa,UAAQ,QAAQ,CAAA;AACnC,IAAA,MAAM,IAAA,GAAO,UAAA,CAAW,UAAA,CAAW,QAAQ,CAAA,CAAE,MAAA,CAAO,MAAA,CAAO,IAAA,CAAK,MAAM,CAAC,CAAA,CAAE,MAAA,CAAO,KAAK,CAAA;AACrF,IAAA,OAAO,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,UAAU,CAAA;AAAA,WACzB,CAAA,EAAG;AACR,IAAA,OAAA,CAAQ,IAAA,CAAK,0BAA0B,CAAC,CAAA;AACxC,IAAA,MAAM,IAAI,MAAM,gCAAgC,CAAA;AAAA;AAEtD;AAEO,SAAS,iBAAiB,IAAA,EAAsB;AACrD,EAAA,OAAO,cAAc,IAAA,EAAK;AAAA,IACxB,SAAA,EAAW;AAAA,GACZ,CAAA;AACH;AAMA,eAAsB,iBAAA,CAAkB,KAAA,EAAY,YAAA,EAAsB,OAAA,GAAiC,EAAC,EAAoB;AAC9H,EAAA,MAAM,EAAE,UAAA,GAAa,CAAA,EAAE,GAAI,OAAA;AAC3B,EAAA,MAAM,IAAA,GAAO,MAAM,WAAA,CAAY,KAAA,EAAO,UAAU,CAAA;AAEhD,EAAA,MAAM,QAAA,GAAW,YAAA,CAAa,WAAA,CAAY,GAAG,CAAA;AAC7C,EAAA,MAAM,OAAO,QAAA,GAAW,CAAA,GAAI,aAAa,KAAA,CAAM,CAAA,EAAG,QAAQ,CAAA,GAAI,YAAA;AAC9D,EAAA,MAAM,MAAM,QAAA,GAAW,CAAA,GAAI,aAAa,KAAA,CAAM,QAAA,GAAW,CAAC,CAAA,GAAI,EAAA;AAE9D,EAAA,MAAM,QAAA,GAAW,iBAAiB,IAAI,CAAA;AACtC,EAAA,OAAO,GAAA,GACH,CAAA,EAAG,QAAQ,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA,EAAI,GAAG,CAAA,CAAA,GAC1B,CAAA,EAAG,QAAQ,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA;AACzB","file":"index.js","sourcesContent":["/**\r\n * Checks if a string is a valid slug\r\n * A valid slug contains only lowercase alphanumeric characters and hyphens,\r\n * with no consecutive hyphens and no leading/trailing hyphens\r\n * \r\n * @param slug - The string to validate\r\n * @returns True if the string is a valid slug, false otherwise\r\n * \r\n * @example\r\n * ```ts\r\n * isValidSlug('hello-world') // true\r\n * isValidSlug('hello--world') // false (consecutive hyphens)\r\n * isValidSlug('Hello-World') // false (uppercase)\r\n * isValidSlug('-hello-world') // false (leading hyphen)\r\n * isValidSlug('hello_world') // false (underscore not allowed)\r\n * ```\r\n */\r\nexport function isValidSlug(\r\n slug: string,\r\n options: { allowDots?: boolean } = {}\r\n): boolean {\r\n if (!slug || typeof slug !== 'string') {\r\n return false;\r\n }\r\n\r\n const { allowDots = false } = options;\r\n\r\n // Check if slug matches the pattern:\r\n // - starts with alphanumeric\r\n // - ends with alphanumeric\r\n // - contains only lowercase alphanumeric and single hyphens (and dots if allowed)\r\n // - no consecutive separators (hyphens or dots)\r\n \r\n if (allowDots) {\r\n // pattern allowing dots:\r\n // starts with alphanumeric\r\n // middle: alphanumeric or single hyphen/dot followed by alphanumeric\r\n // ends with alphanumeric\r\n const slugWithDotsPattern = /^[a-z0-9]+(?:[-.][a-z0-9]+)*$/;\r\n return slugWithDotsPattern.test(slug);\r\n }\r\n\r\n const slugPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\r\n \r\n return slugPattern.test(slug);\r\n}\r\n\r\n/**\r\n * Converts a string to a URL-safe slug\r\n * - Converts to lowercase\r\n * - Removes special characters\r\n * - Replaces spaces and underscores with hyphens\r\n * - Removes consecutive hyphens\r\n * - Trims leading and trailing hyphens\r\n * \r\n * @param text - The string to convert to a slug\r\n * @param options - Optional configuration\r\n * @param options.separator - Character to use as separator (default: '-')\r\n * @returns A URL-safe slug\r\n * \r\n * @example\r\n * ```ts\r\n * convertToSlug('Hello World') // 'hello-world'\r\n * convertToSlug('Hello World!!!') // 'hello-world'\r\n * convertToSlug('Hello_World') // 'hello-world'\r\n * convertToSlug(' Hello World ') // 'hello-world'\r\n * convertToSlug('Hello---World') // 'hello-world'\r\n * convertToSlug('Café & Restaurant') // 'cafe-restaurant'\r\n * convertToSlug('Product #123') // 'product-123'\r\n * ```\r\n */\r\nexport function convertToSlug(\r\n text: string,\r\n options: { separator?: string; allowDots?: boolean } = {}\r\n): string {\r\n const { separator = '-', allowDots = false } = options;\r\n\r\n if (!text || typeof text !== 'string') {\r\n return '';\r\n }\r\n\r\n // Escape separator for use in regex char class\r\n const escapedSeparator = separator.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\r\n\r\n let slug = text\r\n .toString()\r\n .toLowerCase()\r\n .trim()\r\n // Remove accents and diacritics\r\n .normalize('NFD')\r\n .replace(/[\\u0300-\\u036f]/g, '');\r\n\r\n if (!allowDots) {\r\n // Replace dots with separator\r\n slug = slug.replace(/\\./g, separator);\r\n }\r\n\r\n slug = slug\r\n // Replace spaces, underscores with separator\r\n .replace(/[\\s_]+/g, separator)\r\n // Remove all non-alphanumeric characters except the separator (and dot if allowed)\r\n .replace(new RegExp(`[^a-z0-9${escapedSeparator}${allowDots ? '\\\\.' : ''}]`, 'g'), '');\r\n\r\n if (allowDots) {\r\n // Collapse consecutive separators/dots\r\n // If sequence contains a dot, we generally want to keep it as a dot (e.g. file.-extension -> file.extension)\r\n slug = slug.replace(new RegExp(`[${escapedSeparator}.]+`, 'g'), (match) => {\r\n return match.includes('.') ? '.' : separator;\r\n });\r\n } else {\r\n // Replace multiple consecutive separators with single separator\r\n slug = slug.replace(new RegExp(`${escapedSeparator}+`, 'g'), separator);\r\n }\r\n \r\n // Remove leading and trailing separators (and dots)\r\n const trimRegex = new RegExp(`^[${escapedSeparator}${allowDots ? '\\\\.' : ''}]+|[${escapedSeparator}${allowDots ? '\\\\.' : ''}]+$`, 'g');\r\n return slug.replace(trimRegex, '');\r\n}\r\n\r\n/**\r\n * Generates a unique slug by appending a number if the slug already exists\r\n * \r\n * @param baseSlug - The base slug to make unique\r\n * @param existingSlugs - Array of existing slugs to check against\r\n * @param options - Optional configuration\r\n * @param options.separator - Character to use before the number (default: '-')\r\n * @returns A unique slug\r\n * \r\n * @example\r\n * ```ts\r\n * generateUniqueSlug('hello-world', ['hello-world']) // 'hello-world-1'\r\n * generateUniqueSlug('hello-world', ['hello-world', 'hello-world-1']) // 'hello-world-2'\r\n * generateUniqueSlug('hello-world', []) // 'hello-world'\r\n * ```\r\n */\r\nexport function generateUniqueSlug(\r\n baseSlug: string,\r\n existingSlugs: string[],\r\n options: { separator?: string } = {}\r\n): string {\r\n const { separator = '-' } = options;\r\n\r\n if (!existingSlugs.includes(baseSlug)) {\r\n return baseSlug;\r\n }\r\n\r\n let counter = 1;\r\n let uniqueSlug = `${baseSlug}${separator}${counter}`;\r\n\r\n while (existingSlugs.includes(uniqueSlug)) {\r\n counter++;\r\n uniqueSlug = `${baseSlug}${separator}${counter}`;\r\n }\r\n\r\n return uniqueSlug;\r\n}\r\n\r\n/**\r\n * Creates a Zod refinement function for slug validation\r\n * Use with z.string().refine() or z.string().superRefine()\r\n * \r\n * @param optionsOrMessage - Custom error message or options object\r\n * @returns Refinement function for Zod\r\n * \r\n * @example\r\n * ```ts\r\n * import { z } from 'zod';\r\n * import { zodSlugValidation } from '@digicroz/js-kit';\r\n * \r\n * // Basic usage\r\n * const schema = z.object({\r\n * slug: z.string().refine(zodSlugValidation(), {\r\n * message: 'Invalid slug format'\r\n * })\r\n * });\r\n * \r\n * // Allow dots (e.g. for filenames)\r\n * const fileSchema = z.object({\r\n * filename: z.string().refine(zodSlugValidation({ allowDots: true }), {\r\n * message: 'Invalid filename format'\r\n * })\r\n * });\r\n * ```\r\n */\r\nexport function zodSlugValidation(\r\n optionsOrMessage?: string | { message?: string; allowDots?: boolean }\r\n) {\r\n const options =\r\n typeof optionsOrMessage === 'string'\r\n ? { message: optionsOrMessage }\r\n : optionsOrMessage || {};\r\n \r\n return (val: string) => isValidSlug(val, { allowDots: options.allowDots });\r\n}\r\n\r\n/**\r\n * Creates a Zod transform function that converts strings to slugs\r\n * Use with z.string().transform()\r\n * \r\n * @param options - Optional configuration\r\n * @param options.separator - Character to use as separator (default: '-')\r\n * @param options.allowDots - Whether to allow dots in the slug (default: false)\r\n * @returns Transform function for Zod\r\n * \r\n * @example\r\n * ```ts\r\n * import { z } from 'zod';\r\n * import { zodSlugTransform } from '@digicroz/js-kit';\r\n * \r\n * const schema = z.object({\r\n * title: z.string(),\r\n * slug: z.string().transform(zodSlugTransform())\r\n * });\r\n * \r\n * schema.parse({ title: 'Hello', slug: 'Hello World!!!' })\r\n * // { title: 'Hello', slug: 'hello-world' }\r\n * ```\r\n */\r\nexport function zodSlugTransform(options?: { separator?: string; allowDots?: boolean }) {\r\n return (val: string) => convertToSlug(val, options);\r\n}\r\n\r\n/**\r\n * Pre-configured Zod schema for slug validation\r\n * Validates that the string is a valid slug format\r\n * \r\n * @example\r\n * ```ts\r\n * import { z } from 'zod';\r\n * import { slugSchema } from '@digicroz/js-kit';\r\n * \r\n * const postSchema = z.object({\r\n * slug: slugSchema\r\n * });\r\n * \r\n * postSchema.parse({ slug: 'hello-world' }); // ✓ Valid\r\n * postSchema.parse({ slug: 'Hello World' }); // ✗ Invalid\r\n * ```\r\n */\r\nexport const slugSchema = {\r\n /**\r\n * Get a Zod string schema that validates slug format\r\n * Requires zod to be installed: npm install zod\r\n */\r\n create: (\r\n customMessageOrOptions?: string | { message?: string; allowDots?: boolean }\r\n ) => {\r\n // Dynamic import to avoid making zod a required dependency\r\n const options =\r\n typeof customMessageOrOptions === 'string'\r\n ? { message: customMessageOrOptions }\r\n : customMessageOrOptions || {};\r\n \r\n const message =\r\n options.message ||\r\n 'Must be a valid slug (lowercase, alphanumeric, and hyphens only, no consecutive hyphens)';\r\n\r\n return {\r\n _type: 'slug-validator' as const,\r\n validate: zodSlugValidation(options),\r\n message\r\n };\r\n }\r\n};\r\n\r\n/**\r\n * Pre-configured Zod schema that auto-converts strings to slugs\r\n * Automatically transforms any string input into a valid slug\r\n * \r\n * @example\r\n * ```ts\r\n * import { z } from 'zod';\r\n * import { autoSlugSchema } from '@digicroz/js-kit';\r\n * \r\n * const postSchema = z.object({\r\n * title: z.string(),\r\n * slug: z.string().transform(autoSlugSchema.transform())\r\n * });\r\n * \r\n * postSchema.parse({ title: 'My Post', slug: 'Hello World!!!' })\r\n * // { title: 'My Post', slug: 'hello-world' }\r\n * ```\r\n */\r\nexport const autoSlugSchema = {\r\n /**\r\n * Get a transform function for Zod\r\n */\r\n transform: (options?: { separator?: string }) => zodSlugTransform(options)\r\n};\r\n","\r\n// Universal File/Buffer types mapping for TS\r\n// \"File\" and \"Blob\" are DOM types. \"Buffer\" is a Node type.\r\n// We use 'any' or check existence to avoid build errors in pure Node/Browser setups without full libs.\r\n\r\nimport { convertToSlug } from \"../slug\";\r\n\r\nexport async function getArrayBuffer(input: any): Promise<ArrayBuffer> {\r\n // Browser: File or Blob\r\n if (input && typeof input.arrayBuffer === 'function') {\r\n return await input.arrayBuffer();\r\n }\r\n\r\n // Node: Buffer or Uint8Array\r\n if (typeof Buffer !== \"undefined\" && Buffer.isBuffer(input)) {\r\n // Buffer shares memory with the underlying ArrayBuffer\r\n // We must slice it to get the correct view if it's a subarray\r\n return input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength) as ArrayBuffer;\r\n }\r\n\r\n if (input instanceof Uint8Array) {\r\n return input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength) as ArrayBuffer;\r\n }\r\n\r\n throw new Error(\"Unsupported input type for hashing\");\r\n}\r\n\r\nexport async function hashContent(input: any, hashLength = 8): Promise<string> {\r\n const buffer = await getArrayBuffer(input);\r\n\r\n // Browser + Node 15+ (if global crypto is available or polyfilled)\r\n // We check for globalThis.crypto or window.crypto\r\n const cryptoAPI = (typeof crypto !== \"undefined\" ? crypto : (typeof window !== \"undefined\" ? window.crypto : undefined));\r\n \r\n if (cryptoAPI?.subtle?.digest) {\r\n const hashBuffer = await cryptoAPI.subtle.digest(\"SHA-256\", buffer);\r\n return Array.from(new Uint8Array(hashBuffer))\r\n .map(b => b.toString(16).padStart(2, \"0\"))\r\n .join(\"\")\r\n .slice(0, hashLength);\r\n }\r\n\r\n // Node fallback (using require for CommonJS/Node compatibility)\r\n // We use logic to avoid bundler errors if possible, but user provided require('crypto')\r\n try {\r\n // Dynamic require to avoid static analysis issues in some bundlers if targeting browser only\r\n // However, in a general js-kit, we can just try it.\r\n const nodeCrypto = require(\"crypto\");\r\n const hash = nodeCrypto.createHash(\"sha256\").update(Buffer.from(buffer)).digest(\"hex\");\r\n return hash.slice(0, hashLength);\r\n } catch (e) {\r\n console.warn(\"Crypto fallback failed\", e);\r\n throw new Error(\"No crypto implementation found\");\r\n }\r\n}\r\n\r\nexport function sanitizeFilename(name: string): string {\r\n return convertToSlug(name,{\r\n allowDots: true,\r\n });\r\n}\r\n\r\nexport interface HashedFilenameOptions {\r\n hashLength?: number;\r\n}\r\n\r\nexport async function getHashedFilename(input: any, originalName: string, options: HashedFilenameOptions = {}): Promise<string> {\r\n const { hashLength = 8 } = options;\r\n const hash = await hashContent(input, hashLength);\r\n\r\n const dotIndex = originalName.lastIndexOf(\".\");\r\n const base = dotIndex > 0 ? originalName.slice(0, dotIndex) : originalName;\r\n const ext = dotIndex > 0 ? originalName.slice(dotIndex + 1) : \"\";\r\n\r\n const safeBase = sanitizeFilename(base);\r\n return ext\r\n ? `${safeBase}.${hash}.${ext}`\r\n : `${safeBase}.${hash}`;\r\n}\r\n"]}
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
4
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
5
|
+
}) : x)(function(x) {
|
|
6
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
7
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
8
|
+
});
|
|
9
|
+
|
|
3
10
|
// src/array/index.ts
|
|
4
11
|
function chunk(array, size) {
|
|
5
12
|
if (size <= 0) {
|
|
@@ -93,19 +100,38 @@ function randomStringWithFixedLength(length) {
|
|
|
93
100
|
}
|
|
94
101
|
|
|
95
102
|
// src/slug/index.ts
|
|
96
|
-
function isValidSlug(slug) {
|
|
103
|
+
function isValidSlug(slug, options = {}) {
|
|
97
104
|
if (!slug || typeof slug !== "string") {
|
|
98
105
|
return false;
|
|
99
106
|
}
|
|
107
|
+
const { allowDots = false } = options;
|
|
108
|
+
if (allowDots) {
|
|
109
|
+
const slugWithDotsPattern = /^[a-z0-9]+(?:[-.][a-z0-9]+)*$/;
|
|
110
|
+
return slugWithDotsPattern.test(slug);
|
|
111
|
+
}
|
|
100
112
|
const slugPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
101
113
|
return slugPattern.test(slug);
|
|
102
114
|
}
|
|
103
115
|
function convertToSlug(text, options = {}) {
|
|
104
|
-
const { separator = "-" } = options;
|
|
116
|
+
const { separator = "-", allowDots = false } = options;
|
|
105
117
|
if (!text || typeof text !== "string") {
|
|
106
118
|
return "";
|
|
107
119
|
}
|
|
108
|
-
|
|
120
|
+
const escapedSeparator = separator.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
121
|
+
let slug = text.toString().toLowerCase().trim().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
122
|
+
if (!allowDots) {
|
|
123
|
+
slug = slug.replace(/\./g, separator);
|
|
124
|
+
}
|
|
125
|
+
slug = slug.replace(/[\s_]+/g, separator).replace(new RegExp(`[^a-z0-9${escapedSeparator}${allowDots ? "\\." : ""}]`, "g"), "");
|
|
126
|
+
if (allowDots) {
|
|
127
|
+
slug = slug.replace(new RegExp(`[${escapedSeparator}.]+`, "g"), (match) => {
|
|
128
|
+
return match.includes(".") ? "." : separator;
|
|
129
|
+
});
|
|
130
|
+
} else {
|
|
131
|
+
slug = slug.replace(new RegExp(`${escapedSeparator}+`, "g"), separator);
|
|
132
|
+
}
|
|
133
|
+
const trimRegex = new RegExp(`^[${escapedSeparator}${allowDots ? "\\." : ""}]+|[${escapedSeparator}${allowDots ? "\\." : ""}]+$`, "g");
|
|
134
|
+
return slug.replace(trimRegex, "");
|
|
109
135
|
}
|
|
110
136
|
function generateUniqueSlug(baseSlug, existingSlugs, options = {}) {
|
|
111
137
|
const { separator = "-" } = options;
|
|
@@ -120,8 +146,9 @@ function generateUniqueSlug(baseSlug, existingSlugs, options = {}) {
|
|
|
120
146
|
}
|
|
121
147
|
return uniqueSlug;
|
|
122
148
|
}
|
|
123
|
-
function zodSlugValidation(
|
|
124
|
-
|
|
149
|
+
function zodSlugValidation(optionsOrMessage) {
|
|
150
|
+
const options = typeof optionsOrMessage === "string" ? { } : optionsOrMessage || {};
|
|
151
|
+
return (val) => isValidSlug(val, { allowDots: options.allowDots });
|
|
125
152
|
}
|
|
126
153
|
function zodSlugTransform(options) {
|
|
127
154
|
return (val) => convertToSlug(val, options);
|
|
@@ -131,11 +158,13 @@ var slugSchema = {
|
|
|
131
158
|
* Get a Zod string schema that validates slug format
|
|
132
159
|
* Requires zod to be installed: npm install zod
|
|
133
160
|
*/
|
|
134
|
-
create: (
|
|
161
|
+
create: (customMessageOrOptions) => {
|
|
162
|
+
const options = typeof customMessageOrOptions === "string" ? { message: customMessageOrOptions } : customMessageOrOptions || {};
|
|
163
|
+
const message = options.message || "Must be a valid slug (lowercase, alphanumeric, and hyphens only, no consecutive hyphens)";
|
|
135
164
|
return {
|
|
136
165
|
_type: "slug-validator",
|
|
137
|
-
validate: zodSlugValidation(),
|
|
138
|
-
message
|
|
166
|
+
validate: zodSlugValidation(options),
|
|
167
|
+
message
|
|
139
168
|
};
|
|
140
169
|
}
|
|
141
170
|
};
|
|
@@ -387,6 +416,134 @@ var requireEnumValue = (enumArray, value) => {
|
|
|
387
416
|
return value;
|
|
388
417
|
};
|
|
389
418
|
|
|
419
|
+
// src/name/index.ts
|
|
420
|
+
function isValidName(name, options = {}) {
|
|
421
|
+
if (!name || typeof name !== "string") {
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
const { allowedSpecialChars = [" "] } = options;
|
|
425
|
+
const escapedChars = allowedSpecialChars.map((char) => char.replace(/[.*+?^${}()|[\]\\-]/g, "\\$&")).join("");
|
|
426
|
+
const pattern = new RegExp(
|
|
427
|
+
`^[a-zA-Z0-9]+(?:[${escapedChars}][a-zA-Z0-9]+)*$`
|
|
428
|
+
);
|
|
429
|
+
return pattern.test(name);
|
|
430
|
+
}
|
|
431
|
+
function normalizeName(text, options = {}) {
|
|
432
|
+
if (!text || typeof text !== "string") {
|
|
433
|
+
return "";
|
|
434
|
+
}
|
|
435
|
+
const { allowedSpecialChars = [" "] } = options;
|
|
436
|
+
const escapedChars = allowedSpecialChars.map((char) => char.replace(/[.*+?^${}()|[\]\\-]/g, "\\$&")).join("");
|
|
437
|
+
let normalized = text.trim();
|
|
438
|
+
normalized = normalized.replace(
|
|
439
|
+
new RegExp(`[^a-zA-Z0-9${escapedChars}]`, "g"),
|
|
440
|
+
""
|
|
441
|
+
);
|
|
442
|
+
normalized = normalized.replace(
|
|
443
|
+
new RegExp(`[${escapedChars}]{2,}`, "g"),
|
|
444
|
+
(match) => match[0]
|
|
445
|
+
);
|
|
446
|
+
normalized = normalized.replace(
|
|
447
|
+
new RegExp(`^[${escapedChars}]+|[${escapedChars}]+$`, "g"),
|
|
448
|
+
""
|
|
449
|
+
);
|
|
450
|
+
return normalized;
|
|
451
|
+
}
|
|
452
|
+
function zodNameValidation(optionsOrMessage) {
|
|
453
|
+
const options = typeof optionsOrMessage === "string" ? { } : optionsOrMessage || {};
|
|
454
|
+
return (val) => isValidName(val, { allowedSpecialChars: options.allowedSpecialChars });
|
|
455
|
+
}
|
|
456
|
+
function zodNameTransform(options) {
|
|
457
|
+
return (val) => normalizeName(val, options);
|
|
458
|
+
}
|
|
459
|
+
var nameSchema = {
|
|
460
|
+
create: (customMessageOrOptions) => {
|
|
461
|
+
const options = typeof customMessageOrOptions === "string" ? { message: customMessageOrOptions } : customMessageOrOptions || {};
|
|
462
|
+
const message = options.message || "Invalid name format. Must be alphanumeric, no leading/trailing/consecutive special characters.";
|
|
463
|
+
return {
|
|
464
|
+
_type: "name-validator",
|
|
465
|
+
validate: zodNameValidation(options),
|
|
466
|
+
message
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
// src/std-response/index.ts
|
|
472
|
+
var stdResponse = Object.freeze({
|
|
473
|
+
success: (result) => ({
|
|
474
|
+
status: "success",
|
|
475
|
+
result
|
|
476
|
+
}),
|
|
477
|
+
error: (code, message) => ({
|
|
478
|
+
status: "error",
|
|
479
|
+
error: { code, message }
|
|
480
|
+
})
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
// src/base64/index.ts
|
|
484
|
+
function encodeToBase64(obj) {
|
|
485
|
+
if (obj === null || obj === void 0) {
|
|
486
|
+
throw new Error("Cannot encode null or undefined");
|
|
487
|
+
}
|
|
488
|
+
const json = JSON.stringify(obj);
|
|
489
|
+
return Buffer.from(json, "utf8").toString("base64");
|
|
490
|
+
}
|
|
491
|
+
function decodeFromBase64(base64) {
|
|
492
|
+
if (!base64 || typeof base64 !== "string") {
|
|
493
|
+
throw new Error("Invalid base64 input");
|
|
494
|
+
}
|
|
495
|
+
try {
|
|
496
|
+
const json = Buffer.from(base64, "base64").toString("utf8");
|
|
497
|
+
return JSON.parse(json);
|
|
498
|
+
} catch (error) {
|
|
499
|
+
throw new Error("Failed to decode base64 string");
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// src/file/index.ts
|
|
504
|
+
async function getArrayBuffer(input) {
|
|
505
|
+
if (input && typeof input.arrayBuffer === "function") {
|
|
506
|
+
return await input.arrayBuffer();
|
|
507
|
+
}
|
|
508
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(input)) {
|
|
509
|
+
return input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength);
|
|
510
|
+
}
|
|
511
|
+
if (input instanceof Uint8Array) {
|
|
512
|
+
return input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength);
|
|
513
|
+
}
|
|
514
|
+
throw new Error("Unsupported input type for hashing");
|
|
515
|
+
}
|
|
516
|
+
async function hashContent(input, hashLength = 8) {
|
|
517
|
+
const buffer = await getArrayBuffer(input);
|
|
518
|
+
const cryptoAPI = typeof crypto !== "undefined" ? crypto : typeof window !== "undefined" ? window.crypto : void 0;
|
|
519
|
+
if (cryptoAPI?.subtle?.digest) {
|
|
520
|
+
const hashBuffer = await cryptoAPI.subtle.digest("SHA-256", buffer);
|
|
521
|
+
return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, hashLength);
|
|
522
|
+
}
|
|
523
|
+
try {
|
|
524
|
+
const nodeCrypto = __require("crypto");
|
|
525
|
+
const hash = nodeCrypto.createHash("sha256").update(Buffer.from(buffer)).digest("hex");
|
|
526
|
+
return hash.slice(0, hashLength);
|
|
527
|
+
} catch (e) {
|
|
528
|
+
console.warn("Crypto fallback failed", e);
|
|
529
|
+
throw new Error("No crypto implementation found");
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
function sanitizeFilename(name) {
|
|
533
|
+
return convertToSlug(name, {
|
|
534
|
+
allowDots: true
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
async function getHashedFilename(input, originalName, options = {}) {
|
|
538
|
+
const { hashLength = 8 } = options;
|
|
539
|
+
const hash = await hashContent(input, hashLength);
|
|
540
|
+
const dotIndex = originalName.lastIndexOf(".");
|
|
541
|
+
const base = dotIndex > 0 ? originalName.slice(0, dotIndex) : originalName;
|
|
542
|
+
const ext = dotIndex > 0 ? originalName.slice(dotIndex + 1) : "";
|
|
543
|
+
const safeBase = sanitizeFilename(base);
|
|
544
|
+
return ext ? `${safeBase}.${hash}.${ext}` : `${safeBase}.${hash}`;
|
|
545
|
+
}
|
|
546
|
+
|
|
390
547
|
exports.EnvironmentError = EnvironmentError;
|
|
391
548
|
exports.assertBrowserEnvironment = assertBrowserEnvironment;
|
|
392
549
|
exports.assertNodeEnvironment = assertNodeEnvironment;
|
|
@@ -400,31 +557,43 @@ exports.convertToInt = convertToInt;
|
|
|
400
557
|
exports.convertToSeconds = convertToSeconds;
|
|
401
558
|
exports.convertToSlug = convertToSlug;
|
|
402
559
|
exports.convertToTwoDecimalInt = convertToTwoDecimalInt;
|
|
560
|
+
exports.decodeFromBase64 = decodeFromBase64;
|
|
561
|
+
exports.encodeToBase64 = encodeToBase64;
|
|
403
562
|
exports.generateUniqueSlug = generateUniqueSlug;
|
|
563
|
+
exports.getArrayBuffer = getArrayBuffer;
|
|
404
564
|
exports.getEnvironment = getEnvironment;
|
|
405
565
|
exports.getFullYear = getFullYear;
|
|
566
|
+
exports.getHashedFilename = getHashedFilename;
|
|
406
567
|
exports.getUnixTimestamp = getUnixTimestamp;
|
|
407
568
|
exports.getUnixTimestampMs = getUnixTimestampMs;
|
|
569
|
+
exports.hashContent = hashContent;
|
|
408
570
|
exports.inRange = inRange;
|
|
409
571
|
exports.isBrowserEnvironment = isBrowserEnvironment;
|
|
410
572
|
exports.isNodeEnvironment = isNodeEnvironment;
|
|
573
|
+
exports.isValidName = isValidName;
|
|
411
574
|
exports.isValidSlug = isValidSlug;
|
|
412
575
|
exports.isWebWorkerEnvironment = isWebWorkerEnvironment;
|
|
576
|
+
exports.nameSchema = nameSchema;
|
|
577
|
+
exports.normalizeName = normalizeName;
|
|
413
578
|
exports.objectKeysToCamelCase = objectKeysToCamelCase;
|
|
414
579
|
exports.objectKeysToSnakeCase = objectKeysToSnakeCase;
|
|
415
580
|
exports.parseEnumValue = parseEnumValue;
|
|
416
581
|
exports.randomNumberWithFixedLength = randomNumberWithFixedLength;
|
|
417
582
|
exports.randomStringWithFixedLength = randomStringWithFixedLength;
|
|
418
583
|
exports.requireEnumValue = requireEnumValue;
|
|
584
|
+
exports.sanitizeFilename = sanitizeFilename;
|
|
419
585
|
exports.sleep = sleep;
|
|
420
586
|
exports.sleepMinutes = sleepMinutes;
|
|
421
587
|
exports.sleepMs = sleepMs;
|
|
422
588
|
exports.sleepSeconds = sleepSeconds;
|
|
423
589
|
exports.sleepUntil = sleepUntil;
|
|
424
590
|
exports.slugSchema = slugSchema;
|
|
591
|
+
exports.stdResponse = stdResponse;
|
|
425
592
|
exports.toCamelCase = toCamelCase;
|
|
426
593
|
exports.toSnakeCase = toSnakeCase;
|
|
427
594
|
exports.truncateText = truncateText;
|
|
595
|
+
exports.zodNameTransform = zodNameTransform;
|
|
596
|
+
exports.zodNameValidation = zodNameValidation;
|
|
428
597
|
exports.zodSlugTransform = zodSlugTransform;
|
|
429
598
|
exports.zodSlugValidation = zodSlugValidation;
|
|
430
599
|
//# sourceMappingURL=index.cjs.map
|