@wtasnorg/node-lib 0.0.7 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/changelog.txt +26 -0
  2. package/dev_checklist.txt +56 -0
  3. package/docs/README.md +15 -32
  4. package/docs/docs.json +916 -240
  5. package/docs/functions/createFindDirectories.md +2 -2
  6. package/docs/functions/decode.md +49 -0
  7. package/docs/functions/encode.md +45 -0
  8. package/docs/functions/hello.md +2 -2
  9. package/docs/functions/parseUserAgent.md +42 -0
  10. package/docs/functions/pojo.md +2 -2
  11. package/docs/interfaces/FileSystemDependencies.md +9 -9
  12. package/docs/interfaces/FindDirectoriesOptions.md +8 -8
  13. package/docs/interfaces/UserAgentInfo.md +61 -0
  14. package/docs/type-aliases/Base64CharsetType.md +13 -0
  15. package/docs/variables/Base64Charset.md +17 -0
  16. package/eslint.config.js +7 -2
  17. package/gen-docs/001_base64_refine.txt +50 -0
  18. package/gen-docs/001_commands.txt +44 -0
  19. package/gen-docs/001_coverage.txt +43 -0
  20. package/gen-docs/001_env.txt +33 -0
  21. package/gen-docs/001_lint.txt +40 -0
  22. package/gen-docs/001_state.txt +58 -0
  23. package/gen-docs/002_api.txt +34 -0
  24. package/gen-docs/002_deps.txt +46 -0
  25. package/gen-docs/002_errors.txt +34 -0
  26. package/gen-docs/002_naming.txt +36 -0
  27. package/gen-docs/002_notes.txt +20 -0
  28. package/gen-docs/002_purity.txt +36 -0
  29. package/gen-docs/002_scope.txt +28 -0
  30. package/gen-docs/002_srp.txt +34 -0
  31. package/gen-sec/001_base64_security.txt +75 -0
  32. package/gen-sec/001_commands.txt +65 -0
  33. package/gen-sec/001_env.txt +28 -0
  34. package/gen-sec/001_findings.txt +63 -0
  35. package/gen-sec/001_inventory.txt +41 -0
  36. package/gen-sec/001_owasp.txt +78 -0
  37. package/gen-sec/001_scope.txt +44 -0
  38. package/package.json +3 -2
  39. package/{README.md → readme.txt} +3 -1
  40. package/src/base64.d.ts +58 -0
  41. package/src/base64.js +138 -0
  42. package/src/base64.test.d.ts +2 -0
  43. package/src/base64.test.js +106 -0
  44. package/src/base64.test.ts +125 -0
  45. package/src/base64.ts +163 -0
  46. package/src/find.d.ts +4 -4
  47. package/src/find.js +12 -6
  48. package/src/find.ts +10 -10
  49. package/src/index.d.ts +6 -2
  50. package/src/index.js +3 -1
  51. package/src/index.ts +11 -1
  52. package/src/pojo.js +1 -1
  53. package/src/pojo.test.js +1 -3
  54. package/src/pojo.test.ts +2 -1
  55. package/src/pojo.ts +1 -1
  56. package/src/user-agent.d.ts +48 -0
  57. package/src/user-agent.js +189 -0
  58. package/src/user-agent.test.d.ts +2 -0
  59. package/src/user-agent.test.js +54 -0
  60. package/src/user-agent.test.ts +60 -0
  61. package/src/user-agent.ts +199 -0
  62. package/DEV_CHECKLIST.md +0 -15
  63. package/docs/_media/LICENSE +0 -21
  64. package/docs/globals.md +0 -16
@@ -10,6 +10,8 @@ A library project for nodejs. #nodejs #typescript #library
10
10
  1. hello (for debugging)
11
11
  2. `pojo` for converting class objects to Plain Old Javascript Objects.
12
12
  3. `createFindDirectories` as a factory for finding directories; think `find path -type d`.
13
+ 4. `parseUserAgent` for extracting information from user-agent strings.
14
+ 5. `encode` / `decode` for Base64 encoding/decoding with charset variants (`standard`, `urlsafe`, `imap`, `radix64`).
13
15
 
