wip library to store cold objects in s3, warm objects on disk, and hot objects in memory
nodejs
typescript
1/**
2 * Encode a key to be safe for use as a filesystem path.
3 *
4 * @param key - The key to encode
5 * @param encodeColons - Whether to encode colons as %3A (default: false)
6 * @returns Filesystem-safe encoded key
7 *
8 * @remarks
9 * Preserves forward slashes to create directory structure.
10 * Encodes characters that are problematic in filenames:
11 * - Backslash (\) → %5C
12 * - Colon (:) → %3A (when encodeColons is true, invalid on Windows)
13 * - Asterisk (*) → %2A
14 * - Question mark (?) → %3F
15 * - Quote (") → %22
16 * - Less than (<) → %3C
17 * - Greater than (>) → %3E
18 * - Pipe (|) → %7C
19 * - Percent (%) → %25
20 * - Null byte → %00
21 *
22 * @example
23 * ```typescript
24 * const key = 'did:plc:abc123/site/index.html';
25 *
26 * // Encode colons (Windows/cross-platform)
27 * const encoded = encodeKey(key, true);
28 * // Result: 'did%3Aplc%3Aabc123/site/index.html'
29 * // Creates: cache/did%3Aplc%3Aabc123/site/index.html
30 *
31 * // Preserve colons (Unix/macOS with readable paths)
32 * const readable = encodeKey(key, false);
33 * // Result: 'did:plc:abc123/site/index.html'
34 * // Creates: cache/did:plc:abc123/site/index.html
35 * ```
36 */
37export function encodeKey(key: string, encodeColons = false): string {
38 let result = key
39 .replace(/%/g, '%25') // Must be first!
40 .replace(/\\/g, '%5C');
41
42 if (encodeColons) {
43 result = result.replace(/:/g, '%3A');
44 }
45
46 return result
47 .replace(/\*/g, '%2A')
48 .replace(/\?/g, '%3F')
49 .replace(/"/g, '%22')
50 .replace(/</g, '%3C')
51 .replace(/>/g, '%3E')
52 .replace(/\|/g, '%7C')
53 .replace(/\0/g, '%00');
54}
55
56/**
57 * Decode a filesystem-safe key back to original form.
58 *
59 * @param encoded - The encoded key
60 * @param decodeColons - Whether to decode %3A to : (default: false)
61 * @returns Original key
62 *
63 * @example
64 * ```typescript
65 * const encoded = 'did%3Aplc%3Aabc123/site/index.html';
66 *
67 * // Decode with colons
68 * const key = decodeKey(encoded, true);
69 * // Result: 'did:plc:abc123/site/index.html'
70 *
71 * // Decode without colons (already readable)
72 * const readable = decodeKey('did:plc:abc123/site/index.html', false);
73 * // Result: 'did:plc:abc123/site/index.html'
74 * ```
75 */
76export function decodeKey(encoded: string, decodeColons = false): string {
77 let result = encoded
78 .replace(/%5C/g, '\\')
79 .replace(/%2A/g, '*')
80 .replace(/%3F/g, '?')
81 .replace(/%22/g, '"')
82 .replace(/%3C/g, '<')
83 .replace(/%3E/g, '>')
84 .replace(/%7C/g, '|')
85 .replace(/%00/g, '\0');
86
87 if (decodeColons) {
88 result = result.replace(/%3A/g, ':');
89 }
90
91 return result.replace(/%25/g, '%'); // Must be last!
92}