cipher-kit 2.0.0-beta.3 → 2.0.0-beta.5
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 +199 -111
- package/dist/{chunk-ZJ32WGAA.cjs → chunk-3FJZA77A.cjs} +15 -12
- package/dist/chunk-3FJZA77A.cjs.map +1 -0
- package/dist/{chunk-BNQERV4S.js → chunk-7WPZVN7G.js} +71 -84
- package/dist/chunk-7WPZVN7G.js.map +1 -0
- package/dist/{chunk-6GBH7YTP.js → chunk-BWE6JWHY.js} +67 -81
- package/dist/chunk-BWE6JWHY.js.map +1 -0
- package/dist/{chunk-NKLNWTQA.cjs → chunk-CEXY7GOU.cjs} +159 -173
- package/dist/chunk-CEXY7GOU.cjs.map +1 -0
- package/dist/{chunk-UHP3PPXP.cjs → chunk-WLLCFK4U.cjs} +150 -165
- package/dist/chunk-WLLCFK4U.cjs.map +1 -0
- package/dist/{chunk-YPYDYYV2.js → chunk-YAZRJN6X.js} +13 -11
- package/dist/chunk-YAZRJN6X.js.map +1 -0
- package/dist/export-DPAoLdh1.d.ts +417 -0
- package/dist/export-DX7bFv-3.d.cts +416 -0
- package/dist/export-DjUgZ7dz.d.ts +416 -0
- package/dist/export-Du70yDea.d.cts +417 -0
- package/dist/index.cjs +14 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +52 -4
- package/dist/index.d.ts +52 -4
- package/dist/index.js +9 -3
- package/dist/index.js.map +1 -1
- package/dist/node.cjs +34 -42
- package/dist/node.d.cts +2 -2
- package/dist/node.d.ts +2 -2
- package/dist/node.js +2 -2
- package/dist/validate-DrBddQyu.d.cts +384 -0
- package/dist/validate-DrBddQyu.d.ts +384 -0
- package/dist/web-api.cjs +34 -42
- package/dist/web-api.d.cts +2 -2
- package/dist/web-api.d.ts +2 -2
- package/dist/web-api.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-6GBH7YTP.js.map +0 -1
- package/dist/chunk-BNQERV4S.js.map +0 -1
- package/dist/chunk-NKLNWTQA.cjs.map +0 -1
- package/dist/chunk-UHP3PPXP.cjs.map +0 -1
- package/dist/chunk-YPYDYYV2.js.map +0 -1
- package/dist/chunk-ZJ32WGAA.cjs.map +0 -1
- package/dist/export--ndIQ3j3.d.cts +0 -271
- package/dist/export-C2M5UPLX.d.cts +0 -270
- package/dist/export-CPUbAFZA.d.ts +0 -271
- package/dist/export-v9ULdDL0.d.ts +0 -270
- package/dist/validate-CULVlPck.d.cts +0 -157
- package/dist/validate-CULVlPck.d.ts +0 -157
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
<h1 align="center" style="font-weight:900;">cipher-kit</h1>
|
|
5
5
|
|
|
6
|
+
<!-- TODO -->
|
|
6
7
|
<p align="center">
|
|
7
8
|
Secure, Lightweight, and Cross-Platform <br/>
|
|
8
9
|
Encryption, Decryption, and Hashing <br/>
|
|
@@ -16,148 +17,235 @@
|
|
|
16
17
|
|
|
17
18
|
</div>
|
|
18
19
|
|
|
19
|
-
##
|
|
20
|
+
## Why `cipher-kit`? 🤔
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
- 🛡️ **AES-GCM Encryption** – Secure and authenticated encryption with built-in integrity checks.
|
|
27
|
-
- 🌐 **Cross-Platform** – Works in Web, Node.js, Deno, and Bun without code changes.
|
|
22
|
+
- 🛡️ **Secure and Flexible** - Uses best practices and modern cryptographic techniques, while providing a flexible and simple API.
|
|
23
|
+
- 📦 **All-in-One Toolkit** – Combines encryption, hashing, encoding, serialization, and more into a single package.
|
|
24
|
+
- 🌐 **Cross-Platform** – Works seamlessly across Web, Node.js, Deno, Bun, and Cloudflare Workers.
|
|
25
|
+
- 💡 **Typed and Ergonomic** - Type safe and provides throwing and non-throwing (`Result`) APIs.
|
|
26
|
+
- 🌳 **Tree-Shakable** - import root or platform-specific modules to reduce bundle size.
|
|
28
27
|
- 🚫 **Zero Dependencies** – Fully self-contained, no external libraries required.
|
|
29
|
-
-
|
|
30
|
-
- 🧪 **Strict Validation & `Result<T>` Typing** – Unified return type with robust input validation.
|
|
28
|
+
- 🍼 **Explain like I'm five** - Newbie-friendly explanations and documentation.
|
|
31
29
|
|
|
32
30
|
## Installation 🔥
|
|
33
31
|
|
|
34
32
|
```bash
|
|
35
33
|
npm install cipher-kit@latest
|
|
34
|
+
# or
|
|
35
|
+
yarn add cipher-kit@latest
|
|
36
|
+
# or
|
|
37
|
+
pnpm install cipher-kit@latest
|
|
38
|
+
# or
|
|
39
|
+
bun add cipher-kit@latest
|
|
36
40
|
```
|
|
37
41
|
|
|
38
|
-
> 💡 Works with `npm`, `pnpm`, `yarn`, `bun`, and `deno`. You can use it in dev dependencies since it's typically used only for local HTTPS.
|
|
39
|
-
|
|
40
42
|
## Usage 🪛
|
|
41
43
|
|
|
42
|
-
|
|
44
|
+
### The `try` Prefix (Non-Throwing `Result` API)
|
|
45
|
+
|
|
46
|
+
The `try` prefix functions return a `Result<T>` object that indicates success or failure without throwing exceptions.
|
|
47
|
+
|
|
48
|
+
This is useful in scenarios where you want to handle errors gracefully without using `try/catch` blocks.
|
|
43
49
|
|
|
44
50
|
```typescript
|
|
45
|
-
//
|
|
46
|
-
|
|
51
|
+
// Throwing version - simpler but requires try/catch
|
|
52
|
+
const message = encrypt('Secret message', secretKey);
|
|
53
|
+
console.log(`Encrypted message: ${message}`);
|
|
54
|
+
|
|
55
|
+
// Non-throwing version - returns a Result<T> object
|
|
56
|
+
const message = tryEncrypt('Secret message', secretKey);
|
|
57
|
+
if (message.success) {
|
|
58
|
+
console.log(`Encrypted message: ${message.result}`);
|
|
59
|
+
} else {
|
|
60
|
+
console.error(`Encryption failed: ${message.error.message} - ${message.error.description}`);
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### The `webKit` and `nodeKit` Objects
|
|
65
|
+
|
|
66
|
+
The `webKit` and `nodeKit` objects provide platform-specific implementations for Web (including Deno, Bun, and Cloudflare Workers) and Node.js environments, respectively.
|
|
47
67
|
|
|
48
|
-
|
|
49
|
-
|
|
68
|
+
You can also import them directly from `cipher-kit/web-api` and `cipher-kit/node` for smaller bundle sizes.
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { webKit, nodeKit } from 'cipher-kit';
|
|
72
|
+
import { isNodeSecretKey } from 'cipher-kit/node';
|
|
73
|
+
import { isWebSecretKey } from 'cipher-kit/web-api';
|
|
50
74
|
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
75
|
+
// These are the same:
|
|
76
|
+
function isSecretKey(key: unknown): boolean {
|
|
77
|
+
return isNodeSecretKey(key) || isWebSecretKey(key);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isSecretKey(key: unknown): boolean {
|
|
81
|
+
return nodeKit.isNodeSecretKey(key) || webKit.isWebSecretKey(key);
|
|
82
|
+
}
|
|
55
83
|
```
|
|
56
84
|
|
|
57
|
-
|
|
85
|
+
### Encryption and Decryption
|
|
86
|
+
|
|
87
|
+
Encryption is the process of converting readable plaintext into unreadable ciphertext using an algorithm and a secret key to protect its confidentiality. Decryption is the reverse process, using the same algorithm and the correct key to convert the ciphertext back into its original, readable plaintext form.
|
|
88
|
+
|
|
89
|
+
The package provides functions for both encryption and decryption. On top of that the function provides `encryptObj` and `decryptObj` functions that work the same way but for objects (using JSON serialization).
|
|
58
90
|
|
|
59
|
-
|
|
91
|
+
#### Secret Key Creation
|
|
60
92
|
|
|
61
|
-
|
|
93
|
+
Before encrypting or decrypting data, you need to create a secret key.
|
|
94
|
+
|
|
95
|
+
Each key is tied to a specific platform (Web or Node.js) and cannot be used interchangeably.
|
|
62
96
|
|
|
63
97
|
```typescript
|
|
64
|
-
import {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const encrypted = encrypt(str, secretKey);
|
|
86
|
-
console.log(`Encrypted Data: ${encrypted}`);
|
|
87
|
-
console.log(`Decrypted Data: ${decrypt(encrypted, secretKey)}`);
|
|
88
|
-
|
|
89
|
-
const encryptedObj = encryptObj({ message: 'Hello, World! 🌍', count: 42 }, secretKey);
|
|
90
|
-
console.log(`Encrypted Object: ${encryptedObj}`);
|
|
91
|
-
console.log(`Decrypted Object: ${JSON.stringify(decryptObj(encryptedObj, secretKey))}`);
|
|
92
|
-
|
|
93
|
-
const { result: tryEncrypted, error: tryEncryptError } = tryEncrypt(str, secretKey);
|
|
94
|
-
if (tryEncryptError) {
|
|
95
|
-
console.error(`Encryption Try failed: ${tryEncryptError.message} - ${tryEncryptError.description}`);
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
console.log(`Encrypted Try Data: ${tryEncrypted}`);
|
|
99
|
-
|
|
100
|
-
const decryptedTry = tryDecrypt(tryEncrypted, secretKey);
|
|
101
|
-
if (decryptedTry.success === false) {
|
|
102
|
-
console.error(`Decryption Try failed: ${decryptedTry.error.message} - ${decryptedTry.error.description}`);
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
console.log(`Try Decrypted Data: ${decryptedTry.result}`);
|
|
98
|
+
import { createSecretKey } from 'cipher-kit/node'; // or 'cipher-kit/web-api'
|
|
99
|
+
|
|
100
|
+
const secretKey = createSecretKey('my-passphrase');
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The function accepts an optional `options` as well, which allows you to customize the key derivation process.
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
interface CreateSecretKeyOptions {
|
|
107
|
+
// Which encryption algorithm to use (default: "aes256gcm")
|
|
108
|
+
algorithm?: 'aes256gcm' | 'aes192gcm' | 'aes128gcm';
|
|
109
|
+
|
|
110
|
+
// Digest algorithm for HKDF (key derivation) (default: "sha256")
|
|
111
|
+
digest?: 'sha256' | 'sha384' | 'sha512';
|
|
112
|
+
|
|
113
|
+
// Optional salt for HKDF (key derivation), if you provide a random one it will return a different key each time (default: "cipher-kit-salt", must be >= 8 characters).
|
|
114
|
+
salt?: string;
|
|
115
|
+
|
|
116
|
+
// Optional context info for HKDF (default: "cipher-kit").
|
|
117
|
+
info?: string;
|
|
106
118
|
}
|
|
119
|
+
```
|
|
107
120
|
|
|
108
|
-
|
|
121
|
+
#### Encrypting Data
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
import { encrypt } from 'cipher-kit/node'; // or 'cipher-kit/web-api'
|
|
125
|
+
|
|
126
|
+
const encrypted = encrypt('Hello, World!', secretKey);
|
|
127
|
+
console.log(`Encrypted: ${encrypted}`);
|
|
109
128
|
```
|
|
110
129
|
|
|
111
|
-
|
|
130
|
+
The function accepts an optional `options` parameter to customize the output encoding.
|
|
112
131
|
|
|
113
132
|
```typescript
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
createSecretKey,
|
|
118
|
-
encrypt,
|
|
119
|
-
decrypt,
|
|
120
|
-
encryptObj,
|
|
121
|
-
decryptObj,
|
|
122
|
-
tryEncrypt,
|
|
123
|
-
tryDecrypt,
|
|
124
|
-
} from 'cipher-kit/web-api';
|
|
125
|
-
|
|
126
|
-
const str = 'The brown fox 🦊 jumps over the lazy dog 🐶.';
|
|
127
|
-
|
|
128
|
-
async function webApiExample() {
|
|
129
|
-
console.log(`New UUID: ${generateUuid()}`);
|
|
130
|
-
|
|
131
|
-
console.log(`SHA-256 Hash (ABCDEFG): ${await hash('ABCDEFG')}`);
|
|
132
|
-
|
|
133
|
-
const secretKey = await createSecretKey('my secure passphrase');
|
|
134
|
-
|
|
135
|
-
const encrypted = await encrypt(str, secretKey);
|
|
136
|
-
console.log(`Encrypted Data: ${encrypted}`);
|
|
137
|
-
console.log(`Decrypted Data: ${await decrypt(encrypted, secretKey)}`);
|
|
138
|
-
|
|
139
|
-
const encryptedObj = await encryptObj({ message: 'Hello, World! 🌍', count: 42 }, secretKey);
|
|
140
|
-
console.log(`Encrypted Object: ${encryptedObj}`);
|
|
141
|
-
console.log(`Decrypted Object: ${JSON.stringify(await decryptObj(encryptedObj, secretKey))}`);
|
|
142
|
-
|
|
143
|
-
const { result: tryEncrypted, error: tryEncryptError } = await tryEncrypt(str, secretKey);
|
|
144
|
-
if (tryEncryptError) {
|
|
145
|
-
console.error(`Encryption Try failed: ${tryEncryptError.message} - ${tryEncryptError.description}`);
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
console.log(`Encrypted Try Data: ${tryEncrypted}`);
|
|
149
|
-
|
|
150
|
-
const decryptedTry = await tryDecrypt(tryEncrypted, secretKey);
|
|
151
|
-
if (decryptedTry.success === false) {
|
|
152
|
-
console.error(`Decryption Try failed: ${decryptedTry.error.message} - ${decryptedTry.error.description}`);
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
console.log(`Try Decrypted Data: ${decryptedTry.result}`);
|
|
133
|
+
interface EncryptOptions {
|
|
134
|
+
// Output ciphertext encoding(default: "base64url")
|
|
135
|
+
encoding?: 'base64url' | 'base64' | 'hex';
|
|
156
136
|
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
#### Decrypting Data
|
|
157
140
|
|
|
158
|
-
|
|
141
|
+
```typescript
|
|
142
|
+
import { decrypt } from 'cipher-kit/node'; // or 'cipher-kit/web-api'
|
|
143
|
+
|
|
144
|
+
const decrypted = decrypt(encrypted, secretKey);
|
|
145
|
+
console.log(`Decrypted: ${decrypted}`);
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The function accepts an optional `options` parameter to specify the input encoding.
|
|
149
|
+
|
|
150
|
+
Make sure to use the same encoding that was used during encryption.
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
interface DecryptOptions {
|
|
154
|
+
// Input ciphertext encoding (default: "base64url")
|
|
155
|
+
encoding?: 'base64url' | 'base64' | 'hex';
|
|
156
|
+
}
|
|
159
157
|
```
|
|
160
158
|
|
|
159
|
+
### Hashing
|
|
160
|
+
|
|
161
|
+
Hashing is a one-way process that uses an algorithm to transform data of any size into a fixed-length string of characters, called a hash value or digest. It serves as a digital fingerprint for the data, enabling quick data retrieval in hash tables, password storage, and file integrity checks. Key features include its irreversibility (you can't get the original data back from the hash).
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import { hash } from 'cipher-kit/node'; // or 'cipher-kit/web-api'
|
|
165
|
+
|
|
166
|
+
const hashed = hash('Hello, World!');
|
|
167
|
+
console.log(`Hashed: ${hashed}`);
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The function accepts an optional `options` parameter to customize the hashing process.
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
interface HashOptions {
|
|
174
|
+
// Digest algorithm to use (default: "sha256").
|
|
175
|
+
digest?: 'sha256' | 'sha384' | 'sha512';
|
|
176
|
+
|
|
177
|
+
// Output encoding (default: "base64url").
|
|
178
|
+
encoding?: 'base64url' | 'base64' | 'hex';
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### UUID Generation
|
|
183
|
+
|
|
184
|
+
UUID (Universally Unique Identifier) is a 128-bit identifier used to uniquely identify information in computer systems. It is designed to be globally unique, meaning that no two UUIDs should be the same, even if generated on different systems or at different times. UUIDs are commonly used in databases, distributed systems, and applications where unique identification is crucial.
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
import { generateUUID } from 'cipher-kit/node'; // or 'cipher-kit/web-api'
|
|
188
|
+
|
|
189
|
+
const uuid = generateUUID();
|
|
190
|
+
console.log(`Generated UUID: ${uuid}`);
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Password Hashing and Verification
|
|
194
|
+
|
|
195
|
+
Password hashing is a one-way process that transforms a plaintext password into a fixed-length hash. Password hashing is crucial for securely storing passwords in databases, as it protects user credentials from being exposed in case of a data breach.
|
|
196
|
+
|
|
197
|
+
Password hashing is different from general-purpose hashing because it often involves additional techniques like salting and key stretching to enhance security against brute-force attacks, and it's usually slower to compute to make rainbow table attacks less feasible.
|
|
198
|
+
|
|
199
|
+
To verify a password, the same hashing process is applied to the input password, and the resulting hash is compared to the stored hash, in a time-safe manner to prevent timing attacks.
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
import { hashPassword, verifyPassword } from 'cipher-kit/node'; // or 'cipher-kit/web-api'
|
|
203
|
+
|
|
204
|
+
const password = 'my-secure-password';
|
|
205
|
+
const hashedPassword = hashPassword(password);
|
|
206
|
+
console.log(`Hashed Password: ${hashedPassword}`);
|
|
207
|
+
|
|
208
|
+
const isMatch = verifyPassword(password, hashedPassword);
|
|
209
|
+
console.log(`Password match: ${isMatch}`);
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The `hashPassword` and `verifyPassword` functions accept an optional `options` parameter to customize the hashing process.
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
interface HashPasswordOptions {
|
|
216
|
+
// Digest algorithm to use (default: "sha512").
|
|
217
|
+
digest?: 'sha256' | 'sha384' | 'sha512';
|
|
218
|
+
|
|
219
|
+
// Encoding format for the output hash (default: "base64url").
|
|
220
|
+
encoding?: 'base64url' | 'base64' | 'hex';
|
|
221
|
+
|
|
222
|
+
// Length of the salt in bytes (default: 16 bytes, min: 8 bytes).
|
|
223
|
+
saltLength?: number;
|
|
224
|
+
|
|
225
|
+
// Number of iterations for key derivation (default: 320000, min: 1000).
|
|
226
|
+
iterations?: number;
|
|
227
|
+
|
|
228
|
+
// Length of the derived key in bytes (default: 64 bytes, min: 16 bytes).
|
|
229
|
+
keyLength?: number;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
interface VerifyPasswordOptions {
|
|
233
|
+
// Digest algorithm used during the original hashing (default: `'sha512'`).
|
|
234
|
+
digest?: 'sha256' | 'sha384' | 'sha512';
|
|
235
|
+
|
|
236
|
+
// Encoding format used during the original hashing (default: `'base64url'`).
|
|
237
|
+
encoding?: 'base64url' | 'base64' | 'hex';
|
|
238
|
+
|
|
239
|
+
// Number of iterations used during the original hashing (default: `320000`).
|
|
240
|
+
iterations?: number;
|
|
241
|
+
|
|
242
|
+
// Length of the key used during the original hashing (default: `64`).
|
|
243
|
+
keyLength?: number;
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
<!-- TODO obj <-> string, bytes <-> string, regex -->
|
|
248
|
+
|
|
161
249
|
## Contributions 🤝
|
|
162
250
|
|
|
163
251
|
Want to contribute or suggest a feature?
|
|
@@ -13,7 +13,8 @@ var __export = (target, all) => {
|
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
// src/helpers/consts.ts
|
|
16
|
-
var
|
|
16
|
+
var ENCODING = Object.freeze(["base64", "base64url", "hex", "utf8", "latin1"]);
|
|
17
|
+
var CIPHER_ENCODING = Object.freeze(["base64", "base64url", "hex"]);
|
|
17
18
|
var DIGEST_ALGORITHMS = Object.freeze({
|
|
18
19
|
sha256: { node: "sha256", web: "SHA-256" },
|
|
19
20
|
sha384: { node: "sha384", web: "SHA-384" },
|
|
@@ -59,9 +60,6 @@ function $isSecretKey(x, platform) {
|
|
|
59
60
|
}
|
|
60
61
|
return Object.freeze({ ...x, injected: algorithm });
|
|
61
62
|
}
|
|
62
|
-
function isSecretKey(x, platform) {
|
|
63
|
-
return $isSecretKey(x, platform) !== null;
|
|
64
|
-
}
|
|
65
63
|
var ENCRYPTED_REGEX = Object.freeze({
|
|
66
64
|
node: /^([^.]+)\.([^.]+)\.([^.]+)\.$/,
|
|
67
65
|
web: /^([^.]+)\.([^.]+)\.$/,
|
|
@@ -88,13 +86,17 @@ function $err(err) {
|
|
|
88
86
|
};
|
|
89
87
|
}
|
|
90
88
|
function $fmtError(error) {
|
|
91
|
-
if (error instanceof Error) return error.message;
|
|
92
89
|
if (typeof error === "string") return error;
|
|
90
|
+
if (error instanceof Error) return error.message;
|
|
93
91
|
return String(error);
|
|
94
92
|
}
|
|
95
93
|
function $fmtResultErr(err) {
|
|
94
|
+
if (!err) return "Unknown error";
|
|
96
95
|
return `${err.message} - ${err.description}`;
|
|
97
96
|
}
|
|
97
|
+
function title(platform, title2) {
|
|
98
|
+
return `${platform === "web" ? "Crypto Web API" : "Crypto NodeJS API"} - ${title2}`;
|
|
99
|
+
}
|
|
98
100
|
|
|
99
101
|
// src/helpers/object.ts
|
|
100
102
|
function $stringifyObj(obj) {
|
|
@@ -105,14 +107,14 @@ function $stringifyObj(obj) {
|
|
|
105
107
|
return $err({ msg: "Utility: Stringify error", desc: $fmtError(error) });
|
|
106
108
|
}
|
|
107
109
|
}
|
|
110
|
+
function tryStringifyObj(obj) {
|
|
111
|
+
return $stringifyObj(obj);
|
|
112
|
+
}
|
|
108
113
|
function stringifyObj(obj) {
|
|
109
114
|
const { result, error } = $stringifyObj(obj);
|
|
110
115
|
if (error) throw new Error($fmtResultErr(error));
|
|
111
116
|
return result;
|
|
112
117
|
}
|
|
113
|
-
function tryStringifyObj(obj) {
|
|
114
|
-
return $stringifyObj(obj);
|
|
115
|
-
}
|
|
116
118
|
function $parseToObj(str) {
|
|
117
119
|
try {
|
|
118
120
|
if (!$isStr(str)) return $err({ msg: "Utility: Invalid input", desc: "Input is not a valid string" });
|
|
@@ -140,16 +142,17 @@ exports.$isStr = $isStr;
|
|
|
140
142
|
exports.$ok = $ok;
|
|
141
143
|
exports.$parseToObj = $parseToObj;
|
|
142
144
|
exports.$stringifyObj = $stringifyObj;
|
|
145
|
+
exports.CIPHER_ENCODING = CIPHER_ENCODING;
|
|
143
146
|
exports.DIGEST_ALGORITHMS = DIGEST_ALGORITHMS;
|
|
144
|
-
exports.
|
|
147
|
+
exports.ENCODING = ENCODING;
|
|
145
148
|
exports.ENCRYPTED_REGEX = ENCRYPTED_REGEX;
|
|
146
149
|
exports.ENCRYPTION_ALGORITHMS = ENCRYPTION_ALGORITHMS;
|
|
147
150
|
exports.__export = __export;
|
|
148
|
-
exports.isSecretKey = isSecretKey;
|
|
149
151
|
exports.matchPattern = matchPattern;
|
|
150
152
|
exports.parseToObj = parseToObj;
|
|
151
153
|
exports.stringifyObj = stringifyObj;
|
|
154
|
+
exports.title = title;
|
|
152
155
|
exports.tryParseToObj = tryParseToObj;
|
|
153
156
|
exports.tryStringifyObj = tryStringifyObj;
|
|
154
|
-
//# sourceMappingURL=chunk-
|
|
155
|
-
//# sourceMappingURL=chunk-
|
|
157
|
+
//# sourceMappingURL=chunk-3FJZA77A.cjs.map
|
|
158
|
+
//# sourceMappingURL=chunk-3FJZA77A.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/helpers/consts.ts","../src/helpers/validate.ts","../src/helpers/error.ts","../src/helpers/object.ts"],"names":["nodeCrypto","title"],"mappings":";;;;;;;;;;;;;;;AAAO,IAAM,QAAA,GAAW,OAAO,MAAA,CAAO,CAAC,UAAU,WAAA,EAAa,KAAA,EAAO,MAAA,EAAQ,QAAQ,CAAU;AAExF,IAAM,kBAAkB,MAAA,CAAO,MAAA,CAAO,CAAC,QAAA,EAAU,WAAA,EAAa,KAAK,CAAU;AAE7E,IAAM,iBAAA,GAAoB,OAAO,MAAA,CAAO;AAAA,EAC7C,MAAA,EAAQ,EAAE,IAAA,EAAM,QAAA,EAAU,KAAK,SAAA,EAAU;AAAA,EACzC,MAAA,EAAQ,EAAE,IAAA,EAAM,QAAA,EAAU,KAAK,SAAA,EAAU;AAAA,EACzC,MAAA,EAAQ,EAAE,IAAA,EAAM,QAAA,EAAU,KAAK,SAAA;AACjC,CAAU;AAEH,IAAM,qBAAA,GAAwB,OAAO,MAAA,CAAO;AAAA,EACjD,SAAA,EAAW,EAAE,QAAA,EAAU,EAAA,EAAI,UAAU,EAAA,EAAI,IAAA,EAAM,aAAA,EAAe,GAAA,EAAK,SAAA,EAAU;AAAA,EAC7E,SAAA,EAAW,EAAE,QAAA,EAAU,EAAA,EAAI,UAAU,EAAA,EAAI,IAAA,EAAM,aAAA,EAAe,GAAA,EAAK,SAAA,EAAU;AAAA,EAC7E,SAAA,EAAW,EAAE,QAAA,EAAU,EAAA,EAAI,UAAU,EAAA,EAAI,IAAA,EAAM,aAAA,EAAe,GAAA,EAAK,SAAA;AACrE,CAAU;;;ACVH,SAAS,MAAA,CAAO,CAAA,EAAY,GAAA,GAAM,CAAA,EAAgB;AACvD,EAAA,OAAO,CAAA,KAAM,IAAA,IAAQ,CAAA,KAAM,MAAA,IAAa,OAAO,MAAM,QAAA,IAAY,CAAA,CAAE,IAAA,EAAK,CAAE,MAAA,IAAU,GAAA;AACtF;AAEO,SAAS,OAAO,CAAA,EAA0C;AAC/D,EAAA,IAAI,OAAO,CAAA,KAAM,QAAA,IAAY,MAAM,IAAA,IAAQ,CAAA,KAAM,QAAW,OAAO,KAAA;AACnE,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,cAAA,CAAe,CAAC,CAAA;AACrC,EAAA,OAAO,KAAA,KAAU,MAAA,CAAO,SAAA,IAAa,KAAA,KAAU,IAAA;AACjD;AAEO,SAAS,YAAY,CAAA,EAA0C;AACpE,EAAA,OAAO,OAAO,CAAA,KAAM,QAAA,IAAY,CAAA,KAAM,QAAQ,CAAA,KAAM,MAAA;AACtD;AAMA,IAAM,YAAA,uBAAmB,GAAA,CAAI,CAAC,YAAY,QAAA,EAAU,WAAA,EAAa,KAAK,CAAC,CAAA;AAEhE,SAAS,YAAA,CACd,GACA,QAAA,EACoC;AACpC,EAAA,IAAI,CAAC,WAAA,CAAY,CAAC,CAAA,IAAM,QAAA,KAAa,MAAA,IAAU,QAAA,KAAa,KAAA,IAAU,CAAA,CAAE,QAAA,KAAa,QAAA,EAAU,OAAO,IAAA;AAEtG,EAAA,MAAM,IAAA,GAAO,MAAA,CAAO,IAAA,CAAK,CAAC,CAAA;AAC1B,EAAA,IAAI,IAAA,CAAK,MAAA,KAAW,YAAA,CAAa,IAAA,EAAM,OAAO,IAAA;AAC9C,EAAA,KAAA,MAAW,GAAA,IAAO,MAAM,IAAI,CAAC,aAAa,GAAA,CAAI,GAAG,GAAG,OAAO,IAAA;AAC3D,EAAA,KAAA,MAAW,GAAA,IAAO,cAAc,IAAI,CAAC,OAAO,MAAA,CAAO,CAAA,EAAG,GAAG,CAAA,EAAG,OAAO,IAAA;AAEnE,EAAA,IACE,OAAO,CAAA,CAAE,MAAA,KAAW,QAAA,IACpB,EAAE,EAAE,MAAA,IAAU,iBAAA,CAAA,IACd,OAAO,CAAA,CAAE,SAAA,KAAc,QAAA,IACvB,EAAE,CAAA,CAAE,SAAA,IAAa,qBAAA,CAAA,IACjB,CAAC,WAAA,CAAY,CAAA,CAAE,GAAG,CAAA,IAClB,CAAA,CAAE,GAAA,CAAI,IAAA,KAAS,QAAA,EACf;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,SAAA,GAAY,qBAAA,CAAsB,CAAA,CAAE,SAA+C,CAAA;AAEzF,EAAA,IAAI,aAAa,MAAA,EAAQ;AACvB,IAAA,IACE,EAAE,CAAA,CAAE,GAAA,YAAeA,2BAAA,CAAW,cAC7B,OAAO,CAAA,CAAE,GAAA,CAAI,gBAAA,KAAqB,QAAA,IAAY,CAAA,CAAE,GAAA,CAAI,gBAAA,KAAqB,UAAU,QAAA,EACpF;AACA,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,OAAO,OAAO,MAAA,CAAO,EAAE,GAAG,CAAA,EAAG,QAAA,EAAU,WAAW,CAAA;AAAA,EACpD;AAEA,EAAA,IACE,CAAC,WAAA,CAAY,CAAA,CAAE,GAAA,CAAI,SAAS,KAC5B,CAAA,CAAE,GAAA,CAAI,SAAA,CAAU,IAAA,KAAS,SAAA,CAAU,GAAA,IAClC,OAAO,CAAA,CAAE,GAAA,CAAI,SAAA,CAAU,MAAA,KAAW,QAAA,IAAY,CAAA,CAAE,GAAA,CAAI,SAAA,CAAU,MAAA,KAAW,SAAA,CAAU,QAAA,GAAW,CAAA,IAC/F,OAAO,CAAA,CAAE,IAAI,WAAA,KAAgB,SAAA,IAC7B,CAAC,KAAA,CAAM,OAAA,CAAQ,CAAA,CAAE,IAAI,MAAM,CAAA,IAC3B,CAAA,CAAE,GAAA,CAAI,MAAA,CAAO,MAAA,KAAW,KACxB,EAAE,CAAA,CAAE,GAAA,CAAI,MAAA,CAAO,QAAA,CAAS,SAAS,CAAA,IAAK,CAAA,CAAE,GAAA,CAAI,MAAA,CAAO,QAAA,CAAS,SAAS,CAAA,CAAA,EACrE;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,OAAO,OAAO,MAAA,CAAO,EAAE,GAAG,CAAA,EAAG,QAAA,EAAU,WAAW,CAAA;AACpD;AAeO,IAAM,eAAA,GAAkB,OAAO,MAAA,CAAO;AAAA,EAC3C,IAAA,EAAM,+BAAA;AAAA,EACN,GAAA,EAAK,sBAAA;AAAA,EACL,OAAA,EAAS;AACX,CAAC;AA8BM,SAAS,YAAA,CAAa,MAAc,MAAA,EAA6C;AACtF,EAAA,IAAI,OAAO,IAAA,KAAS,QAAA,EAAU,OAAO,KAAA;AACrC,EAAA,IAAI,EAAE,UAAU,eAAA,CAAA,EAAkB,MAAM,IAAI,KAAA,CAAM,CAAA,gBAAA,EAAmB,MAAM,CAAA,CAAE,CAAA;AAC7E,EAAA,OAAO,eAAA,CAAgB,MAAM,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA;AAC1C;;;AC9EO,SAAS,IAAO,MAAA,EAAuB;AAC5C,EAAA,IAAI,MAAA,CAAO,MAAM,CAAA,EAAG,OAAO,EAAE,OAAA,EAAS,IAAA,EAAM,GAAI,MAAA,EAAsB;AACtE,EAAA,OAAO,EAAE,OAAA,EAAS,IAAA,EAAM,MAAA,EAAO;AACjC;AAIO,SAAS,KAAK,GAAA,EAA0E;AAC7F,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,KAAA;AAAA,IACT,KAAA,EAAO;AAAA,MACL,OAAA,EAAS,KAAA,IAAS,GAAA,GAAM,GAAA,CAAI,MAAM,GAAA,CAAI,OAAA;AAAA,MACtC,WAAA,EAAa,MAAA,IAAU,GAAA,GAAM,GAAA,CAAI,OAAO,GAAA,CAAI;AAAA;AAC9C,GACF;AACF;AAEO,SAAS,UAAU,KAAA,EAAwB;AAChD,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,EAAU,OAAO,KAAA;AACtC,EAAA,IAAI,KAAA,YAAiB,KAAA,EAAO,OAAO,KAAA,CAAM,OAAA;AACzC,EAAA,OAAO,OAAO,KAAK,CAAA;AACrB;AAEO,SAAS,cAAc,GAAA,EAAoC;AAChE,EAAA,IAAI,CAAC,KAAK,OAAO,eAAA;AACjB,EAAA,OAAO,CAAA,EAAG,GAAA,CAAI,OAAO,CAAA,GAAA,EAAM,IAAI,WAAW,CAAA,CAAA;AAC5C;AAEO,SAAS,KAAA,CAAM,UAA0BC,MAAAA,EAAuB;AACrE,EAAA,OAAO,GAAG,QAAA,KAAa,KAAA,GAAQ,gBAAA,GAAmB,mBAAmB,MAAMA,MAAK,CAAA,CAAA;AAClF;;;ACxEO,SAAS,cAA0D,GAAA,EAAwB;AAChG,EAAA,IAAI;AACF,IAAA,IAAI,CAAC,MAAA,CAAO,GAAG,CAAA,EAAG,OAAO,IAAA,CAAK,EAAE,GAAA,EAAK,gBAAA,EAAkB,IAAA,EAAM,6BAAA,EAA+B,CAAA;AAC5F,IAAA,OAAO,GAAA,CAAI,IAAA,CAAK,SAAA,CAAU,GAAG,CAAC,CAAA;AAAA,EAChC,SAAS,KAAA,EAAO;AACd,IAAA,OAAO,IAAA,CAAK,EAAE,GAAA,EAAK,0BAAA,EAA4B,MAAM,SAAA,CAAU,KAAK,GAAG,CAAA;AAAA,EACzE;AACF;AA0BO,SAAS,gBAA4D,GAAA,EAAwB;AAClG,EAAA,OAAO,cAAc,GAAG,CAAA;AAC1B;AA0BO,SAAS,aAAyD,GAAA,EAAgB;AACvF,EAAA,MAAM,EAAE,MAAA,EAAQ,KAAA,EAAM,GAAI,cAAc,GAAG,CAAA;AAC3C,EAAA,IAAI,OAAO,MAAM,IAAI,KAAA,CAAM,aAAA,CAAc,KAAK,CAAC,CAAA;AAC/C,EAAA,OAAO,MAAA;AACT;AAEO,SAAS,YAAwD,GAAA,EAAoC;AAC1G,EAAA,IAAI;AACF,IAAA,IAAI,CAAC,MAAA,CAAO,GAAG,CAAA,EAAG,OAAO,IAAA,CAAK,EAAE,GAAA,EAAK,wBAAA,EAA0B,IAAA,EAAM,6BAAA,EAA+B,CAAA;AACpG,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AAE1B,IAAA,IAAI,CAAC,MAAA,CAAO,GAAG,CAAA,EAAG,OAAO,IAAA,CAAK,EAAE,GAAA,EAAK,gCAAA,EAAkC,IAAA,EAAM,mCAAA,EAAqC,CAAA;AAClH,IAAA,OAAO,GAAA,CAAI,EAAE,MAAA,EAAQ,GAAA,EAAU,CAAA;AAAA,EACjC,SAAS,KAAA,EAAO;AACd,IAAA,OAAO,IAAA,CAAK,EAAE,GAAA,EAAK,yBAAA,EAA2B,MAAM,SAAA,CAAU,KAAK,GAAG,CAAA;AAAA,EACxE;AACF;AAwBO,SAAS,cAA0D,GAAA,EAAoC;AAC5G,EAAA,OAAO,YAAe,GAAG,CAAA;AAC3B;AAwBO,SAAS,WAAuD,GAAA,EAAgB;AACrF,EAAA,MAAM,EAAE,MAAA,EAAQ,KAAA,EAAM,GAAI,YAAe,GAAG,CAAA;AAC5C,EAAA,IAAI,OAAO,MAAM,IAAI,KAAA,CAAM,aAAA,CAAc,KAAK,CAAC,CAAA;AAC/C,EAAA,OAAO,MAAA;AACT","file":"chunk-3FJZA77A.cjs","sourcesContent":["export const ENCODING = Object.freeze(['base64', 'base64url', 'hex', 'utf8', 'latin1'] as const);\r\n\r\nexport const CIPHER_ENCODING = Object.freeze(['base64', 'base64url', 'hex'] as const);\r\n\r\nexport const DIGEST_ALGORITHMS = Object.freeze({\r\n sha256: { node: 'sha256', web: 'SHA-256' },\r\n sha384: { node: 'sha384', web: 'SHA-384' },\r\n sha512: { node: 'sha512', web: 'SHA-512' },\r\n} as const);\r\n\r\nexport const ENCRYPTION_ALGORITHMS = Object.freeze({\r\n aes256gcm: { keyBytes: 32, ivLength: 12, node: 'aes-256-gcm', web: 'AES-GCM' },\r\n aes192gcm: { keyBytes: 24, ivLength: 12, node: 'aes-192-gcm', web: 'AES-GCM' },\r\n aes128gcm: { keyBytes: 16, ivLength: 12, node: 'aes-128-gcm', web: 'AES-GCM' },\r\n} as const);\r\n","import nodeCrypto from 'node:crypto';\r\nimport type { SecretKey } from '~/helpers/types';\r\nimport { DIGEST_ALGORITHMS, ENCRYPTION_ALGORITHMS } from './consts';\r\n\r\nexport function $isStr(x: unknown, min = 1): x is string {\r\n return x !== null && x !== undefined && typeof x === 'string' && x.trim().length >= min;\r\n}\r\n\r\nexport function $isObj(x: unknown): x is Record<string, unknown> {\r\n if (typeof x !== 'object' || x === null || x === undefined) return false;\r\n const proto = Object.getPrototypeOf(x);\r\n return proto === Object.prototype || proto === null;\r\n}\r\n\r\nexport function $isLooseObj(x: unknown): x is Record<string, unknown> {\r\n return typeof x === 'object' && x !== null && x !== undefined;\r\n}\r\n\r\ntype InjectedSecretKey<Platform extends 'web' | 'node'> = SecretKey<Platform> & {\r\n readonly injected: (typeof ENCRYPTION_ALGORITHMS)[keyof typeof ENCRYPTION_ALGORITHMS];\r\n};\r\n\r\nconst expectedKeys = new Set(['platform', 'digest', 'algorithm', 'key']);\r\n\r\nexport function $isSecretKey<Platform extends 'node' | 'web'>(\r\n x: unknown,\r\n platform: Platform,\r\n): InjectedSecretKey<Platform> | null {\r\n if (!$isLooseObj(x) || (platform !== 'node' && platform !== 'web') || x.platform !== platform) return null;\r\n\r\n const keys = Object.keys(x);\r\n if (keys.length !== expectedKeys.size) return null;\r\n for (const key of keys) if (!expectedKeys.has(key)) return null;\r\n for (const key of expectedKeys) if (!Object.hasOwn(x, key)) return null;\r\n\r\n if (\r\n typeof x.digest !== 'string' ||\r\n !(x.digest in DIGEST_ALGORITHMS) ||\r\n typeof x.algorithm !== 'string' ||\r\n !(x.algorithm in ENCRYPTION_ALGORITHMS) ||\r\n !$isLooseObj(x.key) ||\r\n x.key.type !== 'secret'\r\n ) {\r\n return null;\r\n }\r\n\r\n const algorithm = ENCRYPTION_ALGORITHMS[x.algorithm as keyof typeof ENCRYPTION_ALGORITHMS];\r\n\r\n if (platform === 'node') {\r\n if (\r\n !(x.key instanceof nodeCrypto.KeyObject) ||\r\n (typeof x.key.symmetricKeySize === 'number' && x.key.symmetricKeySize !== algorithm.keyBytes)\r\n ) {\r\n return null;\r\n }\r\n return Object.freeze({ ...x, injected: algorithm }) as InjectedSecretKey<Platform>;\r\n }\r\n\r\n if (\r\n !$isLooseObj(x.key.algorithm) ||\r\n x.key.algorithm.name !== algorithm.web ||\r\n (typeof x.key.algorithm.length === 'number' && x.key.algorithm.length !== algorithm.keyBytes * 8) ||\r\n typeof x.key.extractable !== 'boolean' ||\r\n !Array.isArray(x.key.usages) ||\r\n x.key.usages.length !== 2 ||\r\n !(x.key.usages.includes('encrypt') && x.key.usages.includes('decrypt'))\r\n ) {\r\n return null;\r\n }\r\n return Object.freeze({ ...x, injected: algorithm }) as InjectedSecretKey<Platform>;\r\n}\r\n\r\n/**\r\n * Regular expressions for encrypted data patterns.\r\n *\r\n * - **node**: `\"iv.cipher.tag.\"` (three dot-separated parts plus a trailing dot)\r\n * - **web**: `\"iv.cipherWithTag.\"` (two parts plus a trailing dot)\r\n * - **general**: accepts both shapes (2 or 3 parts) with a trailing dot\r\n *\r\n * Each part is any non-empty string without dots.\r\n *\r\n * ### 🍼 Explain Like I'm Five\r\n * You have a secret code you want to check. Before you check it,\r\n * you make sure it looks how a secret code should look.\r\n */\r\nexport const ENCRYPTED_REGEX = Object.freeze({\r\n node: /^([^.]+)\\.([^.]+)\\.([^.]+)\\.$/,\r\n web: /^([^.]+)\\.([^.]+)\\.$/,\r\n general: /^([^.]+)\\.([^.]+)(?:\\.([^.]+))?\\.$/,\r\n});\r\n\r\n/**\r\n * Checks if a string matches an expected encrypted payload shape.\r\n *\r\n * - **node**: `\"iv.cipher.tag.\"` (three dot-separated parts plus a trailing dot)\r\n * - **web**: `\"iv.cipherWithTag.\"` (two parts plus a trailing dot)\r\n * - **general**: accepts both shapes (2 or 3 parts) with a trailing dot\r\n *\r\n * Each part is any non-empty string without dots.\r\n *\r\n * This validates only the **shape**, not whether content is valid base64/hex, etc.\r\n *\r\n * ### 🍼 Explain Like I'm Five\r\n * You have a secret code you want to check. Before you check it,\r\n * you make sure it looks how a secret code should look.\r\n *\r\n * @param data - The string to test.\r\n * @param format - Which layout to check: `'node'`, `'web'`, or `'general'`.\r\n * @returns `true` if the string matches the pattern; otherwise `false`.\r\n * @throws {Error} If an unknown `format` is provided.\r\n *\r\n * @example\r\n * ```ts\r\n * matchPattern(\"abc.def.ghi.\", \"node\"); // true\r\n * matchPattern(\"abc.def.\", \"web\"); // true\r\n * matchPattern(\"abc.def.\", \"node\"); // false\r\n * matchPattern(\"abc.def.ghi.\", \"general\"); // true\r\n * ```\r\n */\r\nexport function matchPattern(data: string, format: 'general' | 'node' | 'web'): boolean {\r\n if (typeof data !== 'string') return false;\r\n if (!(format in ENCRYPTED_REGEX)) throw new Error(`Unknown format: ${format}`);\r\n return ENCRYPTED_REGEX[format].test(data);\r\n}\r\n","import { $isObj } from './validate';\r\n\r\n/**\r\n * Standardized error object for the `Result` type.\r\n * Always has a brief `message` and a more detailed `description`.\r\n */\r\nexport interface ResultErr {\r\n readonly message: string;\r\n readonly description: string;\r\n}\r\n\r\n/**\r\n * Discriminated union for functions that can succeed or fail.\r\n *\r\n * - On **success**:\r\n * - If `T` is an object - properties of `T` are **spread** into the result\r\n * along with `success: true`.\r\n * - Otherwise - `{ success: true, result: T }`.\r\n * - On **failure**: `{ success: false, error: E }`.\r\n *\r\n * @example\r\n * ```ts\r\n * // Primitive result\r\n * function getNum(): Result<number> {\r\n * return $ok(42);\r\n * }\r\n * const r1 = getNum();\r\n * if (r1.success) console.log(r1.result); // 42\r\n *\r\n * // Object result (spread)\r\n * function getObject(): Result<{ name: string; age: number }> {\r\n * return $ok({ name: 'Alice', age: 30 });\r\n * }\r\n * const r2 = getObject();\r\n * if (r2.success) console.log(r2.name, r2.age); // 'Alice' 30\r\n * ```\r\n */\r\nexport type Result<T, E = ResultErr> = T extends object\r\n ?\r\n | ({ readonly [K in keyof T]: T[K] } & { readonly success: true; readonly error?: undefined })\r\n | ({ readonly [K in keyof T]?: undefined } & { readonly success: false; readonly error: E })\r\n :\r\n | { readonly success: true; readonly result: T; readonly error?: undefined }\r\n | { readonly success: false; readonly error: E; readonly result?: undefined };\r\n\r\nexport function $ok<T>(result?: T): Result<T> {\r\n if ($isObj(result)) return { success: true, ...(result as T & object) } as Result<T>;\r\n return { success: true, result } as Result<T>;\r\n}\r\n\r\nexport function $err(err: { msg: string; desc: string }): Result<never, ResultErr>;\r\nexport function $err(err: ResultErr): Result<never, ResultErr>;\r\nexport function $err(err: { msg: string; desc: string } | ResultErr): Result<never, ResultErr> {\r\n return {\r\n success: false,\r\n error: {\r\n message: 'msg' in err ? err.msg : err.message,\r\n description: 'desc' in err ? err.desc : err.description,\r\n },\r\n } as Result<never, ResultErr>;\r\n}\r\n\r\nexport function $fmtError(error: unknown): string {\r\n if (typeof error === 'string') return error;\r\n if (error instanceof Error) return error.message;\r\n return String(error);\r\n}\r\n\r\nexport function $fmtResultErr(err: ResultErr | undefined): string {\r\n if (!err) return 'Unknown error';\r\n return `${err.message} - ${err.description}`;\r\n}\r\n\r\nexport function title(platform: 'web' | 'node', title: string): string {\r\n return `${platform === 'web' ? 'Crypto Web API' : 'Crypto NodeJS API'} - ${title}`;\r\n}\r\n","import { $err, $fmtError, $fmtResultErr, $ok, type Result } from './error';\r\nimport { $isObj, $isStr } from './validate';\r\n\r\nexport function $stringifyObj<T extends object = Record<string, unknown>>(obj: T): Result<string> {\r\n try {\r\n if (!$isObj(obj)) return $err({ msg: 'Invalid object', desc: 'Input is not a plain object' });\r\n return $ok(JSON.stringify(obj));\r\n } catch (error) {\r\n return $err({ msg: 'Utility: Stringify error', desc: $fmtError(error) });\r\n }\r\n}\r\n\r\n/**\r\n * Safely serializes a plain object to JSON without throwing.\r\n *\r\n * Wraps `JSON.stringify` and returns a `Result` containing the JSON string or an error.\r\n * Only plain objects (POJOs) are accepted. Class instances, Maps, Sets, etc. are rejected.\r\n *\r\n * ### 🍼 Explain Like I'm Five\r\n * You have a box of toys (your object) and take a photo of it (a JSON string)\r\n * so you can send it to a friend.\r\n *\r\n * @template T - Plain object type to serialize.\r\n * @param obj - The object to stringify (must be a plain object).\r\n * @returns A `Result` with the JSON string on success, or an error.\r\n *\r\n * @example\r\n * ```ts\r\n * const res = tryStringifyObj({ a: 1 });\r\n * if (res.success) {\r\n * console.log(res.result); // {\"a\":1}\r\n * } else {\r\n * console.error(res.error.message, res.error.description);\r\n * }\r\n * ```\r\n */\r\nexport function tryStringifyObj<T extends object = Record<string, unknown>>(obj: T): Result<string> {\r\n return $stringifyObj(obj);\r\n}\r\n\r\n/**\r\n * Serializes a plain object to JSON (throwing).\r\n *\r\n * Wraps `JSON.stringify` and returns the result or throws an error.\r\n * Only plain objects (POJOs) are accepted. Class instances, Maps, Sets, etc. are rejected.\r\n *\r\n * ### 🍼 Explain Like I'm Five\r\n * You have a box of toys (your object) and take a photo of it (a JSON string)\r\n * so you can send it to a friend.\r\n *\r\n * @template T - Plain object type to serialize.\r\n * @param obj - The object to stringify (must be a plain object).\r\n * @returns JSON string representation of the object.\r\n * @throws {Error} If `obj` is not a plain object or serialization fails.\r\n *\r\n * @example\r\n * ```ts\r\n * try {\r\n * const json = stringifyObj({ a: 1 }); // {\"a\":1}\r\n * } catch (error: unknown) {\r\n * console.error(error);\r\n * }\r\n * ```\r\n */\r\nexport function stringifyObj<T extends object = Record<string, unknown>>(obj: T): string {\r\n const { result, error } = $stringifyObj(obj);\r\n if (error) throw new Error($fmtResultErr(error));\r\n return result;\r\n}\r\n\r\nexport function $parseToObj<T extends object = Record<string, unknown>>(str: string): Result<{ result: T }> {\r\n try {\r\n if (!$isStr(str)) return $err({ msg: 'Utility: Invalid input', desc: 'Input is not a valid string' });\r\n const obj = JSON.parse(str);\r\n\r\n if (!$isObj(obj)) return $err({ msg: 'Utility: Invalid object format', desc: 'Parsed data is not a plain object' });\r\n return $ok({ result: obj as T });\r\n } catch (error) {\r\n return $err({ msg: 'Utility: Invalid format', desc: $fmtError(error) });\r\n }\r\n}\r\n\r\n/**\r\n * Safely parses a JSON string to a plain object (non-throwing).\r\n *\r\n * Wraps `JSON.parse` and returns a `Result` containing the parsed object, or an error.\r\n *\r\n * ### 🍼 Explain Like I'm Five\r\n * You rebuild your toy box (an object) from a photo you took (a JSON string).\r\n *\r\n * @template T - The expected object type.\r\n * @param str - The JSON string to parse.\r\n * @returns A `Result` with the parsed object on success, or an error.\r\n *\r\n * @example\r\n * ```ts\r\n * const res = tryParseToObj<{ a: number }>('{\"a\":1}');\r\n * if (res.success) {\r\n * console.log(res.result.a); // 1\r\n * } else {\r\n * console.error(res.error.message, res.error.description);\r\n * }\r\n * ```\r\n */\r\nexport function tryParseToObj<T extends object = Record<string, unknown>>(str: string): Result<{ result: T }> {\r\n return $parseToObj<T>(str);\r\n}\r\n\r\n/**\r\n * Parses a JSON string to a plain object (throwing).\r\n *\r\n * Wraps `JSON.parse` and returns the parsed object, or throws on failure.\r\n *\r\n * ### 🍼 Explain Like I'm Five\r\n * You rebuild your toy box (an object) from a photo you took (a JSON string).\r\n *\r\n * @template T - The expected object type.\r\n * @param str - The JSON string to parse.\r\n * @returns The parsed plain object.\r\n * @throws {Error} If the string can’t be parsed or doesn’t represent a plain object.\r\n *\r\n * @example\r\n * ```ts\r\n * try {\r\n * const obj = parseToObj<{ a: number }>('{\"a\":1}'); // obj.a === 1\r\n * } catch (error: unknown) {\r\n * console.error(error);\r\n * }\r\n * ```\r\n */\r\nexport function parseToObj<T extends object = Record<string, unknown>>(str: string): T {\r\n const { result, error } = $parseToObj<T>(str);\r\n if (error) throw new Error($fmtResultErr(error));\r\n return result;\r\n}\r\n"]}
|