14
16
  ## Develop
15
17
 
@@ -32,7 +34,7 @@ npm install @wtasnorg/node-lib
32
34
  # check if you can run code
33
35
  import {hello} from "@wtasnorg/node-lib";
34
36
 
35
- await hello();
37
+ await hello();
36
38
  // "hello from @wtasnorg/node-lib"
37
39
  ```
38
40
 
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Base64 encoding/decoding with multiple charset variants.
3
+ * @module base64
4
+ */
5
+ /**
6
+ * Available Base64 charset variants.
7
+ * - `standard`: RFC 4648 standard alphabet (A-Z, a-z, 0-9, +, /)
8
+ * - `urlsafe`: URL and filename safe (A-Z, a-z, 0-9, -, _)
9
+ * - `imap`: Modified Base64 for IMAP mailbox names (A-Z, a-z, 0-9, +, ,)
10
+ * - `radix64`: Base64 variant used in OpenPGP (A-Z, a-z, 0-9, +, /)
11
+ */
12
+ declare const Base64Charset: readonly ["standard", "urlsafe", "imap", "radix64"];
13
+ /**
14
+ * Base64 charset type.
15
+ */
16
+ type Base64CharsetType = (typeof Base64Charset)[number];
17
+ /**
18
+ * Encode a string to Base64.
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * import { encode } from "./base64.js";
23
+ *
24
+ * encode("Hello, World!");
25
+ * // => "SGVsbG8sIFdvcmxkIQ=="
26
+ *
27
+ * encode("Hello, World!", "urlsafe");
28
+ * // => "SGVsbG8sIFdvcmxkIQ=="
29
+ * ```
30
+ *
31
+ * @param input - The string to encode.
32
+ * @param charset - The charset variant to use (default: "standard").
33
+ * @returns The Base64 encoded string.
34
+ */
35
+ declare function encode(input: string, charset?: Base64CharsetType): string;
36
+ /**
37
+ * Decode a Base64 string.
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * import { decode } from "./base64.js";
42
+ *
43
+ * decode("SGVsbG8sIFdvcmxkIQ==");
44
+ * // => "Hello, World!"
45
+ *
46
+ * decode("SGVsbG8sIFdvcmxkIQ==", "standard");
47
+ * // => "Hello, World!"
48
+ * ```
49
+ *
50
+ * @param input - The Base64 encoded string.
51
+ * @param charset - The charset variant to use (default: "standard").
52
+ * @returns The decoded string.
53
+ * @throws Error if the input contains invalid characters.
54
+ */
55
+ declare function decode(input: string, charset?: Base64CharsetType): string;
56
+ export { encode, decode, Base64Charset };
57
+ export type { Base64CharsetType };
58
+ //# sourceMappingURL=base64.d.ts.map
package/src/base64.js ADDED
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Base64 encoding/decoding with multiple charset variants.
3
+ * @module base64
4
+ */
5
+ /**
6
+ * Available Base64 charset variants.
7
+ * - `standard`: RFC 4648 standard alphabet (A-Z, a-z, 0-9, +, /)
8
+ * - `urlsafe`: URL and filename safe (A-Z, a-z, 0-9, -, _)
9
+ * - `imap`: Modified Base64 for IMAP mailbox names (A-Z, a-z, 0-9, +, ,)
10
+ * - `radix64`: Base64 variant used in OpenPGP (A-Z, a-z, 0-9, +, /)
11
+ */
12
+ const Base64Charset = ["standard", "urlsafe", "imap", "radix64"];
13
+ /**
14
+ * Charset alphabets for Base64 variants.
15
+ */
16
+ const CHARSETS = {
17
+ standard: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
18
+ urlsafe: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
19
+ imap: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,",
20
+ radix64: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
21
+ };
22
+ /**
23
+ * Padding character for Base64 encoding.
24
+ */
25
+ const PAD = "=";
26
+ /**
27
+ * Build a reverse lookup table for decoding.
28
+ * @param alphabet - The 64-character alphabet string.
29
+ * @returns Map of character to index.
30
+ */
31
+ function buildDecodeTable(alphabet) {
32
+ const table = new Map();
33
+ for (let i = 0; i < alphabet.length; i++) {
34
+ const char = alphabet[i];
35
+ if (char !== undefined) {
36
+ table.set(char, i);
37
+ }
38
+ }
39
+ return table;
40
+ }
41
+ /**
42
+ * Pre-built decode tables for each charset.
43
+ */
44
+ const DECODE_TABLES = {
45
+ standard: buildDecodeTable(CHARSETS.standard),
46
+ urlsafe: buildDecodeTable(CHARSETS.urlsafe),
47
+ imap: buildDecodeTable(CHARSETS.imap),
48
+ radix64: buildDecodeTable(CHARSETS.radix64)
49
+ };
50
+ /**
51
+ * Encode a string to Base64.
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * import { encode } from "./base64.js";
56
+ *
57
+ * encode("Hello, World!");
58
+ * // => "SGVsbG8sIFdvcmxkIQ=="
59
+ *
60
+ * encode("Hello, World!", "urlsafe");
61
+ * // => "SGVsbG8sIFdvcmxkIQ=="
62
+ * ```
63
+ *
64
+ * @param input - The string to encode.
65
+ * @param charset - The charset variant to use (default: "standard").
66
+ * @returns The Base64 encoded string.
67
+ */
68
+ function encode(input, charset = "standard") {
69
+ const alphabet = CHARSETS[charset];
70
+ const bytes = new TextEncoder().encode(input);
71
+ let result = "";
72
+ for (let i = 0; i < bytes.length; i += 3) {
73
+ const b0 = bytes[i] ?? 0;
74
+ const b1 = i + 1 < bytes.length ? (bytes[i + 1] ?? 0) : 0;
75
+ const b2 = i + 2 < bytes.length ? (bytes[i + 2] ?? 0) : 0;
76
+ const triple = (b0 << 16) | (b1 << 8) | b2;
77
+ result += alphabet[(triple >> 18) & 0x3f];
78
+ result += alphabet[(triple >> 12) & 0x3f];
79
+ result += i + 1 < bytes.length ? alphabet[(triple >> 6) & 0x3f] : PAD;
80
+ result += i + 2 < bytes.length ? alphabet[triple & 0x3f] : PAD;
81
+ }
82
+ return result;
83
+ }
84
+ /**
85
+ * Decode a Base64 string.
86
+ *
87
+ * @example
88
+ * ```typescript
89
+ * import { decode } from "./base64.js";
90
+ *
91
+ * decode("SGVsbG8sIFdvcmxkIQ==");
92
+ * // => "Hello, World!"
93
+ *
94
+ * decode("SGVsbG8sIFdvcmxkIQ==", "standard");
95
+ * // => "Hello, World!"
96
+ * ```
97
+ *
98
+ * @param input - The Base64 encoded string.
99
+ * @param charset - The charset variant to use (default: "standard").
100
+ * @returns The decoded string.
101
+ * @throws Error if the input contains invalid characters.
102
+ */
103
+ function decode(input, charset = "standard") {
104
+ const decodeTable = DECODE_TABLES[charset];
105
+ // Remove padding
106
+ const cleanInput = input.replace(/=+$/, "");
107
+ const bytes = [];
108
+ for (let i = 0; i < cleanInput.length; i += 4) {
109
+ const c0 = cleanInput[i];
110
+ const c1 = cleanInput[i + 1];
111
+ const c2 = cleanInput[i + 2];
112
+ const c3 = cleanInput[i + 3];
113
+ if (c0 === undefined) {
114
+ break;
115
+ }
116
+ const v0 = decodeTable.get(c0);
117
+ const v1 = c1 !== undefined ? decodeTable.get(c1) : 0;
118
+ const v2 = c2 !== undefined ? decodeTable.get(c2) : 0;
119
+ const v3 = c3 !== undefined ? decodeTable.get(c3) : 0;
120
+ if (v0 === undefined) {
121
+ throw new Error(`Invalid Base64 character: ${c0}`);
122
+ }
123
+ if (c1 !== undefined && v1 === undefined) {
124
+ throw new Error(`Invalid Base64 character: ${c1}`);
125
+ }
126
+ const triple = ((v0 ?? 0) << 18) | ((v1 ?? 0) << 12) | ((v2 ?? 0) << 6) | (v3 ?? 0);
127
+ bytes.push((triple >> 16) & 0xff);
128
+ if (c2 !== undefined) {
129
+ bytes.push((triple >> 8) & 0xff);
130
+ }
131
+ if (c3 !== undefined) {
132
+ bytes.push(triple & 0xff);
133
+ }
134
+ }
135
+ return new TextDecoder().decode(new Uint8Array(bytes));
136
+ }
137
+ export { encode, decode, Base64Charset };
138
+ //# sourceMappingURL=base64.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=base64.test.d.ts.map
@@ -0,0 +1,106 @@
1
+ import { describe, it } from "node:test";
2
+ import { strictEqual, throws } from "node:assert";
3
+ import { encode, decode, Base64Charset } from "./base64.js";
4
+ describe("Base64 encode", () => {
5
+ it("encodes empty string", () => {
6
+ strictEqual(encode(""), "");
7
+ });
8
+ it("encodes 'Hello, World!' with standard charset", () => {
9
+ strictEqual(encode("Hello, World!"), "SGVsbG8sIFdvcmxkIQ==");
10
+ });
11
+ it("encodes single character", () => {
12
+ strictEqual(encode("A"), "QQ==");
13
+ });
14
+ it("encodes two characters (padding test)", () => {
15
+ strictEqual(encode("AB"), "QUI=");
16
+ });
17
+ it("encodes three characters (no padding)", () => {
18
+ strictEqual(encode("ABC"), "QUJD");
19
+ });
20
+ it("encodes with urlsafe charset", () => {
21
+ // Bytes that produce +/ in standard: 0xfb, 0xef, 0xbe -> ++/+
22
+ const input = "\xfb\xef\xbe";
23
+ const standard = encode(input, "standard");
24
+ const urlsafe = encode(input, "urlsafe");
25
+ // standard uses + and /, urlsafe uses - and _
26
+ strictEqual(standard.includes("+") || standard.includes("/"), true);
27
+ strictEqual(urlsafe.includes("+"), false);
28
+ strictEqual(urlsafe.includes("/"), false);
29
+ });
30
+ it("encodes with imap charset", () => {
31
+ // imap uses , instead of /
32
+ const input = "\xff\xff";
33
+ const standard = encode(input, "standard");
34
+ const imap = encode(input, "imap");
35
+ strictEqual(imap.includes(",") || !imap.includes("/"), true);
36
+ strictEqual(standard !== imap || !standard.includes("/"), true);
37
+ });
38
+ it("encodes UTF-8 characters", () => {
39
+ const input = "こんにちは";
40
+ const encoded = encode(input);
41
+ const decoded = decode(encoded);
42
+ strictEqual(decoded, input);
43
+ });
44
+ });
45
+ describe("Base64 decode", () => {
46
+ it("decodes empty string", () => {
47
+ strictEqual(decode(""), "");
48
+ });
49
+ it("decodes 'SGVsbG8sIFdvcmxkIQ==' to 'Hello, World!'", () => {
50
+ strictEqual(decode("SGVsbG8sIFdvcmxkIQ=="), "Hello, World!");
51
+ });
52
+ it("decodes single character padded", () => {
53
+ strictEqual(decode("QQ=="), "A");
54
+ });
55
+ it("decodes two characters padded", () => {
56
+ strictEqual(decode("QUI="), "AB");
57
+ });
58
+ it("decodes three characters (no padding)", () => {
59
+ strictEqual(decode("QUJD"), "ABC");
60
+ });
61
+ it("throws on invalid character", () => {
62
+ throws(() => decode("!!!"), /Invalid Base64 character/);
63
+ });
64
+ it("decodes without padding characters", () => {
65
+ strictEqual(decode("SGVsbG8sIFdvcmxkIQ"), "Hello, World!");
66
+ });
67
+ });
68
+ describe("Base64 round-trip", () => {
69
+ const testCases = [
70
+ "",
71
+ "a",
72
+ "ab",
73
+ "abc",
74
+ "abcd",
75
+ "Hello, World!",
76
+ "The quick brown fox jumps over the lazy dog",
77
+ "こんにちは世界",
78
+ "🎉🚀✨",
79
+ "\x00\x01\x02\xff"
80
+ ];
81
+ for (const charset of Base64Charset) {
82
+ describe(`charset: ${charset}`, () => {
83
+ for (const input of testCases) {
84
+ it(`round-trips: ${JSON.stringify(input)}`, () => {
85
+ const encoded = encode(input, charset);
86
+ const decoded = decode(encoded, charset);
87
+ strictEqual(decoded, input);
88
+ });
89
+ }
90
+ });
91
+ }
92
+ });
93
+ describe("Base64Charset", () => {
94
+ it("exports array with four charsets", () => {
95
+ strictEqual(Base64Charset.length, 4);
96
+ strictEqual(Base64Charset[0], "standard");
97
+ strictEqual(Base64Charset[1], "urlsafe");
98
+ strictEqual(Base64Charset[2], "imap");
99
+ strictEqual(Base64Charset[3], "radix64");
100
+ });
101
+ it("charset type works correctly", () => {
102
+ const cs = "urlsafe";
103
+ strictEqual(encode("test", cs), encode("test", "urlsafe"));
104
+ });
105
+ });
106
+ //# sourceMappingURL=base64.test.js.map
@@ -0,0 +1,125 @@
1
+ import { describe, it } from "node:test";
2
+ import { strictEqual, throws } from "node:assert";
3
+ import { encode, decode, Base64Charset } from "./base64.js";
4
+ import type { Base64CharsetType } from "./base64.js";
5
+
6
+ describe("Base64 encode", () => {
7
+ it("encodes empty string", () => {
8
+ strictEqual(encode(""), "");
9
+ });
10
+
11
+ it("encodes 'Hello, World!' with standard charset", () => {
12
+ strictEqual(encode("Hello, World!"), "SGVsbG8sIFdvcmxkIQ==");
13
+ });
14
+
15
+ it("encodes single character", () => {
16
+ strictEqual(encode("A"), "QQ==");
17
+ });
18
+
19
+ it("encodes two characters (padding test)", () => {
20
+ strictEqual(encode("AB"), "QUI=");
21
+ });
22
+
23
+ it("encodes three characters (no padding)", () => {
24
+ strictEqual(encode("ABC"), "QUJD");
25
+ });
26
+
27
+ it("encodes with urlsafe charset", () => {
28
+ // Bytes that produce +/ in standard: 0xfb, 0xef, 0xbe -> ++/+
29
+ const input = "\xfb\xef\xbe";
30
+ const standard = encode(input, "standard");
31
+ const urlsafe = encode(input, "urlsafe");
32
+ // standard uses + and /, urlsafe uses - and _
33
+ strictEqual(standard.includes("+") || standard.includes("/"), true);
34
+ strictEqual(urlsafe.includes("+"), false);
35
+ strictEqual(urlsafe.includes("/"), false);
36
+ });
37
+
38
+ it("encodes with imap charset", () => {
39
+ // imap uses , instead of /
40
+ const input = "\xff\xff";
41
+ const standard = encode(input, "standard");
42
+ const imap = encode(input, "imap");
43
+ strictEqual(imap.includes(",") || !imap.includes("/"), true);
44
+ strictEqual(standard !== imap || !standard.includes("/"), true);
45
+ });
46
+
47
+ it("encodes UTF-8 characters", () => {
48
+ const input = "こんにちは";
49
+ const encoded = encode(input);
50
+ const decoded = decode(encoded);
51
+ strictEqual(decoded, input);
52
+ });
53
+ });
54
+
55
+ describe("Base64 decode", () => {
56
+ it("decodes empty string", () => {
57
+ strictEqual(decode(""), "");
58
+ });
59
+
60
+ it("decodes 'SGVsbG8sIFdvcmxkIQ==' to 'Hello, World!'", () => {
61
+ strictEqual(decode("SGVsbG8sIFdvcmxkIQ=="), "Hello, World!");
62
+ });
63
+
64
+ it("decodes single character padded", () => {
65
+ strictEqual(decode("QQ=="), "A");
66
+ });
67
+
68
+ it("decodes two characters padded", () => {
69
+ strictEqual(decode("QUI="), "AB");
70
+ });
71
+
72
+ it("decodes three characters (no padding)", () => {
73
+ strictEqual(decode("QUJD"), "ABC");
74
+ });
75
+
76
+ it("throws on invalid character", () => {
77
+ throws(() => decode("!!!"), /Invalid Base64 character/);
78
+ });
79
+
80
+ it("decodes without padding characters", () => {
81
+ strictEqual(decode("SGVsbG8sIFdvcmxkIQ"), "Hello, World!");
82
+ });
83
+ });
84
+
85
+ describe("Base64 round-trip", () => {
86
+ const testCases = [
87
+ "",
88
+ "a",
89
+ "ab",
90
+ "abc",
91
+ "abcd",
92
+ "Hello, World!",
93
+ "The quick brown fox jumps over the lazy dog",
94
+ "こんにちは世界",
95
+ "🎉🚀✨",
96
+ "\x00\x01\x02\xff"
97
+ ];
98
+
99
+ for (const charset of Base64Charset) {
100
+ describe(`charset: ${charset}`, () => {
101
+ for (const input of testCases) {
102
+ it(`round-trips: ${JSON.stringify(input)}`, () => {
103
+ const encoded = encode(input, charset);
104
+ const decoded = decode(encoded, charset);
105
+ strictEqual(decoded, input);
106
+ });
107
+ }
108
+ });
109
+ }
110
+ });
111
+
112
+ describe("Base64Charset", () => {
113
+ it("exports array with four charsets", () => {
114
+ strictEqual(Base64Charset.length, 4);
115
+ strictEqual(Base64Charset[0], "standard");
116
+ strictEqual(Base64Charset[1], "urlsafe");
117
+ strictEqual(Base64Charset[2], "imap");
118
+ strictEqual(Base64Charset[3], "radix64");
119
+ });
120
+
121
+ it("charset type works correctly", () => {
122
+ const cs: Base64CharsetType = "urlsafe";
123
+ strictEqual(encode("test", cs), encode("test", "urlsafe"));
124
+ });
125
+ });
package/src/base64.ts ADDED
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Base64 encoding/decoding with multiple charset variants.
3
+ * @module base64
4
+ */
5
+
6
+ /**
7
+ * Available Base64 charset variants.
8
+ * - `standard`: RFC 4648 standard alphabet (A-Z, a-z, 0-9, +, /)
9
+ * - `urlsafe`: URL and filename safe (A-Z, a-z, 0-9, -, _)
10
+ * - `imap`: Modified Base64 for IMAP mailbox names (A-Z, a-z, 0-9, +, ,)
11
+ * - `radix64`: Base64 variant used in OpenPGP (A-Z, a-z, 0-9, +, /)
12
+ */
13
+ const Base64Charset = ["standard", "urlsafe", "imap", "radix64"] as const;
14
+
15
+ /**
16
+ * Base64 charset type.
17
+ */
18
+ type Base64CharsetType = (typeof Base64Charset)[number];
19
+
20
+ /**
21
+ * Charset alphabets for Base64 variants.
22
+ */
23
+ const CHARSETS: Record<Base64CharsetType, string> = {
24
+ standard: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
25
+ urlsafe: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
26
+ imap: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,",
27
+ radix64: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
28
+ };
29
+
30
+ /**
31
+ * Padding character for Base64 encoding.
32
+ */
33
+ const PAD = "=";
34
+
35
+ /**
36
+ * Build a reverse lookup table for decoding.
37
+ * @param alphabet - The 64-character alphabet string.
38
+ * @returns Map of character to index.
39
+ */
40
+ function buildDecodeTable(alphabet: string): Map<string, number> {
41
+ const table = new Map<string, number>();
42
+ for (let i = 0; i < alphabet.length; i++) {
43
+ const char = alphabet[i];
44
+ if (char !== undefined) {
45
+ table.set(char, i);
46
+ }
47
+ }
48
+ return table;
49
+ }
50
+
51
+ /**
52
+ * Pre-built decode tables for each charset.
53
+ */
54
+ const DECODE_TABLES: Record<Base64CharsetType, Map<string, number>> = {
55
+ standard: buildDecodeTable(CHARSETS.standard),
56
+ urlsafe: buildDecodeTable(CHARSETS.urlsafe),
57
+ imap: buildDecodeTable(CHARSETS.imap),
58
+ radix64: buildDecodeTable(CHARSETS.radix64)
59
+ };
60
+
61
+ /**
62
+ * Encode a string to Base64.
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * import { encode } from "./base64.js";
67
+ *
68
+ * encode("Hello, World!");
69
+ * // => "SGVsbG8sIFdvcmxkIQ=="
70
+ *
71
+ * encode("Hello, World!", "urlsafe");
72
+ * // => "SGVsbG8sIFdvcmxkIQ=="
73
+ * ```
74
+ *
75
+ * @param input - The string to encode.
76
+ * @param charset - The charset variant to use (default: "standard").
77
+ * @returns The Base64 encoded string.
78
+ */
79
+ function encode(input: string, charset: Base64CharsetType = "standard"): string {
80
+ const alphabet = CHARSETS[charset];
81
+ const bytes = new TextEncoder().encode(input);
82
+ let result = "";
83
+
84
+ for (let i = 0; i < bytes.length; i += 3) {
85
+ const b0 = bytes[i] ?? 0;
86
+ const b1 = i + 1 < bytes.length ? (bytes[i + 1] ?? 0) : 0;
87
+ const b2 = i + 2 < bytes.length ? (bytes[i + 2] ?? 0) : 0;
88
+
89
+ const triple = (b0 << 16) | (b1 << 8) | b2;
90
+
91
+ result += alphabet[(triple >> 18) & 0x3f];
92
+ result += alphabet[(triple >> 12) & 0x3f];
93
+ result += i + 1 < bytes.length ? alphabet[(triple >> 6) & 0x3f] : PAD;
94
+ result += i + 2 < bytes.length ? alphabet[triple & 0x3f] : PAD;
95
+ }
96
+
97
+ return result;
98
+ }
99
+
100
+ /**
101
+ * Decode a Base64 string.
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * import { decode } from "./base64.js";
106
+ *
107
+ * decode("SGVsbG8sIFdvcmxkIQ==");
108
+ * // => "Hello, World!"
109
+ *
110
+ * decode("SGVsbG8sIFdvcmxkIQ==", "standard");
111
+ * // => "Hello, World!"
112
+ * ```
113
+ *
114
+ * @param input - The Base64 encoded string.
115
+ * @param charset - The charset variant to use (default: "standard").
116
+ * @returns The decoded string.
117
+ * @throws Error if the input contains invalid characters.
118
+ */
119
+ function decode(input: string, charset: Base64CharsetType = "standard"): string {
120
+ const decodeTable = DECODE_TABLES[charset];
121
+
122
+ // Remove padding
123
+ const cleanInput = input.replace(/=+$/, "");
124
+ const bytes: number[] = [];
125
+
126
+ for (let i = 0; i < cleanInput.length; i += 4) {
127
+ const c0 = cleanInput[i];
128
+ const c1 = cleanInput[i + 1];
129
+ const c2 = cleanInput[i + 2];
130
+ const c3 = cleanInput[i + 3];
131
+
132
+ if (c0 === undefined) {
133
+ break;
134
+ }
135
+
136
+ const v0 = decodeTable.get(c0);
137
+ const v1 = c1 !== undefined ? decodeTable.get(c1) : 0;
138
+ const v2 = c2 !== undefined ? decodeTable.get(c2) : 0;
139
+ const v3 = c3 !== undefined ? decodeTable.get(c3) : 0;
140
+
141
+ if (v0 === undefined) {
142
+ throw new Error(`Invalid Base64 character: ${c0}`);
143
+ }
144
+ if (c1 !== undefined && v1 === undefined) {
145
+ throw new Error(`Invalid Base64 character: ${c1}`);
146
+ }
147
+
148
+ const triple = ((v0 ?? 0) << 18) | ((v1 ?? 0) << 12) | ((v2 ?? 0) << 6) | (v3 ?? 0);
149
+
150
+ bytes.push((triple >> 16) & 0xff);
151
+ if (c2 !== undefined) {
152
+ bytes.push((triple >> 8) & 0xff);
153
+ }
154
+ if (c3 !== undefined) {
155
+ bytes.push(triple & 0xff);
156
+ }
157
+ }
158
+
159
+ return new TextDecoder().decode(new Uint8Array(bytes));
160
+ }
161
+
162
+ export { encode, decode, Base64Charset };
163
+ export type { Base64CharsetType };
package/src/find.d.ts CHANGED
@@ -1,19 +1,19 @@
1
1
  interface FileSystemDependencies {
2
- readdir: (path: string, opts: {
2
+ readdir: (_path: string, _opts: {
3
3
  withFileTypes: true;
4
4
  }) => Promise<{
5
5
  name: string;
6
6
  isDirectory(): boolean;
7
7
  }[]>;
8
- stat: (path: string) => Promise<{
8
+ stat: (_path: string) => Promise<{
9
9
  isDirectory(): boolean;
10
10
  }>;
11
11
  }
12
12
  interface FindDirectoriesOptions {
13
13
  maxDepth?: number;
14
14
  followSymlinks?: boolean;
15
- allowlist?: string[] | ((absPath: string, name: string) => boolean);
16
- blocklist?: string[] | ((absPath: string, name: string) => boolean);
15
+ allowlist?: string[] | ((_absPath: string, _name: string) => boolean);
16
+ blocklist?: string[] | ((_absPath: string, _name: string) => boolean);
17
17
  }
18
18
  /**
19
19
  * Factory that produces an async findDirectories function with
package/src/find.js CHANGED
@@ -12,8 +12,9 @@ function createFindDirectories(deps) {
12
12
  function isAllowed(absPath, name) {
13
13
  if (allowlist) {
14
14
  if (Array.isArray(allowlist)) {
15
- if (!allowlist.includes(name))
15
+ if (!allowlist.includes(name)) {
16
16
  return false;
17
+ }
17
18
  }
18
19
  else if (!allowlist(absPath, name)) {
19
20
  return false;
@@ -24,8 +25,9 @@ function createFindDirectories(deps) {
24
25
  function isBlocked(absPath, name) {
25
26
  if (blocklist) {
26
27
  if (Array.isArray(blocklist)) {
27
- if (blocklist.includes(name))
28
+ if (blocklist.includes(name)) {
28
29
  return true;
30
+ }
29
31
  }
30
32
  else if (blocklist(absPath, name)) {
31
33
  return true;
@@ -34,18 +36,22 @@ function createFindDirectories(deps) {
34
36
  return false;
35
37
  }
36
38
  async function walk(currentPath, depth) {
37
- if (depth > maxDepth)
39
+ if (depth > maxDepth) {
38
40
  return;
41
+ }
39
42
  const entries = await readdir(currentPath, { withFileTypes: true });
40
43
  for (const entry of entries) {
41
44
  const childPath = resolve(join(currentPath, entry.name));
42
45
  const isDirectory = entry.isDirectory();
43
- if (!isDirectory)
46
+ if (!isDirectory) {
44
47
  continue;
45
- if (isBlocked(childPath, entry.name))
48
+ }
49
+ if (isBlocked(childPath, entry.name)) {
46
50
  continue;
47
- if (!isAllowed(childPath, entry.name))
51
+ }
52
+ if (!isAllowed(childPath, entry.name)) {
48
53
  continue;
54
+ }
49
55
  results.push(childPath);
50
56
  if (depth < maxDepth) {
51
57
  await walk(childPath, depth + 1);