@digicroz/js-kit 1.0.10 → 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 +90 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +84 -1
- package/dist/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) {
|
|
@@ -461,6 +468,82 @@ var nameSchema = {
|
|
|
461
468
|
}
|
|
462
469
|
};
|
|
463
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
|
+
|
|
464
547
|
exports.EnvironmentError = EnvironmentError;
|
|
465
548
|
exports.assertBrowserEnvironment = assertBrowserEnvironment;
|
|
466
549
|
exports.assertNodeEnvironment = assertNodeEnvironment;
|
|
@@ -474,11 +557,16 @@ exports.convertToInt = convertToInt;
|
|
|
474
557
|
exports.convertToSeconds = convertToSeconds;
|
|
475
558
|
exports.convertToSlug = convertToSlug;
|
|
476
559
|
exports.convertToTwoDecimalInt = convertToTwoDecimalInt;
|
|
560
|
+
exports.decodeFromBase64 = decodeFromBase64;
|
|
561
|
+
exports.encodeToBase64 = encodeToBase64;
|
|
477
562
|
exports.generateUniqueSlug = generateUniqueSlug;
|
|
563
|
+
exports.getArrayBuffer = getArrayBuffer;
|
|
478
564
|
exports.getEnvironment = getEnvironment;
|
|
479
565
|
exports.getFullYear = getFullYear;
|
|
566
|
+
exports.getHashedFilename = getHashedFilename;
|
|
480
567
|
exports.getUnixTimestamp = getUnixTimestamp;
|
|
481
568
|
exports.getUnixTimestampMs = getUnixTimestampMs;
|
|
569
|
+
exports.hashContent = hashContent;
|
|
482
570
|
exports.inRange = inRange;
|
|
483
571
|
exports.isBrowserEnvironment = isBrowserEnvironment;
|
|
484
572
|
exports.isNodeEnvironment = isNodeEnvironment;
|
|
@@ -493,12 +581,14 @@ exports.parseEnumValue = parseEnumValue;
|
|
|
493
581
|
exports.randomNumberWithFixedLength = randomNumberWithFixedLength;
|
|
494
582
|
exports.randomStringWithFixedLength = randomStringWithFixedLength;
|
|
495
583
|
exports.requireEnumValue = requireEnumValue;
|
|
584
|
+
exports.sanitizeFilename = sanitizeFilename;
|
|
496
585
|
exports.sleep = sleep;
|
|
497
586
|
exports.sleepMinutes = sleepMinutes;
|
|
498
587
|
exports.sleepMs = sleepMs;
|
|
499
588
|
exports.sleepSeconds = sleepSeconds;
|
|
500
589
|
exports.sleepUntil = sleepUntil;
|
|
501
590
|
exports.slugSchema = slugSchema;
|
|
591
|
+
exports.stdResponse = stdResponse;
|
|
502
592
|
exports.toCamelCase = toCamelCase;
|
|
503
593
|
exports.toSnakeCase = toSnakeCase;
|
|
504
594
|
exports.truncateText = truncateText;
|