@berkayyalcin/utilkit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Berkay Yalçın
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # utilkit
2
+
3
+ Zero-dependency devkit for everyday JavaScript/TypeScript utilities. Pure functions — works in React, Vue, Angular, Node, or plain browser scripts.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @berkayyalcin/utilkit
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```ts
14
+ import { base64Encode, base64Decode, stringLength, byteLength, safeJsonParse } from "@berkayyalcin/utilkit";
15
+
16
+ base64Encode("Türkçe metin 🎉"); // Unicode-safe, unlike native btoa
17
+ base64Decode("not-valid-base64"); // null, never throws
18
+
19
+ stringLength("😀"); // 1 (visual character)
20
+ "😀".length; // 2 (native — counts UTF-16 units, not visual chars)
21
+ byteLength("😀"); // 4 (UTF-8 byte size)
22
+
23
+ safeJsonParse("not json"); // null, never throws
24
+ safeJsonParse("not json", { empty: true }); // { empty: true } — fallback value
25
+ ```
26
+
27
+ You can also import from a specific category for smaller bundles:
28
+
29
+ ```ts
30
+ import { base64Encode } from "@berkayyalcin/utilkit/encoding";
31
+ import { stringLength } from "@berkayyalcin/utilkit/text";
32
+ import { safeJsonParse } from "@berkayyalcin/utilkit/data";
33
+ ```
34
+
35
+ ## API
36
+
37
+ | Function | Signature | Description |
38
+ |---|---|---|
39
+ | `base64Encode` | `(input: string) => string` | UTF-8-safe Base64 encode. |
40
+ | `base64Decode` | `(input: string) => string \| null` | Base64 decode; `null` on invalid input. |
41
+ | `stringLength` | `(input: string) => number` | Visual character (grapheme) count. |
42
+ | `byteLength` | `(input: string) => number` | UTF-8 byte length. |
43
+ | `safeJsonParse` | `<T>(input: string, fallback?: T) => T \| null` | `JSON.parse` that never throws. |
44
+
45
+ ## Design Principles
46
+
47
+ - **Zero dependencies.**
48
+ - **100% test coverage.**
49
+ - **Tree-shakeable** — `"sideEffects": false`, ESM + CJS builds.
50
+ - **TypeScript-first.**
51
+ - **Null-safe on invalid input** — functions that can fail return `null` (or a caller-supplied fallback) instead of throwing.
52
+ - **MIT licensed.**
53
+
54
+ ## Türkçe
55
+
56
+ `utilkit`, günlük JavaScript/TypeScript ihtiyaçları için sıfır bağımlılıklı bir devkit'tir: Unicode-güvenli Base64 kodlama, doğru string uzunluğu (karakter + byte) ve güvenli JSON ayrıştırma. Saf fonksiyonlardan oluşur — React, Vue, Angular veya Node fark etmeksizin her yerde çalışır. Kurulum ve API referansı için yukarıdaki İngilizce bölüme bakınız (fonksiyon imzaları evrenseldir).
57
+
58
+ ## License
59
+
60
+ MIT © [Berkay Yalçın](https://berkayyalcin.dev)
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/data/index.ts
21
+ var data_exports = {};
22
+ __export(data_exports, {
23
+ safeJsonParse: () => safeJsonParse
24
+ });
25
+ module.exports = __toCommonJS(data_exports);
26
+
27
+ // src/data/json.ts
28
+ function safeJsonParse(input, fallback) {
29
+ try {
30
+ return JSON.parse(input);
31
+ } catch {
32
+ return fallback ?? null;
33
+ }
34
+ }
35
+ // Annotate the CommonJS export names for ESM import in node:
36
+ 0 && (module.exports = {
37
+ safeJsonParse
38
+ });
39
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/data/index.ts","../../src/data/json.ts"],"sourcesContent":["export { safeJsonParse } from \"./json.js\";\n","/**\n * `JSON.parse` wrapped in try/catch. Returns `fallback` (or `null` if no\n * fallback is given) on invalid input instead of throwing, so callers never\n * need their own try/catch for the common \"might not be JSON\" case.\n */\nexport function safeJsonParse<T = unknown>(input: string, fallback?: T): T | null {\n try {\n return JSON.parse(input) as T;\n } catch {\n return fallback ?? null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACKO,SAAS,cAA2B,OAAe,UAAwB;AAChF,MAAI;AACF,WAAO,KAAK,MAAM,KAAK;AAAA,EACzB,QAAQ;AACN,WAAO,YAAY;AAAA,EACrB;AACF;","names":[]}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `JSON.parse` wrapped in try/catch. Returns `fallback` (or `null` if no
3
+ * fallback is given) on invalid input instead of throwing, so callers never
4
+ * need their own try/catch for the common "might not be JSON" case.
5
+ */
6
+ declare function safeJsonParse<T = unknown>(input: string, fallback?: T): T | null;
7
+
8
+ export { safeJsonParse };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `JSON.parse` wrapped in try/catch. Returns `fallback` (or `null` if no
3
+ * fallback is given) on invalid input instead of throwing, so callers never
4
+ * need their own try/catch for the common "might not be JSON" case.
5
+ */
6
+ declare function safeJsonParse<T = unknown>(input: string, fallback?: T): T | null;
7
+
8
+ export { safeJsonParse };
@@ -0,0 +1,12 @@
1
+ // src/data/json.ts
2
+ function safeJsonParse(input, fallback) {
3
+ try {
4
+ return JSON.parse(input);
5
+ } catch {
6
+ return fallback ?? null;
7
+ }
8
+ }
9
+ export {
10
+ safeJsonParse
11
+ };
12
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/data/json.ts"],"sourcesContent":["/**\n * `JSON.parse` wrapped in try/catch. Returns `fallback` (or `null` if no\n * fallback is given) on invalid input instead of throwing, so callers never\n * need their own try/catch for the common \"might not be JSON\" case.\n */\nexport function safeJsonParse<T = unknown>(input: string, fallback?: T): T | null {\n try {\n return JSON.parse(input) as T;\n } catch {\n return fallback ?? null;\n }\n}\n"],"mappings":";AAKO,SAAS,cAA2B,OAAe,UAAwB;AAChF,MAAI;AACF,WAAO,KAAK,MAAM,KAAK;AAAA,EACzB,QAAQ;AACN,WAAO,YAAY;AAAA,EACrB;AACF;","names":[]}
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/encoding/index.ts
21
+ var encoding_exports = {};
22
+ __export(encoding_exports, {
23
+ base64Decode: () => base64Decode,
24
+ base64Encode: () => base64Encode
25
+ });
26
+ module.exports = __toCommonJS(encoding_exports);
27
+
28
+ // src/encoding/base64.ts
29
+ var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
30
+ function bytesToBase64(bytes) {
31
+ let result = "";
32
+ let i = 0;
33
+ for (; i + 2 < bytes.length; i += 3) {
34
+ const chunk = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2];
35
+ result += BASE64_ALPHABET[chunk >> 18 & 63];
36
+ result += BASE64_ALPHABET[chunk >> 12 & 63];
37
+ result += BASE64_ALPHABET[chunk >> 6 & 63];
38
+ result += BASE64_ALPHABET[chunk & 63];
39
+ }
40
+ const remaining = bytes.length - i;
41
+ if (remaining === 1) {
42
+ const chunk = bytes[i] << 16;
43
+ result += BASE64_ALPHABET[chunk >> 18 & 63];
44
+ result += BASE64_ALPHABET[chunk >> 12 & 63];
45
+ result += "==";
46
+ } else if (remaining === 2) {
47
+ const chunk = bytes[i] << 16 | bytes[i + 1] << 8;
48
+ result += BASE64_ALPHABET[chunk >> 18 & 63];
49
+ result += BASE64_ALPHABET[chunk >> 12 & 63];
50
+ result += BASE64_ALPHABET[chunk >> 6 & 63];
51
+ result += "=";
52
+ }
53
+ return result;
54
+ }
55
+ function base64Encode(input) {
56
+ const bytes = new TextEncoder().encode(input);
57
+ return bytesToBase64(bytes);
58
+ }
59
+ var BASE64_LOOKUP = Object.fromEntries(
60
+ [...BASE64_ALPHABET].map((char, index) => [char, index])
61
+ );
62
+ function base64ToBytes(input) {
63
+ if (input.length === 0) return new Uint8Array(0);
64
+ if (input.length % 4 !== 0) return null;
65
+ const clean = input.replace(/=+$/, "");
66
+ const bytes = [];
67
+ let buffer = 0;
68
+ let bits = 0;
69
+ for (const char of clean) {
70
+ const value = BASE64_LOOKUP[char];
71
+ if (value === void 0) return null;
72
+ buffer = buffer << 6 | value;
73
+ bits += 6;
74
+ if (bits >= 8) {
75
+ bits -= 8;
76
+ bytes.push(buffer >> bits & 255);
77
+ }
78
+ }
79
+ return new Uint8Array(bytes);
80
+ }
81
+ function base64Decode(input) {
82
+ const bytes = base64ToBytes(input);
83
+ if (bytes === null) return null;
84
+ try {
85
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
86
+ } catch {
87
+ return null;
88
+ }
89
+ }
90
+ // Annotate the CommonJS export names for ESM import in node:
91
+ 0 && (module.exports = {
92
+ base64Decode,
93
+ base64Encode
94
+ });
95
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/encoding/index.ts","../../src/encoding/base64.ts"],"sourcesContent":["export { base64Encode, base64Decode } from \"./base64.js\";\n","const BASE64_ALPHABET = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n\nfunction bytesToBase64(bytes: Uint8Array): string {\n let result = \"\";\n let i = 0;\n\n for (; i + 2 < bytes.length; i += 3) {\n const chunk = (bytes[i]! << 16) | (bytes[i + 1]! << 8) | bytes[i + 2]!;\n result += BASE64_ALPHABET[(chunk >> 18) & 63];\n result += BASE64_ALPHABET[(chunk >> 12) & 63];\n result += BASE64_ALPHABET[(chunk >> 6) & 63];\n result += BASE64_ALPHABET[chunk & 63];\n }\n\n const remaining = bytes.length - i;\n if (remaining === 1) {\n const chunk = bytes[i]! << 16;\n result += BASE64_ALPHABET[(chunk >> 18) & 63];\n result += BASE64_ALPHABET[(chunk >> 12) & 63];\n result += \"==\";\n } else if (remaining === 2) {\n const chunk = (bytes[i]! << 16) | (bytes[i + 1]! << 8);\n result += BASE64_ALPHABET[(chunk >> 18) & 63];\n result += BASE64_ALPHABET[(chunk >> 12) & 63];\n result += BASE64_ALPHABET[(chunk >> 6) & 63];\n result += \"=\";\n }\n\n return result;\n}\n\n/**\n * UTF-8-safe Base64 encode. Unlike native `btoa`, this does not corrupt or\n * throw on non-Latin1 input (e.g. Turkish or emoji characters) and does not\n * depend on Node-only `Buffer`, so behavior is identical across runtimes.\n */\nexport function base64Encode(input: string): string {\n const bytes = new TextEncoder().encode(input);\n return bytesToBase64(bytes);\n}\n\nconst BASE64_LOOKUP: Record<string, number> = Object.fromEntries(\n [...BASE64_ALPHABET].map((char, index) => [char, index])\n);\n\nfunction base64ToBytes(input: string): Uint8Array | null {\n if (input.length === 0) return new Uint8Array(0);\n if (input.length % 4 !== 0) return null;\n\n const clean = input.replace(/=+$/, \"\");\n\n const bytes: number[] = [];\n let buffer = 0;\n let bits = 0;\n\n for (const char of clean) {\n const value = BASE64_LOOKUP[char];\n if (value === undefined) return null;\n buffer = (buffer << 6) | value;\n bits += 6;\n if (bits >= 8) {\n bits -= 8;\n bytes.push((buffer >> bits) & 0xff);\n }\n }\n\n return new Uint8Array(bytes);\n}\n\n/**\n * Base64 decode. Does not throw on invalid Base64 input or on a decoded byte\n * sequence that isn't valid UTF-8 — returns `null` in both cases instead,\n * so callers never need to wrap this in try/catch.\n */\nexport function base64Decode(input: string): string | null {\n const bytes = base64ToBytes(input);\n if (bytes === null) return null;\n\n try {\n return new TextDecoder(\"utf-8\", { fatal: true }).decode(bytes);\n } catch {\n return null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,IAAM,kBAAkB;AAExB,SAAS,cAAc,OAA2B;AAChD,MAAI,SAAS;AACb,MAAI,IAAI;AAER,SAAO,IAAI,IAAI,MAAM,QAAQ,KAAK,GAAG;AACnC,UAAM,QAAS,MAAM,CAAC,KAAM,KAAO,MAAM,IAAI,CAAC,KAAM,IAAK,MAAM,IAAI,CAAC;AACpE,cAAU,gBAAiB,SAAS,KAAM,EAAE;AAC5C,cAAU,gBAAiB,SAAS,KAAM,EAAE;AAC5C,cAAU,gBAAiB,SAAS,IAAK,EAAE;AAC3C,cAAU,gBAAgB,QAAQ,EAAE;AAAA,EACtC;AAEA,QAAM,YAAY,MAAM,SAAS;AACjC,MAAI,cAAc,GAAG;AACnB,UAAM,QAAQ,MAAM,CAAC,KAAM;AAC3B,cAAU,gBAAiB,SAAS,KAAM,EAAE;AAC5C,cAAU,gBAAiB,SAAS,KAAM,EAAE;AAC5C,cAAU;AAAA,EACZ,WAAW,cAAc,GAAG;AAC1B,UAAM,QAAS,MAAM,CAAC,KAAM,KAAO,MAAM,IAAI,CAAC,KAAM;AACpD,cAAU,gBAAiB,SAAS,KAAM,EAAE;AAC5C,cAAU,gBAAiB,SAAS,KAAM,EAAE;AAC5C,cAAU,gBAAiB,SAAS,IAAK,EAAE;AAC3C,cAAU;AAAA,EACZ;AAEA,SAAO;AACT;AAOO,SAAS,aAAa,OAAuB;AAClD,QAAM,QAAQ,IAAI,YAAY,EAAE,OAAO,KAAK;AAC5C,SAAO,cAAc,KAAK;AAC5B;AAEA,IAAM,gBAAwC,OAAO;AAAA,EACnD,CAAC,GAAG,eAAe,EAAE,IAAI,CAAC,MAAM,UAAU,CAAC,MAAM,KAAK,CAAC;AACzD;AAEA,SAAS,cAAc,OAAkC;AACvD,MAAI,MAAM,WAAW,EAAG,QAAO,IAAI,WAAW,CAAC;AAC/C,MAAI,MAAM,SAAS,MAAM,EAAG,QAAO;AAEnC,QAAM,QAAQ,MAAM,QAAQ,OAAO,EAAE;AAErC,QAAM,QAAkB,CAAC;AACzB,MAAI,SAAS;AACb,MAAI,OAAO;AAEX,aAAW,QAAQ,OAAO;AACxB,UAAM,QAAQ,cAAc,IAAI;AAChC,QAAI,UAAU,OAAW,QAAO;AAChC,aAAU,UAAU,IAAK;AACzB,YAAQ;AACR,QAAI,QAAQ,GAAG;AACb,cAAQ;AACR,YAAM,KAAM,UAAU,OAAQ,GAAI;AAAA,IACpC;AAAA,EACF;AAEA,SAAO,IAAI,WAAW,KAAK;AAC7B;AAOO,SAAS,aAAa,OAA8B;AACzD,QAAM,QAAQ,cAAc,KAAK;AACjC,MAAI,UAAU,KAAM,QAAO;AAE3B,MAAI;AACF,WAAO,IAAI,YAAY,SAAS,EAAE,OAAO,KAAK,CAAC,EAAE,OAAO,KAAK;AAAA,EAC/D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * UTF-8-safe Base64 encode. Unlike native `btoa`, this does not corrupt or
3
+ * throw on non-Latin1 input (e.g. Turkish or emoji characters) and does not
4
+ * depend on Node-only `Buffer`, so behavior is identical across runtimes.
5
+ */
6
+ declare function base64Encode(input: string): string;
7
+ /**
8
+ * Base64 decode. Does not throw on invalid Base64 input or on a decoded byte
9
+ * sequence that isn't valid UTF-8 — returns `null` in both cases instead,
10
+ * so callers never need to wrap this in try/catch.
11
+ */
12
+ declare function base64Decode(input: string): string | null;
13
+
14
+ export { base64Decode, base64Encode };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * UTF-8-safe Base64 encode. Unlike native `btoa`, this does not corrupt or
3
+ * throw on non-Latin1 input (e.g. Turkish or emoji characters) and does not
4
+ * depend on Node-only `Buffer`, so behavior is identical across runtimes.
5
+ */
6
+ declare function base64Encode(input: string): string;
7
+ /**
8
+ * Base64 decode. Does not throw on invalid Base64 input or on a decoded byte
9
+ * sequence that isn't valid UTF-8 — returns `null` in both cases instead,
10
+ * so callers never need to wrap this in try/catch.
11
+ */
12
+ declare function base64Decode(input: string): string | null;
13
+
14
+ export { base64Decode, base64Encode };
@@ -0,0 +1,67 @@
1
+ // src/encoding/base64.ts
2
+ var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
3
+ function bytesToBase64(bytes) {
4
+ let result = "";
5
+ let i = 0;
6
+ for (; i + 2 < bytes.length; i += 3) {
7
+ const chunk = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2];
8
+ result += BASE64_ALPHABET[chunk >> 18 & 63];
9
+ result += BASE64_ALPHABET[chunk >> 12 & 63];
10
+ result += BASE64_ALPHABET[chunk >> 6 & 63];
11
+ result += BASE64_ALPHABET[chunk & 63];
12
+ }
13
+ const remaining = bytes.length - i;
14
+ if (remaining === 1) {
15
+ const chunk = bytes[i] << 16;
16
+ result += BASE64_ALPHABET[chunk >> 18 & 63];
17
+ result += BASE64_ALPHABET[chunk >> 12 & 63];
18
+ result += "==";
19
+ } else if (remaining === 2) {
20
+ const chunk = bytes[i] << 16 | bytes[i + 1] << 8;
21
+ result += BASE64_ALPHABET[chunk >> 18 & 63];
22
+ result += BASE64_ALPHABET[chunk >> 12 & 63];
23
+ result += BASE64_ALPHABET[chunk >> 6 & 63];
24
+ result += "=";
25
+ }
26
+ return result;
27
+ }
28
+ function base64Encode(input) {
29
+ const bytes = new TextEncoder().encode(input);
30
+ return bytesToBase64(bytes);
31
+ }
32
+ var BASE64_LOOKUP = Object.fromEntries(
33
+ [...BASE64_ALPHABET].map((char, index) => [char, index])
34
+ );
35
+ function base64ToBytes(input) {
36
+ if (input.length === 0) return new Uint8Array(0);
37
+ if (input.length % 4 !== 0) return null;
38
+ const clean = input.replace(/=+$/, "");
39
+ const bytes = [];
40
+ let buffer = 0;
41
+ let bits = 0;
42
+ for (const char of clean) {
43
+ const value = BASE64_LOOKUP[char];
44
+ if (value === void 0) return null;
45
+ buffer = buffer << 6 | value;
46
+ bits += 6;
47
+ if (bits >= 8) {
48
+ bits -= 8;
49
+ bytes.push(buffer >> bits & 255);
50
+ }
51
+ }
52
+ return new Uint8Array(bytes);
53
+ }
54
+ function base64Decode(input) {
55
+ const bytes = base64ToBytes(input);
56
+ if (bytes === null) return null;
57
+ try {
58
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
59
+ } catch {
60
+ return null;
61
+ }
62
+ }
63
+ export {
64
+ base64Decode,
65
+ base64Encode
66
+ };
67
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/encoding/base64.ts"],"sourcesContent":["const BASE64_ALPHABET = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n\nfunction bytesToBase64(bytes: Uint8Array): string {\n let result = \"\";\n let i = 0;\n\n for (; i + 2 < bytes.length; i += 3) {\n const chunk = (bytes[i]! << 16) | (bytes[i + 1]! << 8) | bytes[i + 2]!;\n result += BASE64_ALPHABET[(chunk >> 18) & 63];\n result += BASE64_ALPHABET[(chunk >> 12) & 63];\n result += BASE64_ALPHABET[(chunk >> 6) & 63];\n result += BASE64_ALPHABET[chunk & 63];\n }\n\n const remaining = bytes.length - i;\n if (remaining === 1) {\n const chunk = bytes[i]! << 16;\n result += BASE64_ALPHABET[(chunk >> 18) & 63];\n result += BASE64_ALPHABET[(chunk >> 12) & 63];\n result += \"==\";\n } else if (remaining === 2) {\n const chunk = (bytes[i]! << 16) | (bytes[i + 1]! << 8);\n result += BASE64_ALPHABET[(chunk >> 18) & 63];\n result += BASE64_ALPHABET[(chunk >> 12) & 63];\n result += BASE64_ALPHABET[(chunk >> 6) & 63];\n result += \"=\";\n }\n\n return result;\n}\n\n/**\n * UTF-8-safe Base64 encode. Unlike native `btoa`, this does not corrupt or\n * throw on non-Latin1 input (e.g. Turkish or emoji characters) and does not\n * depend on Node-only `Buffer`, so behavior is identical across runtimes.\n */\nexport function base64Encode(input: string): string {\n const bytes = new TextEncoder().encode(input);\n return bytesToBase64(bytes);\n}\n\nconst BASE64_LOOKUP: Record<string, number> = Object.fromEntries(\n [...BASE64_ALPHABET].map((char, index) => [char, index])\n);\n\nfunction base64ToBytes(input: string): Uint8Array | null {\n if (input.length === 0) return new Uint8Array(0);\n if (input.length % 4 !== 0) return null;\n\n const clean = input.replace(/=+$/, \"\");\n\n const bytes: number[] = [];\n let buffer = 0;\n let bits = 0;\n\n for (const char of clean) {\n const value = BASE64_LOOKUP[char];\n if (value === undefined) return null;\n buffer = (buffer << 6) | value;\n bits += 6;\n if (bits >= 8) {\n bits -= 8;\n bytes.push((buffer >> bits) & 0xff);\n }\n }\n\n return new Uint8Array(bytes);\n}\n\n/**\n * Base64 decode. Does not throw on invalid Base64 input or on a decoded byte\n * sequence that isn't valid UTF-8 — returns `null` in both cases instead,\n * so callers never need to wrap this in try/catch.\n */\nexport function base64Decode(input: string): string | null {\n const bytes = base64ToBytes(input);\n if (bytes === null) return null;\n\n try {\n return new TextDecoder(\"utf-8\", { fatal: true }).decode(bytes);\n } catch {\n return null;\n }\n}\n"],"mappings":";AAAA,IAAM,kBAAkB;AAExB,SAAS,cAAc,OAA2B;AAChD,MAAI,SAAS;AACb,MAAI,IAAI;AAER,SAAO,IAAI,IAAI,MAAM,QAAQ,KAAK,GAAG;AACnC,UAAM,QAAS,MAAM,CAAC,KAAM,KAAO,MAAM,IAAI,CAAC,KAAM,IAAK,MAAM,IAAI,CAAC;AACpE,cAAU,gBAAiB,SAAS,KAAM,EAAE;AAC5C,cAAU,gBAAiB,SAAS,KAAM,EAAE;AAC5C,cAAU,gBAAiB,SAAS,IAAK,EAAE;AAC3C,cAAU,gBAAgB,QAAQ,EAAE;AAAA,EACtC;AAEA,QAAM,YAAY,MAAM,SAAS;AACjC,MAAI,cAAc,GAAG;AACnB,UAAM,QAAQ,MAAM,CAAC,KAAM;AAC3B,cAAU,gBAAiB,SAAS,KAAM,EAAE;AAC5C,cAAU,gBAAiB,SAAS,KAAM,EAAE;AAC5C,cAAU;AAAA,EACZ,WAAW,cAAc,GAAG;AAC1B,UAAM,QAAS,MAAM,CAAC,KAAM,KAAO,MAAM,IAAI,CAAC,KAAM;AACpD,cAAU,gBAAiB,SAAS,KAAM,EAAE;AAC5C,cAAU,gBAAiB,SAAS,KAAM,EAAE;AAC5C,cAAU,gBAAiB,SAAS,IAAK,EAAE;AAC3C,cAAU;AAAA,EACZ;AAEA,SAAO;AACT;AAOO,SAAS,aAAa,OAAuB;AAClD,QAAM,QAAQ,IAAI,YAAY,EAAE,OAAO,KAAK;AAC5C,SAAO,cAAc,KAAK;AAC5B;AAEA,IAAM,gBAAwC,OAAO;AAAA,EACnD,CAAC,GAAG,eAAe,EAAE,IAAI,CAAC,MAAM,UAAU,CAAC,MAAM,KAAK,CAAC;AACzD;AAEA,SAAS,cAAc,OAAkC;AACvD,MAAI,MAAM,WAAW,EAAG,QAAO,IAAI,WAAW,CAAC;AAC/C,MAAI,MAAM,SAAS,MAAM,EAAG,QAAO;AAEnC,QAAM,QAAQ,MAAM,QAAQ,OAAO,EAAE;AAErC,QAAM,QAAkB,CAAC;AACzB,MAAI,SAAS;AACb,MAAI,OAAO;AAEX,aAAW,QAAQ,OAAO;AACxB,UAAM,QAAQ,cAAc,IAAI;AAChC,QAAI,UAAU,OAAW,QAAO;AAChC,aAAU,UAAU,IAAK;AACzB,YAAQ;AACR,QAAI,QAAQ,GAAG;AACb,cAAQ;AACR,YAAM,KAAM,UAAU,OAAQ,GAAI;AAAA,IACpC;AAAA,EACF;AAEA,SAAO,IAAI,WAAW,KAAK;AAC7B;AAOO,SAAS,aAAa,OAA8B;AACzD,QAAM,QAAQ,cAAc,KAAK;AACjC,MAAI,UAAU,KAAM,QAAO;AAE3B,MAAI;AACF,WAAO,IAAI,YAAY,SAAS,EAAE,OAAO,KAAK,CAAC,EAAE,OAAO,KAAK;AAAA,EAC/D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
package/dist/index.cjs ADDED
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ base64Decode: () => base64Decode,
24
+ base64Encode: () => base64Encode,
25
+ byteLength: () => byteLength,
26
+ safeJsonParse: () => safeJsonParse,
27
+ stringLength: () => stringLength
28
+ });
29
+ module.exports = __toCommonJS(src_exports);
30
+
31
+ // src/encoding/base64.ts
32
+ var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
33
+ function bytesToBase64(bytes) {
34
+ let result = "";
35
+ let i = 0;
36
+ for (; i + 2 < bytes.length; i += 3) {
37
+ const chunk = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2];
38
+ result += BASE64_ALPHABET[chunk >> 18 & 63];
39
+ result += BASE64_ALPHABET[chunk >> 12 & 63];
40
+ result += BASE64_ALPHABET[chunk >> 6 & 63];
41
+ result += BASE64_ALPHABET[chunk & 63];
42
+ }
43
+ const remaining = bytes.length - i;
44
+ if (remaining === 1) {
45
+ const chunk = bytes[i] << 16;
46
+ result += BASE64_ALPHABET[chunk >> 18 & 63];
47
+ result += BASE64_ALPHABET[chunk >> 12 & 63];
48
+ result += "==";
49
+ } else if (remaining === 2) {
50
+ const chunk = bytes[i] << 16 | bytes[i + 1] << 8;
51
+ result += BASE64_ALPHABET[chunk >> 18 & 63];
52
+ result += BASE64_ALPHABET[chunk >> 12 & 63];
53
+ result += BASE64_ALPHABET[chunk >> 6 & 63];
54
+ result += "=";
55
+ }
56
+ return result;
57
+ }
58
+ function base64Encode(input) {
59
+ const bytes = new TextEncoder().encode(input);
60
+ return bytesToBase64(bytes);
61
+ }
62
+ var BASE64_LOOKUP = Object.fromEntries(
63
+ [...BASE64_ALPHABET].map((char, index) => [char, index])
64
+ );
65
+ function base64ToBytes(input) {
66
+ if (input.length === 0) return new Uint8Array(0);
67
+ if (input.length % 4 !== 0) return null;
68
+ const clean = input.replace(/=+$/, "");
69
+ const bytes = [];
70
+ let buffer = 0;
71
+ let bits = 0;
72
+ for (const char of clean) {
73
+ const value = BASE64_LOOKUP[char];
74
+ if (value === void 0) return null;
75
+ buffer = buffer << 6 | value;
76
+ bits += 6;
77
+ if (bits >= 8) {
78
+ bits -= 8;
79
+ bytes.push(buffer >> bits & 255);
80
+ }
81
+ }
82
+ return new Uint8Array(bytes);
83
+ }
84
+ function base64Decode(input) {
85
+ const bytes = base64ToBytes(input);
86
+ if (bytes === null) return null;
87
+ try {
88
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
89
+ } catch {
90
+ return null;
91
+ }
92
+ }
93
+
94
+ // src/text/length.ts
95
+ function stringLength(input) {
96
+ if (typeof Intl !== "undefined" && "Segmenter" in Intl) {
97
+ const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
98
+ return [...segmenter.segment(input)].length;
99
+ }
100
+ return [...input].length;
101
+ }
102
+ function byteLength(input) {
103
+ return new TextEncoder().encode(input).length;
104
+ }
105
+
106
+ // src/data/json.ts
107
+ function safeJsonParse(input, fallback) {
108
+ try {
109
+ return JSON.parse(input);
110
+ } catch {
111
+ return fallback ?? null;
112
+ }
113
+ }
114
+ // Annotate the CommonJS export names for ESM import in node:
115
+ 0 && (module.exports = {
116
+ base64Decode,
117
+ base64Encode,
118
+ byteLength,
119
+ safeJsonParse,
120
+ stringLength
121
+ });
122
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/encoding/base64.ts","../src/text/length.ts","../src/data/json.ts"],"sourcesContent":["export * from \"./encoding/index.js\";\nexport * from \"./text/index.js\";\nexport * from \"./data/index.js\";\n","const BASE64_ALPHABET = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n\nfunction bytesToBase64(bytes: Uint8Array): string {\n let result = \"\";\n let i = 0;\n\n for (; i + 2 < bytes.length; i += 3) {\n const chunk = (bytes[i]! << 16) | (bytes[i + 1]! << 8) | bytes[i + 2]!;\n result += BASE64_ALPHABET[(chunk >> 18) & 63];\n result += BASE64_ALPHABET[(chunk >> 12) & 63];\n result += BASE64_ALPHABET[(chunk >> 6) & 63];\n result += BASE64_ALPHABET[chunk & 63];\n }\n\n const remaining = bytes.length - i;\n if (remaining === 1) {\n const chunk = bytes[i]! << 16;\n result += BASE64_ALPHABET[(chunk >> 18) & 63];\n result += BASE64_ALPHABET[(chunk >> 12) & 63];\n result += \"==\";\n } else if (remaining === 2) {\n const chunk = (bytes[i]! << 16) | (bytes[i + 1]! << 8);\n result += BASE64_ALPHABET[(chunk >> 18) & 63];\n result += BASE64_ALPHABET[(chunk >> 12) & 63];\n result += BASE64_ALPHABET[(chunk >> 6) & 63];\n result += \"=\";\n }\n\n return result;\n}\n\n/**\n * UTF-8-safe Base64 encode. Unlike native `btoa`, this does not corrupt or\n * throw on non-Latin1 input (e.g. Turkish or emoji characters) and does not\n * depend on Node-only `Buffer`, so behavior is identical across runtimes.\n */\nexport function base64Encode(input: string): string {\n const bytes = new TextEncoder().encode(input);\n return bytesToBase64(bytes);\n}\n\nconst BASE64_LOOKUP: Record<string, number> = Object.fromEntries(\n [...BASE64_ALPHABET].map((char, index) => [char, index])\n);\n\nfunction base64ToBytes(input: string): Uint8Array | null {\n if (input.length === 0) return new Uint8Array(0);\n if (input.length % 4 !== 0) return null;\n\n const clean = input.replace(/=+$/, \"\");\n\n const bytes: number[] = [];\n let buffer = 0;\n let bits = 0;\n\n for (const char of clean) {\n const value = BASE64_LOOKUP[char];\n if (value === undefined) return null;\n buffer = (buffer << 6) | value;\n bits += 6;\n if (bits >= 8) {\n bits -= 8;\n bytes.push((buffer >> bits) & 0xff);\n }\n }\n\n return new Uint8Array(bytes);\n}\n\n/**\n * Base64 decode. Does not throw on invalid Base64 input or on a decoded byte\n * sequence that isn't valid UTF-8 — returns `null` in both cases instead,\n * so callers never need to wrap this in try/catch.\n */\nexport function base64Decode(input: string): string | null {\n const bytes = base64ToBytes(input);\n if (bytes === null) return null;\n\n try {\n return new TextDecoder(\"utf-8\", { fatal: true }).decode(bytes);\n } catch {\n return null;\n }\n}\n","/**\n * Visual-character-accurate length. Native `.length` counts UTF-16 code\n * units, which over-counts emoji and other supplementary-plane characters\n * (e.g. `\"😀\".length === 2`). Uses `Intl.Segmenter` (grapheme granularity)\n * when available, falling back to Unicode code-point counting otherwise —\n * still more accurate than native `.length` on older runtimes.\n */\nexport function stringLength(input: string): number {\n if (typeof Intl !== \"undefined\" && \"Segmenter\" in Intl) {\n const segmenter = new Intl.Segmenter(undefined, { granularity: \"grapheme\" });\n return [...segmenter.segment(input)].length;\n }\n return [...input].length;\n}\n\n/**\n * UTF-8 byte length — useful for storage/column-size limits, distinct from\n * `stringLength` (visual character count). A single emoji is 1 character\n * but up to 4 bytes.\n */\nexport function byteLength(input: string): number {\n return new TextEncoder().encode(input).length;\n}\n","/**\n * `JSON.parse` wrapped in try/catch. Returns `fallback` (or `null` if no\n * fallback is given) on invalid input instead of throwing, so callers never\n * need their own try/catch for the common \"might not be JSON\" case.\n */\nexport function safeJsonParse<T = unknown>(input: string, fallback?: T): T | null {\n try {\n return JSON.parse(input) as T;\n } catch {\n return fallback ?? null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,IAAM,kBAAkB;AAExB,SAAS,cAAc,OAA2B;AAChD,MAAI,SAAS;AACb,MAAI,IAAI;AAER,SAAO,IAAI,IAAI,MAAM,QAAQ,KAAK,GAAG;AACnC,UAAM,QAAS,MAAM,CAAC,KAAM,KAAO,MAAM,IAAI,CAAC,KAAM,IAAK,MAAM,IAAI,CAAC;AACpE,cAAU,gBAAiB,SAAS,KAAM,EAAE;AAC5C,cAAU,gBAAiB,SAAS,KAAM,EAAE;AAC5C,cAAU,gBAAiB,SAAS,IAAK,EAAE;AAC3C,cAAU,gBAAgB,QAAQ,EAAE;AAAA,EACtC;AAEA,QAAM,YAAY,MAAM,SAAS;AACjC,MAAI,cAAc,GAAG;AACnB,UAAM,QAAQ,MAAM,CAAC,KAAM;AAC3B,cAAU,gBAAiB,SAAS,KAAM,EAAE;AAC5C,cAAU,gBAAiB,SAAS,KAAM,EAAE;AAC5C,cAAU;AAAA,EACZ,WAAW,cAAc,GAAG;AAC1B,UAAM,QAAS,MAAM,CAAC,KAAM,KAAO,MAAM,IAAI,CAAC,KAAM;AACpD,cAAU,gBAAiB,SAAS,KAAM,EAAE;AAC5C,cAAU,gBAAiB,SAAS,KAAM,EAAE;AAC5C,cAAU,gBAAiB,SAAS,IAAK,EAAE;AAC3C,cAAU;AAAA,EACZ;AAEA,SAAO;AACT;AAOO,SAAS,aAAa,OAAuB;AAClD,QAAM,QAAQ,IAAI,YAAY,EAAE,OAAO,KAAK;AAC5C,SAAO,cAAc,KAAK;AAC5B;AAEA,IAAM,gBAAwC,OAAO;AAAA,EACnD,CAAC,GAAG,eAAe,EAAE,IAAI,CAAC,MAAM,UAAU,CAAC,MAAM,KAAK,CAAC;AACzD;AAEA,SAAS,cAAc,OAAkC;AACvD,MAAI,MAAM,WAAW,EAAG,QAAO,IAAI,WAAW,CAAC;AAC/C,MAAI,MAAM,SAAS,MAAM,EAAG,QAAO;AAEnC,QAAM,QAAQ,MAAM,QAAQ,OAAO,EAAE;AAErC,QAAM,QAAkB,CAAC;AACzB,MAAI,SAAS;AACb,MAAI,OAAO;AAEX,aAAW,QAAQ,OAAO;AACxB,UAAM,QAAQ,cAAc,IAAI;AAChC,QAAI,UAAU,OAAW,QAAO;AAChC,aAAU,UAAU,IAAK;AACzB,YAAQ;AACR,QAAI,QAAQ,GAAG;AACb,cAAQ;AACR,YAAM,KAAM,UAAU,OAAQ,GAAI;AAAA,IACpC;AAAA,EACF;AAEA,SAAO,IAAI,WAAW,KAAK;AAC7B;AAOO,SAAS,aAAa,OAA8B;AACzD,QAAM,QAAQ,cAAc,KAAK;AACjC,MAAI,UAAU,KAAM,QAAO;AAE3B,MAAI;AACF,WAAO,IAAI,YAAY,SAAS,EAAE,OAAO,KAAK,CAAC,EAAE,OAAO,KAAK;AAAA,EAC/D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AC5EO,SAAS,aAAa,OAAuB;AAClD,MAAI,OAAO,SAAS,eAAe,eAAe,MAAM;AACtD,UAAM,YAAY,IAAI,KAAK,UAAU,QAAW,EAAE,aAAa,WAAW,CAAC;AAC3E,WAAO,CAAC,GAAG,UAAU,QAAQ,KAAK,CAAC,EAAE;AAAA,EACvC;AACA,SAAO,CAAC,GAAG,KAAK,EAAE;AACpB;AAOO,SAAS,WAAW,OAAuB;AAChD,SAAO,IAAI,YAAY,EAAE,OAAO,KAAK,EAAE;AACzC;;;ACjBO,SAAS,cAA2B,OAAe,UAAwB;AAChF,MAAI;AACF,WAAO,KAAK,MAAM,KAAK;AAAA,EACzB,QAAQ;AACN,WAAO,YAAY;AAAA,EACrB;AACF;","names":[]}
@@ -0,0 +1,3 @@
1
+ export { base64Decode, base64Encode } from './encoding/index.cjs';
2
+ export { byteLength, stringLength } from './text/index.cjs';
3
+ export { safeJsonParse } from './data/index.cjs';
@@ -0,0 +1,3 @@
1
+ export { base64Decode, base64Encode } from './encoding/index.js';
2
+ export { byteLength, stringLength } from './text/index.js';
3
+ export { safeJsonParse } from './data/index.js';
package/dist/index.mjs ADDED
@@ -0,0 +1,91 @@
1
+ // src/encoding/base64.ts
2
+ var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
3
+ function bytesToBase64(bytes) {
4
+ let result = "";
5
+ let i = 0;
6
+ for (; i + 2 < bytes.length; i += 3) {
7
+ const chunk = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2];
8
+ result += BASE64_ALPHABET[chunk >> 18 & 63];
9
+ result += BASE64_ALPHABET[chunk >> 12 & 63];
10
+ result += BASE64_ALPHABET[chunk >> 6 & 63];
11
+ result += BASE64_ALPHABET[chunk & 63];
12
+ }
13
+ const remaining = bytes.length - i;
14
+ if (remaining === 1) {
15
+ const chunk = bytes[i] << 16;
16
+ result += BASE64_ALPHABET[chunk >> 18 & 63];
17
+ result += BASE64_ALPHABET[chunk >> 12 & 63];
18
+ result += "==";
19
+ } else if (remaining === 2) {
20
+ const chunk = bytes[i] << 16 | bytes[i + 1] << 8;
21
+ result += BASE64_ALPHABET[chunk >> 18 & 63];
22
+ result += BASE64_ALPHABET[chunk >> 12 & 63];
23
+ result += BASE64_ALPHABET[chunk >> 6 & 63];
24
+ result += "=";
25
+ }
26
+ return result;
27
+ }
28
+ function base64Encode(input) {
29
+ const bytes = new TextEncoder().encode(input);
30
+ return bytesToBase64(bytes);
31
+ }
32
+ var BASE64_LOOKUP = Object.fromEntries(
33
+ [...BASE64_ALPHABET].map((char, index) => [char, index])
34
+ );
35
+ function base64ToBytes(input) {
36
+ if (input.length === 0) return new Uint8Array(0);
37
+ if (input.length % 4 !== 0) return null;
38
+ const clean = input.replace(/=+$/, "");
39
+ const bytes = [];
40
+ let buffer = 0;
41
+ let bits = 0;
42
+ for (const char of clean) {
43
+ const value = BASE64_LOOKUP[char];
44
+ if (value === void 0) return null;
45
+ buffer = buffer << 6 | value;
46
+ bits += 6;
47
+ if (bits >= 8) {
48
+ bits -= 8;
49
+ bytes.push(buffer >> bits & 255);
50
+ }
51
+ }
52
+ return new Uint8Array(bytes);
53
+ }
54
+ function base64Decode(input) {
55
+ const bytes = base64ToBytes(input);
56
+ if (bytes === null) return null;
57
+ try {
58
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
59
+ } catch {
60
+ return null;
61
+ }
62
+ }
63
+
64
+ // src/text/length.ts
65
+ function stringLength(input) {
66
+ if (typeof Intl !== "undefined" && "Segmenter" in Intl) {
67
+ const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
68
+ return [...segmenter.segment(input)].length;
69
+ }
70
+ return [...input].length;
71
+ }
72
+ function byteLength(input) {
73
+ return new TextEncoder().encode(input).length;
74
+ }
75
+
76
+ // src/data/json.ts
77
+ function safeJsonParse(input, fallback) {
78
+ try {
79
+ return JSON.parse(input);
80
+ } catch {
81
+ return fallback ?? null;
82
+ }
83
+ }
84
+ export {
85
+ base64Decode,
86
+ base64Encode,
87
+ byteLength,
88
+ safeJsonParse,
89
+ stringLength
90
+ };
91
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/encoding/base64.ts","../src/text/length.ts","../src/data/json.ts"],"sourcesContent":["const BASE64_ALPHABET = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n\nfunction bytesToBase64(bytes: Uint8Array): string {\n let result = \"\";\n let i = 0;\n\n for (; i + 2 < bytes.length; i += 3) {\n const chunk = (bytes[i]! << 16) | (bytes[i + 1]! << 8) | bytes[i + 2]!;\n result += BASE64_ALPHABET[(chunk >> 18) & 63];\n result += BASE64_ALPHABET[(chunk >> 12) & 63];\n result += BASE64_ALPHABET[(chunk >> 6) & 63];\n result += BASE64_ALPHABET[chunk & 63];\n }\n\n const remaining = bytes.length - i;\n if (remaining === 1) {\n const chunk = bytes[i]! << 16;\n result += BASE64_ALPHABET[(chunk >> 18) & 63];\n result += BASE64_ALPHABET[(chunk >> 12) & 63];\n result += \"==\";\n } else if (remaining === 2) {\n const chunk = (bytes[i]! << 16) | (bytes[i + 1]! << 8);\n result += BASE64_ALPHABET[(chunk >> 18) & 63];\n result += BASE64_ALPHABET[(chunk >> 12) & 63];\n result += BASE64_ALPHABET[(chunk >> 6) & 63];\n result += \"=\";\n }\n\n return result;\n}\n\n/**\n * UTF-8-safe Base64 encode. Unlike native `btoa`, this does not corrupt or\n * throw on non-Latin1 input (e.g. Turkish or emoji characters) and does not\n * depend on Node-only `Buffer`, so behavior is identical across runtimes.\n */\nexport function base64Encode(input: string): string {\n const bytes = new TextEncoder().encode(input);\n return bytesToBase64(bytes);\n}\n\nconst BASE64_LOOKUP: Record<string, number> = Object.fromEntries(\n [...BASE64_ALPHABET].map((char, index) => [char, index])\n);\n\nfunction base64ToBytes(input: string): Uint8Array | null {\n if (input.length === 0) return new Uint8Array(0);\n if (input.length % 4 !== 0) return null;\n\n const clean = input.replace(/=+$/, \"\");\n\n const bytes: number[] = [];\n let buffer = 0;\n let bits = 0;\n\n for (const char of clean) {\n const value = BASE64_LOOKUP[char];\n if (value === undefined) return null;\n buffer = (buffer << 6) | value;\n bits += 6;\n if (bits >= 8) {\n bits -= 8;\n bytes.push((buffer >> bits) & 0xff);\n }\n }\n\n return new Uint8Array(bytes);\n}\n\n/**\n * Base64 decode. Does not throw on invalid Base64 input or on a decoded byte\n * sequence that isn't valid UTF-8 — returns `null` in both cases instead,\n * so callers never need to wrap this in try/catch.\n */\nexport function base64Decode(input: string): string | null {\n const bytes = base64ToBytes(input);\n if (bytes === null) return null;\n\n try {\n return new TextDecoder(\"utf-8\", { fatal: true }).decode(bytes);\n } catch {\n return null;\n }\n}\n","/**\n * Visual-character-accurate length. Native `.length` counts UTF-16 code\n * units, which over-counts emoji and other supplementary-plane characters\n * (e.g. `\"😀\".length === 2`). Uses `Intl.Segmenter` (grapheme granularity)\n * when available, falling back to Unicode code-point counting otherwise —\n * still more accurate than native `.length` on older runtimes.\n */\nexport function stringLength(input: string): number {\n if (typeof Intl !== \"undefined\" && \"Segmenter\" in Intl) {\n const segmenter = new Intl.Segmenter(undefined, { granularity: \"grapheme\" });\n return [...segmenter.segment(input)].length;\n }\n return [...input].length;\n}\n\n/**\n * UTF-8 byte length — useful for storage/column-size limits, distinct from\n * `stringLength` (visual character count). A single emoji is 1 character\n * but up to 4 bytes.\n */\nexport function byteLength(input: string): number {\n return new TextEncoder().encode(input).length;\n}\n","/**\n * `JSON.parse` wrapped in try/catch. Returns `fallback` (or `null` if no\n * fallback is given) on invalid input instead of throwing, so callers never\n * need their own try/catch for the common \"might not be JSON\" case.\n */\nexport function safeJsonParse<T = unknown>(input: string, fallback?: T): T | null {\n try {\n return JSON.parse(input) as T;\n } catch {\n return fallback ?? null;\n }\n}\n"],"mappings":";AAAA,IAAM,kBAAkB;AAExB,SAAS,cAAc,OAA2B;AAChD,MAAI,SAAS;AACb,MAAI,IAAI;AAER,SAAO,IAAI,IAAI,MAAM,QAAQ,KAAK,GAAG;AACnC,UAAM,QAAS,MAAM,CAAC,KAAM,KAAO,MAAM,IAAI,CAAC,KAAM,IAAK,MAAM,IAAI,CAAC;AACpE,cAAU,gBAAiB,SAAS,KAAM,EAAE;AAC5C,cAAU,gBAAiB,SAAS,KAAM,EAAE;AAC5C,cAAU,gBAAiB,SAAS,IAAK,EAAE;AAC3C,cAAU,gBAAgB,QAAQ,EAAE;AAAA,EACtC;AAEA,QAAM,YAAY,MAAM,SAAS;AACjC,MAAI,cAAc,GAAG;AACnB,UAAM,QAAQ,MAAM,CAAC,KAAM;AAC3B,cAAU,gBAAiB,SAAS,KAAM,EAAE;AAC5C,cAAU,gBAAiB,SAAS,KAAM,EAAE;AAC5C,cAAU;AAAA,EACZ,WAAW,cAAc,GAAG;AAC1B,UAAM,QAAS,MAAM,CAAC,KAAM,KAAO,MAAM,IAAI,CAAC,KAAM;AACpD,cAAU,gBAAiB,SAAS,KAAM,EAAE;AAC5C,cAAU,gBAAiB,SAAS,KAAM,EAAE;AAC5C,cAAU,gBAAiB,SAAS,IAAK,EAAE;AAC3C,cAAU;AAAA,EACZ;AAEA,SAAO;AACT;AAOO,SAAS,aAAa,OAAuB;AAClD,QAAM,QAAQ,IAAI,YAAY,EAAE,OAAO,KAAK;AAC5C,SAAO,cAAc,KAAK;AAC5B;AAEA,IAAM,gBAAwC,OAAO;AAAA,EACnD,CAAC,GAAG,eAAe,EAAE,IAAI,CAAC,MAAM,UAAU,CAAC,MAAM,KAAK,CAAC;AACzD;AAEA,SAAS,cAAc,OAAkC;AACvD,MAAI,MAAM,WAAW,EAAG,QAAO,IAAI,WAAW,CAAC;AAC/C,MAAI,MAAM,SAAS,MAAM,EAAG,QAAO;AAEnC,QAAM,QAAQ,MAAM,QAAQ,OAAO,EAAE;AAErC,QAAM,QAAkB,CAAC;AACzB,MAAI,SAAS;AACb,MAAI,OAAO;AAEX,aAAW,QAAQ,OAAO;AACxB,UAAM,QAAQ,cAAc,IAAI;AAChC,QAAI,UAAU,OAAW,QAAO;AAChC,aAAU,UAAU,IAAK;AACzB,YAAQ;AACR,QAAI,QAAQ,GAAG;AACb,cAAQ;AACR,YAAM,KAAM,UAAU,OAAQ,GAAI;AAAA,IACpC;AAAA,EACF;AAEA,SAAO,IAAI,WAAW,KAAK;AAC7B;AAOO,SAAS,aAAa,OAA8B;AACzD,QAAM,QAAQ,cAAc,KAAK;AACjC,MAAI,UAAU,KAAM,QAAO;AAE3B,MAAI;AACF,WAAO,IAAI,YAAY,SAAS,EAAE,OAAO,KAAK,CAAC,EAAE,OAAO,KAAK;AAAA,EAC/D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AC5EO,SAAS,aAAa,OAAuB;AAClD,MAAI,OAAO,SAAS,eAAe,eAAe,MAAM;AACtD,UAAM,YAAY,IAAI,KAAK,UAAU,QAAW,EAAE,aAAa,WAAW,CAAC;AAC3E,WAAO,CAAC,GAAG,UAAU,QAAQ,KAAK,CAAC,EAAE;AAAA,EACvC;AACA,SAAO,CAAC,GAAG,KAAK,EAAE;AACpB;AAOO,SAAS,WAAW,OAAuB;AAChD,SAAO,IAAI,YAAY,EAAE,OAAO,KAAK,EAAE;AACzC;;;ACjBO,SAAS,cAA2B,OAAe,UAAwB;AAChF,MAAI;AACF,WAAO,KAAK,MAAM,KAAK;AAAA,EACzB,QAAQ;AACN,WAAO,YAAY;AAAA,EACrB;AACF;","names":[]}
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/text/index.ts
21
+ var text_exports = {};
22
+ __export(text_exports, {
23
+ byteLength: () => byteLength,
24
+ stringLength: () => stringLength
25
+ });
26
+ module.exports = __toCommonJS(text_exports);
27
+
28
+ // src/text/length.ts
29
+ function stringLength(input) {
30
+ if (typeof Intl !== "undefined" && "Segmenter" in Intl) {
31
+ const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
32
+ return [...segmenter.segment(input)].length;
33
+ }
34
+ return [...input].length;
35
+ }
36
+ function byteLength(input) {
37
+ return new TextEncoder().encode(input).length;
38
+ }
39
+ // Annotate the CommonJS export names for ESM import in node:
40
+ 0 && (module.exports = {
41
+ byteLength,
42
+ stringLength
43
+ });
44
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/text/index.ts","../../src/text/length.ts"],"sourcesContent":["export { stringLength, byteLength } from \"./length.js\";\n","/**\n * Visual-character-accurate length. Native `.length` counts UTF-16 code\n * units, which over-counts emoji and other supplementary-plane characters\n * (e.g. `\"😀\".length === 2`). Uses `Intl.Segmenter` (grapheme granularity)\n * when available, falling back to Unicode code-point counting otherwise —\n * still more accurate than native `.length` on older runtimes.\n */\nexport function stringLength(input: string): number {\n if (typeof Intl !== \"undefined\" && \"Segmenter\" in Intl) {\n const segmenter = new Intl.Segmenter(undefined, { granularity: \"grapheme\" });\n return [...segmenter.segment(input)].length;\n }\n return [...input].length;\n}\n\n/**\n * UTF-8 byte length — useful for storage/column-size limits, distinct from\n * `stringLength` (visual character count). A single emoji is 1 character\n * but up to 4 bytes.\n */\nexport function byteLength(input: string): number {\n return new TextEncoder().encode(input).length;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACOO,SAAS,aAAa,OAAuB;AAClD,MAAI,OAAO,SAAS,eAAe,eAAe,MAAM;AACtD,UAAM,YAAY,IAAI,KAAK,UAAU,QAAW,EAAE,aAAa,WAAW,CAAC;AAC3E,WAAO,CAAC,GAAG,UAAU,QAAQ,KAAK,CAAC,EAAE;AAAA,EACvC;AACA,SAAO,CAAC,GAAG,KAAK,EAAE;AACpB;AAOO,SAAS,WAAW,OAAuB;AAChD,SAAO,IAAI,YAAY,EAAE,OAAO,KAAK,EAAE;AACzC;","names":[]}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Visual-character-accurate length. Native `.length` counts UTF-16 code
3
+ * units, which over-counts emoji and other supplementary-plane characters
4
+ * (e.g. `"😀".length === 2`). Uses `Intl.Segmenter` (grapheme granularity)
5
+ * when available, falling back to Unicode code-point counting otherwise —
6
+ * still more accurate than native `.length` on older runtimes.
7
+ */
8
+ declare function stringLength(input: string): number;
9
+ /**
10
+ * UTF-8 byte length — useful for storage/column-size limits, distinct from
11
+ * `stringLength` (visual character count). A single emoji is 1 character
12
+ * but up to 4 bytes.
13
+ */
14
+ declare function byteLength(input: string): number;
15
+
16
+ export { byteLength, stringLength };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Visual-character-accurate length. Native `.length` counts UTF-16 code
3
+ * units, which over-counts emoji and other supplementary-plane characters
4
+ * (e.g. `"😀".length === 2`). Uses `Intl.Segmenter` (grapheme granularity)
5
+ * when available, falling back to Unicode code-point counting otherwise —
6
+ * still more accurate than native `.length` on older runtimes.
7
+ */
8
+ declare function stringLength(input: string): number;
9
+ /**
10
+ * UTF-8 byte length — useful for storage/column-size limits, distinct from
11
+ * `stringLength` (visual character count). A single emoji is 1 character
12
+ * but up to 4 bytes.
13
+ */
14
+ declare function byteLength(input: string): number;
15
+
16
+ export { byteLength, stringLength };
@@ -0,0 +1,16 @@
1
+ // src/text/length.ts
2
+ function stringLength(input) {
3
+ if (typeof Intl !== "undefined" && "Segmenter" in Intl) {
4
+ const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
5
+ return [...segmenter.segment(input)].length;
6
+ }
7
+ return [...input].length;
8
+ }
9
+ function byteLength(input) {
10
+ return new TextEncoder().encode(input).length;
11
+ }
12
+ export {
13
+ byteLength,
14
+ stringLength
15
+ };
16
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/text/length.ts"],"sourcesContent":["/**\n * Visual-character-accurate length. Native `.length` counts UTF-16 code\n * units, which over-counts emoji and other supplementary-plane characters\n * (e.g. `\"😀\".length === 2`). Uses `Intl.Segmenter` (grapheme granularity)\n * when available, falling back to Unicode code-point counting otherwise —\n * still more accurate than native `.length` on older runtimes.\n */\nexport function stringLength(input: string): number {\n if (typeof Intl !== \"undefined\" && \"Segmenter\" in Intl) {\n const segmenter = new Intl.Segmenter(undefined, { granularity: \"grapheme\" });\n return [...segmenter.segment(input)].length;\n }\n return [...input].length;\n}\n\n/**\n * UTF-8 byte length — useful for storage/column-size limits, distinct from\n * `stringLength` (visual character count). A single emoji is 1 character\n * but up to 4 bytes.\n */\nexport function byteLength(input: string): number {\n return new TextEncoder().encode(input).length;\n}\n"],"mappings":";AAOO,SAAS,aAAa,OAAuB;AAClD,MAAI,OAAO,SAAS,eAAe,eAAe,MAAM;AACtD,UAAM,YAAY,IAAI,KAAK,UAAU,QAAW,EAAE,aAAa,WAAW,CAAC;AAC3E,WAAO,CAAC,GAAG,UAAU,QAAQ,KAAK,CAAC,EAAE;AAAA,EACvC;AACA,SAAO,CAAC,GAAG,KAAK,EAAE;AACpB;AAOO,SAAS,WAAW,OAAuB;AAChD,SAAO,IAAI,YAAY,EAAE,OAAO,KAAK,EAAE;AACzC;","names":[]}
package/package.json ADDED
@@ -0,0 +1,94 @@
1
+ {
2
+ "name": "@berkayyalcin/utilkit",
3
+ "version": "0.1.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "description": "Zero-dependency devkit for everyday JavaScript/TypeScript utilities: Unicode-safe Base64, correct string length, and safe JSON parsing.",
8
+ "type": "module",
9
+ "main": "./dist/index.cjs",
10
+ "module": "./dist/index.mjs",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "import": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.mjs"
17
+ },
18
+ "require": {
19
+ "types": "./dist/index.d.cts",
20
+ "default": "./dist/index.cjs"
21
+ }
22
+ },
23
+ "./encoding": {
24
+ "import": {
25
+ "types": "./dist/encoding/index.d.ts",
26
+ "default": "./dist/encoding/index.mjs"
27
+ },
28
+ "require": {
29
+ "types": "./dist/encoding/index.d.cts",
30
+ "default": "./dist/encoding/index.cjs"
31
+ }
32
+ },
33
+ "./text": {
34
+ "import": {
35
+ "types": "./dist/text/index.d.ts",
36
+ "default": "./dist/text/index.mjs"
37
+ },
38
+ "require": {
39
+ "types": "./dist/text/index.d.cts",
40
+ "default": "./dist/text/index.cjs"
41
+ }
42
+ },
43
+ "./data": {
44
+ "import": {
45
+ "types": "./dist/data/index.d.ts",
46
+ "default": "./dist/data/index.mjs"
47
+ },
48
+ "require": {
49
+ "types": "./dist/data/index.d.cts",
50
+ "default": "./dist/data/index.cjs"
51
+ }
52
+ }
53
+ },
54
+ "typesVersions": {
55
+ "*": {
56
+ "encoding": ["./dist/encoding/index.d.ts"],
57
+ "text": ["./dist/text/index.d.ts"],
58
+ "data": ["./dist/data/index.d.ts"]
59
+ }
60
+ },
61
+ "files": ["dist"],
62
+ "sideEffects": false,
63
+ "license": "MIT",
64
+ "author": "Berkay Yalçın",
65
+ "homepage": "https://github.com/berkayyalcin7/utilkit#readme",
66
+ "repository": {
67
+ "type": "git",
68
+ "url": "git+https://github.com/berkayyalcin7/utilkit.git"
69
+ },
70
+ "bugs": {
71
+ "url": "https://github.com/berkayyalcin7/utilkit/issues"
72
+ },
73
+ "keywords": ["utility", "base64", "unicode", "json", "typescript", "zero-dependency", "devkit"],
74
+ "scripts": {
75
+ "build": "tsup",
76
+ "test": "vitest run",
77
+ "test:coverage": "vitest run --coverage",
78
+ "lint": "eslint .",
79
+ "typecheck": "tsc --noEmit",
80
+ "prepublishOnly": "npm run lint && npm run typecheck && npm run test && npm run build"
81
+ },
82
+ "devDependencies": {
83
+ "@eslint/js": "^9.17.0",
84
+ "@vitest/coverage-v8": "^3.0.0",
85
+ "eslint": "^9.17.0",
86
+ "tsup": "^8.3.5",
87
+ "typescript": "^5.7.2",
88
+ "typescript-eslint": "^8.18.0",
89
+ "vitest": "^3.0.0"
90
+ },
91
+ "engines": {
92
+ "node": ">=18"
93
+ }
94
+ }