@digicroz/js-kit 1.0.10 → 1.0.13

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 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,4 @@
1
+ declare function encodeToBase64(obj: unknown): string;
2
+ declare function decodeFromBase64<T = unknown>(base64: string): T;
3
+
4
+ export { decodeFromBase64, encodeToBase64 };
@@ -0,0 +1,4 @@
1
+ declare function encodeToBase64(obj: unknown): string;
2
+ declare function decodeFromBase64<T = unknown>(base64: string): T;
3
+
4
+ export { decodeFromBase64, encodeToBase64 };
@@ -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":"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","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, details) => ({
478
+ status: "error",
479
+ error: { code, message, details }
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;