@kumix/utils 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/README.md +275 -0
- package/dist/index.d.ts +5162 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10763 -0
- package/dist/index.js.map +1 -0
- package/dist/logger-BOB35dQN.js +231 -0
- package/dist/logger-BOB35dQN.js.map +1 -0
- package/dist/server.d.ts +446 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +555 -0
- package/dist/server.js.map +1 -0
- package/package.json +73 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
import { t as logger } from "./logger-BOB35dQN.js";
|
|
2
|
+
import { randomBytes, randomInt } from "node:crypto";
|
|
3
|
+
import jwt from "jsonwebtoken";
|
|
4
|
+
import bcrypt from "bcryptjs";
|
|
5
|
+
import * as chrono from "chrono-node";
|
|
6
|
+
//#region src/functions/crypto/generate-random-string.ts
|
|
7
|
+
/**
|
|
8
|
+
* Utility function for generating cryptographically secure random strings
|
|
9
|
+
* Provides secure random string generation for tokens, IDs, and other security-sensitive uses
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Generates a cryptographically secure random string of specified length
|
|
13
|
+
* Uses Node.js crypto module for secure random number generation
|
|
14
|
+
*
|
|
15
|
+
* @param length - The length of the random string to generate
|
|
16
|
+
* @returns A random string consisting of uppercase letters and numbers
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { generateRandomString } from '@/utils/functions';
|
|
21
|
+
*
|
|
22
|
+
* // Generate a 6-character random string
|
|
23
|
+
* const verificationCode = generateRandomString(6);
|
|
24
|
+
* // Example output: "A7B3C9"
|
|
25
|
+
*
|
|
26
|
+
* // Generate a longer random string for tokens
|
|
27
|
+
* const apiToken = generateRandomString(32);
|
|
28
|
+
* // Example output: "X7F9A2B5C8D1E4F7G0H3I6J9K2L5M8N1"
|
|
29
|
+
*
|
|
30
|
+
* // Use as unique identifiers
|
|
31
|
+
* const uniqueId = generateRandomString(12);
|
|
32
|
+
* // Example output: "B7C9D1E3F5G7"
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
function generateRandomString(length) {
|
|
36
|
+
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
37
|
+
const randomBytesArray = randomBytes(length);
|
|
38
|
+
let result = "";
|
|
39
|
+
for (let i = 0; i < length; i++) {
|
|
40
|
+
const randomIndex = randomBytesArray[i] % 36;
|
|
41
|
+
result += charset[randomIndex];
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/functions/crypto/jwt.ts
|
|
47
|
+
/**
|
|
48
|
+
* JWT (JSON Web Token) utilities for SaaS applications
|
|
49
|
+
* Provides secure token generation, verification, and management
|
|
50
|
+
* Includes validation and error handling for production use
|
|
51
|
+
*/
|
|
52
|
+
/**
|
|
53
|
+
* Generates a JWT token with the provided payload
|
|
54
|
+
*
|
|
55
|
+
* @param payload - The data to encode in the token
|
|
56
|
+
* @param secret - The secret key for signing the token
|
|
57
|
+
* @param options - Token generation options
|
|
58
|
+
* @returns The generated JWT token or null if failed
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* import { generateJWT } from '@kumix/utils';
|
|
63
|
+
*
|
|
64
|
+
* // Basic usage
|
|
65
|
+
* const token = generateJWT(
|
|
66
|
+
* { userId: '123', email: 'user@example.com' },
|
|
67
|
+
* process.env.JWT_SECRET
|
|
68
|
+
* );
|
|
69
|
+
*
|
|
70
|
+
* // With custom expiration
|
|
71
|
+
* const token = generateJWT(
|
|
72
|
+
* { userId: '123', email: 'user@example.com', role: 'admin' },
|
|
73
|
+
* process.env.JWT_SECRET,
|
|
74
|
+
* { expiresIn: '1h' }
|
|
75
|
+
* );
|
|
76
|
+
*
|
|
77
|
+
* // For API authentication
|
|
78
|
+
* const authToken = generateJWT(
|
|
79
|
+
* { userId: user.id, email: user.email, workspaceId: workspace.id },
|
|
80
|
+
* process.env.JWT_SECRET,
|
|
81
|
+
* { expiresIn: '24h', issuer: 'myapp.com' }
|
|
82
|
+
* );
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
function generateJWT(payload, secret, options = {}) {
|
|
86
|
+
if (!payload || typeof payload !== "object") {
|
|
87
|
+
logger.warn("JWT generation: Payload must be a valid object");
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
if (!payload.userId || typeof payload.userId !== "string") {
|
|
91
|
+
logger.warn("JWT generation: userId is required in payload");
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
if (!payload.email || typeof payload.email !== "string") {
|
|
95
|
+
logger.warn("JWT generation: email is required in payload");
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
if (!secret || typeof secret !== "string") {
|
|
99
|
+
logger.warn("JWT generation: Secret must be a non-empty string");
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
const { expiresIn = "7d", issuer, audience } = options;
|
|
103
|
+
const { exp, ...cleanPayload } = payload;
|
|
104
|
+
try {
|
|
105
|
+
const signOptions = {
|
|
106
|
+
...typeof exp === "undefined" ? { expiresIn } : {},
|
|
107
|
+
...issuer && { issuer },
|
|
108
|
+
...audience && { audience }
|
|
109
|
+
};
|
|
110
|
+
return jwt.sign(cleanPayload, secret, signOptions);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
logger.error("JWT generation: Failed to generate token", { error });
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Verifies and decodes a JWT token
|
|
118
|
+
*
|
|
119
|
+
* @param token - The JWT token to verify
|
|
120
|
+
* @param secret - The secret key used to sign the token
|
|
121
|
+
* @param options - Verification options
|
|
122
|
+
* @returns Verification result with payload if valid
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```typescript
|
|
126
|
+
* import { verifyJWT } from '@kumix/utils';
|
|
127
|
+
*
|
|
128
|
+
* // Basic usage
|
|
129
|
+
* const result = verifyJWT(token, process.env.JWT_SECRET);
|
|
130
|
+
* if (result.isValid && result.payload) {
|
|
131
|
+
* const { userId, email } = result.payload;
|
|
132
|
+
* // Proceed with authenticated user
|
|
133
|
+
* }
|
|
134
|
+
*
|
|
135
|
+
* // With issuer verification
|
|
136
|
+
* const result = verifyJWT(token, process.env.JWT_SECRET, {
|
|
137
|
+
* issuer: 'myapp.com'
|
|
138
|
+
* });
|
|
139
|
+
*
|
|
140
|
+
* // In authentication middleware
|
|
141
|
+
* async function authenticateRequest(req, res, next) {
|
|
142
|
+
* const token = req.headers.authorization?.replace('Bearer ', '');
|
|
143
|
+
* const result = verifyJWT(token, process.env.JWT_SECRET);
|
|
144
|
+
*
|
|
145
|
+
* if (!result.isValid) {
|
|
146
|
+
* return res.status(401).json({ error: 'Invalid token' });
|
|
147
|
+
* }
|
|
148
|
+
*
|
|
149
|
+
* req.user = result.payload;
|
|
150
|
+
* next();
|
|
151
|
+
* }
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
function verifyJWT(token, secret, options = {}) {
|
|
155
|
+
if (!token || typeof token !== "string") return {
|
|
156
|
+
isValid: false,
|
|
157
|
+
error: "Token must be a non-empty string"
|
|
158
|
+
};
|
|
159
|
+
if (!secret || typeof secret !== "string") return {
|
|
160
|
+
isValid: false,
|
|
161
|
+
error: "Secret must be a non-empty string"
|
|
162
|
+
};
|
|
163
|
+
const { issuer, audience } = options;
|
|
164
|
+
try {
|
|
165
|
+
return {
|
|
166
|
+
isValid: true,
|
|
167
|
+
payload: jwt.verify(token, secret, {
|
|
168
|
+
...issuer && { issuer },
|
|
169
|
+
...audience && { audience }
|
|
170
|
+
})
|
|
171
|
+
};
|
|
172
|
+
} catch (error) {
|
|
173
|
+
logger.warn("JWT verification: Token verification failed", { error });
|
|
174
|
+
if (error instanceof jwt.TokenExpiredError) return {
|
|
175
|
+
isValid: false,
|
|
176
|
+
error: "Token has expired"
|
|
177
|
+
};
|
|
178
|
+
if (error instanceof jwt.JsonWebTokenError) return {
|
|
179
|
+
isValid: false,
|
|
180
|
+
error: "Invalid token format"
|
|
181
|
+
};
|
|
182
|
+
return {
|
|
183
|
+
isValid: false,
|
|
184
|
+
error: "Token verification failed"
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Decodes a JWT token without verification (for debugging/inspection)
|
|
190
|
+
*
|
|
191
|
+
* @param token - The JWT token to decode
|
|
192
|
+
* @returns Decoded payload or null if failed
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```typescript
|
|
196
|
+
* import { decodeJWT } from '@kumix/utils';
|
|
197
|
+
*
|
|
198
|
+
* // Inspect token contents without verification
|
|
199
|
+
* const payload = decodeJWT(token);
|
|
200
|
+
* if (payload) {
|
|
201
|
+
* console.log('Token expires at:', new Date(payload.exp * 1000));
|
|
202
|
+
* console.log('User ID:', payload.userId);
|
|
203
|
+
* }
|
|
204
|
+
*
|
|
205
|
+
* // For debugging purposes
|
|
206
|
+
* function debugToken(token: string) {
|
|
207
|
+
* const payload = decodeJWT(token);
|
|
208
|
+
* if (payload) {
|
|
209
|
+
* console.log('Token payload:', payload);
|
|
210
|
+
* } else {
|
|
211
|
+
* console.log('Invalid token format');
|
|
212
|
+
* }
|
|
213
|
+
* }
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
function decodeJWT(token) {
|
|
217
|
+
if (!token || typeof token !== "string") return null;
|
|
218
|
+
try {
|
|
219
|
+
return jwt.decode(token);
|
|
220
|
+
} catch (error) {
|
|
221
|
+
logger.warn("JWT decode: Failed to decode token", { error });
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Checks if a JWT token is expired
|
|
227
|
+
*
|
|
228
|
+
* @param token - The JWT token to check
|
|
229
|
+
* @returns True if token is expired, false otherwise
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
* ```typescript
|
|
233
|
+
* import { isJWTExpired } from '@kumix/utils';
|
|
234
|
+
*
|
|
235
|
+
* // Check if token needs refresh
|
|
236
|
+
* if (isJWTExpired(userToken)) {
|
|
237
|
+
* // Redirect to login or refresh token
|
|
238
|
+
* await refreshUserToken();
|
|
239
|
+
* }
|
|
240
|
+
*
|
|
241
|
+
* // In token refresh logic
|
|
242
|
+
* function shouldRefreshToken(token: string): boolean {
|
|
243
|
+
* return isJWTExpired(token);
|
|
244
|
+
* }
|
|
245
|
+
* ```
|
|
246
|
+
*/
|
|
247
|
+
function isJWTExpired(token) {
|
|
248
|
+
const payload = decodeJWT(token);
|
|
249
|
+
if (!payload?.exp) return true;
|
|
250
|
+
const currentTime = Math.floor(Date.now() / 1e3);
|
|
251
|
+
return payload.exp < currentTime;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Gets the remaining time until token expiration
|
|
255
|
+
*
|
|
256
|
+
* @param token - The JWT token to check
|
|
257
|
+
* @returns Remaining time in seconds, or 0 if expired/invalid
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* ```typescript
|
|
261
|
+
* import { getJWTTimeRemaining } from '@kumix/utils';
|
|
262
|
+
*
|
|
263
|
+
* // Check how much time is left
|
|
264
|
+
* const timeLeft = getJWTTimeRemaining(userToken);
|
|
265
|
+
* if (timeLeft < 300) { // Less than 5 minutes
|
|
266
|
+
* // Show warning about token expiration
|
|
267
|
+
* showTokenExpirationWarning();
|
|
268
|
+
* }
|
|
269
|
+
*
|
|
270
|
+
* // Format time remaining for display
|
|
271
|
+
* function formatTimeRemaining(token: string): string {
|
|
272
|
+
* const seconds = getJWTTimeRemaining(token);
|
|
273
|
+
* const minutes = Math.floor(seconds / 60);
|
|
274
|
+
* const hours = Math.floor(minutes / 60);
|
|
275
|
+
*
|
|
276
|
+
* if (hours > 0) return `${hours}h ${minutes % 60}m`;
|
|
277
|
+
* if (minutes > 0) return `${minutes}m`;
|
|
278
|
+
* return `${seconds}s`;
|
|
279
|
+
* }
|
|
280
|
+
* ```
|
|
281
|
+
*/
|
|
282
|
+
function getJWTTimeRemaining(token) {
|
|
283
|
+
const payload = decodeJWT(token);
|
|
284
|
+
if (!payload?.exp) return 0;
|
|
285
|
+
const currentTime = Math.floor(Date.now() / 1e3);
|
|
286
|
+
const timeRemaining = payload.exp - currentTime;
|
|
287
|
+
return Math.max(0, timeRemaining);
|
|
288
|
+
}
|
|
289
|
+
//#endregion
|
|
290
|
+
//#region src/functions/crypto/password.ts
|
|
291
|
+
/**
|
|
292
|
+
* Password hashing and verification utilities for SaaS applications
|
|
293
|
+
* Provides secure password handling using bcrypt with proper salt rounds
|
|
294
|
+
* Includes validation and error handling for production use
|
|
295
|
+
*/
|
|
296
|
+
/**
|
|
297
|
+
* Default salt rounds for bcrypt hashing
|
|
298
|
+
* 12 rounds provides good security while maintaining reasonable performance
|
|
299
|
+
* Higher values increase security but also increase computation time
|
|
300
|
+
*/
|
|
301
|
+
const DEFAULT_SALT_ROUNDS = 12;
|
|
302
|
+
/**
|
|
303
|
+
* Hashes a password using bcrypt with salt
|
|
304
|
+
*
|
|
305
|
+
* @param password - The plain text password to hash
|
|
306
|
+
* @param options - Hashing options
|
|
307
|
+
* @returns Promise that resolves to the hashed password or null if failed
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* ```typescript
|
|
311
|
+
* import { hashPassword } from '@kumix/utils';
|
|
312
|
+
*
|
|
313
|
+
* // Basic usage
|
|
314
|
+
* const hashedPassword = await hashPassword('userPassword123');
|
|
315
|
+
* if (hashedPassword) {
|
|
316
|
+
* // Store hashedPassword in database
|
|
317
|
+
* await saveUser({ email, password: hashedPassword });
|
|
318
|
+
* }
|
|
319
|
+
*
|
|
320
|
+
* // With custom salt rounds
|
|
321
|
+
* const hashedPassword = await hashPassword('userPassword123', {
|
|
322
|
+
* saltRounds: 14
|
|
323
|
+
* });
|
|
324
|
+
*
|
|
325
|
+
* // Error handling
|
|
326
|
+
* const hashedPassword = await hashPassword('userPassword123');
|
|
327
|
+
* if (!hashedPassword) {
|
|
328
|
+
* throw new Error('Failed to hash password');
|
|
329
|
+
* }
|
|
330
|
+
* ```
|
|
331
|
+
*/
|
|
332
|
+
async function hashPassword(password, options = {}) {
|
|
333
|
+
if (!password || typeof password !== "string") {
|
|
334
|
+
logger.warn("Password hashing: Password must be a non-empty string");
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
if (password.length < 1) {
|
|
338
|
+
logger.warn("Password hashing: Password cannot be empty");
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
const { saltRounds = DEFAULT_SALT_ROUNDS } = options;
|
|
342
|
+
if (typeof saltRounds !== "number" || saltRounds < 4 || saltRounds > 20) {
|
|
343
|
+
logger.warn("Password hashing: Salt rounds must be between 4 and 20");
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
try {
|
|
347
|
+
const salt = await bcrypt.genSalt(saltRounds);
|
|
348
|
+
return await bcrypt.hash(password, salt);
|
|
349
|
+
} catch (error) {
|
|
350
|
+
logger.error("Password hashing: Failed to hash password", { error });
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Verifies a password against its hash
|
|
356
|
+
*
|
|
357
|
+
* @param password - The plain text password to verify
|
|
358
|
+
* @param hashedPassword - The hashed password to compare against
|
|
359
|
+
* @returns Promise that resolves to verification result
|
|
360
|
+
*
|
|
361
|
+
* @example
|
|
362
|
+
* ```typescript
|
|
363
|
+
* import { verifyPassword } from '@kumix/utils';
|
|
364
|
+
*
|
|
365
|
+
* // Basic usage
|
|
366
|
+
* const result = await verifyPassword('userPassword123', storedHashedPassword);
|
|
367
|
+
* if (result.isValid) {
|
|
368
|
+
* // Password is correct, proceed with login
|
|
369
|
+
* await loginUser(user);
|
|
370
|
+
* } else {
|
|
371
|
+
* // Password is incorrect
|
|
372
|
+
* throw new Error('Invalid credentials');
|
|
373
|
+
* }
|
|
374
|
+
*
|
|
375
|
+
* // With error handling
|
|
376
|
+
* const result = await verifyPassword('userPassword123', storedHashedPassword);
|
|
377
|
+
* if (result.error) {
|
|
378
|
+
* logger.error('Password verification failed:', result.error);
|
|
379
|
+
* throw new Error('Authentication error');
|
|
380
|
+
* }
|
|
381
|
+
*
|
|
382
|
+
* // In authentication middleware
|
|
383
|
+
* async function authenticateUser(email: string, password: string) {
|
|
384
|
+
* const user = await getUserByEmail(email);
|
|
385
|
+
* if (!user) {
|
|
386
|
+
* return { success: false, error: 'User not found' };
|
|
387
|
+
* }
|
|
388
|
+
*
|
|
389
|
+
* const result = await verifyPassword(password, user.hashedPassword);
|
|
390
|
+
* if (!result.isValid) {
|
|
391
|
+
* return { success: false, error: 'Invalid password' };
|
|
392
|
+
* }
|
|
393
|
+
*
|
|
394
|
+
* return { success: true, user };
|
|
395
|
+
* }
|
|
396
|
+
* ```
|
|
397
|
+
*/
|
|
398
|
+
async function verifyPassword(password, hashedPassword) {
|
|
399
|
+
if (!password || typeof password !== "string") return {
|
|
400
|
+
isValid: false,
|
|
401
|
+
error: "Password must be a non-empty string"
|
|
402
|
+
};
|
|
403
|
+
if (!hashedPassword || typeof hashedPassword !== "string") return {
|
|
404
|
+
isValid: false,
|
|
405
|
+
error: "Hashed password must be a non-empty string"
|
|
406
|
+
};
|
|
407
|
+
if (!hashedPassword.startsWith("$2a$") && !hashedPassword.startsWith("$2b$") && !hashedPassword.startsWith("$2y$")) return {
|
|
408
|
+
isValid: false,
|
|
409
|
+
error: "Invalid hash format"
|
|
410
|
+
};
|
|
411
|
+
try {
|
|
412
|
+
return { isValid: await bcrypt.compare(password, hashedPassword) };
|
|
413
|
+
} catch (error) {
|
|
414
|
+
logger.error("Password verification: Failed to verify password", { error });
|
|
415
|
+
return {
|
|
416
|
+
isValid: false,
|
|
417
|
+
error: "Password verification failed"
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Checks if a password needs to be rehashed (e.g., due to updated salt rounds)
|
|
423
|
+
*
|
|
424
|
+
* @param hashedPassword - The current hashed password
|
|
425
|
+
* @param targetSaltRounds - The target salt rounds (default: 12)
|
|
426
|
+
* @returns Whether the password needs to be rehashed
|
|
427
|
+
*
|
|
428
|
+
* @example
|
|
429
|
+
* ```typescript
|
|
430
|
+
* import { needsRehash, hashPassword } from '@kumix/utils';
|
|
431
|
+
*
|
|
432
|
+
* // Check if password needs rehashing during login
|
|
433
|
+
* async function loginUser(email: string, password: string) {
|
|
434
|
+
* const user = await getUserByEmail(email);
|
|
435
|
+
* const result = await verifyPassword(password, user.hashedPassword);
|
|
436
|
+
*
|
|
437
|
+
* if (result.isValid) {
|
|
438
|
+
* // Check if we need to rehash with updated salt rounds
|
|
439
|
+
* if (needsRehash(user.hashedPassword, 14)) {
|
|
440
|
+
* const newHash = await hashPassword(password, { saltRounds: 14 });
|
|
441
|
+
* if (newHash) {
|
|
442
|
+
* await updateUserPassword(user.id, newHash);
|
|
443
|
+
* }
|
|
444
|
+
* }
|
|
445
|
+
*
|
|
446
|
+
* return { success: true, user };
|
|
447
|
+
* }
|
|
448
|
+
*
|
|
449
|
+
* return { success: false, error: 'Invalid credentials' };
|
|
450
|
+
* }
|
|
451
|
+
* ```
|
|
452
|
+
*/
|
|
453
|
+
function needsRehash(hashedPassword, targetSaltRounds = DEFAULT_SALT_ROUNDS) {
|
|
454
|
+
if (!hashedPassword || typeof hashedPassword !== "string") return true;
|
|
455
|
+
try {
|
|
456
|
+
const parts = hashedPassword.split("$");
|
|
457
|
+
if (parts.length < 4) return true;
|
|
458
|
+
return parseInt(parts[2], 10) !== targetSaltRounds;
|
|
459
|
+
} catch (error) {
|
|
460
|
+
logger.warn("Password rehash check: Failed to parse hash", { error });
|
|
461
|
+
return true;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Generates a secure random password
|
|
466
|
+
*
|
|
467
|
+
* @param length - Length of the password (default: 16, min: 8, max: 128)
|
|
468
|
+
* @param options - Password generation options
|
|
469
|
+
* @returns Generated password string
|
|
470
|
+
*
|
|
471
|
+
* @example
|
|
472
|
+
* ```typescript
|
|
473
|
+
* import { generateSecurePassword } from '@kumix/utils';
|
|
474
|
+
*
|
|
475
|
+
* // Generate default password (16 characters)
|
|
476
|
+
* const password = generateSecurePassword();
|
|
477
|
+
*
|
|
478
|
+
* // Generate longer password
|
|
479
|
+
* const longPassword = generateSecurePassword(24);
|
|
480
|
+
*
|
|
481
|
+
* // Generate password without symbols
|
|
482
|
+
* const simplePassword = generateSecurePassword(12, {
|
|
483
|
+
* includeSymbols: false
|
|
484
|
+
* });
|
|
485
|
+
*
|
|
486
|
+
* // For temporary passwords
|
|
487
|
+
* const tempPassword = generateSecurePassword(12);
|
|
488
|
+
* await sendPasswordResetEmail(user.email, tempPassword);
|
|
489
|
+
* ```
|
|
490
|
+
*/
|
|
491
|
+
function generateSecurePassword(length = 16, options = {}) {
|
|
492
|
+
const { includeUppercase = true, includeLowercase = true, includeNumbers = true, includeSymbols = true } = options;
|
|
493
|
+
const validLength = Math.max(8, Math.min(128, length));
|
|
494
|
+
let charset = "";
|
|
495
|
+
if (includeUppercase) charset += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
496
|
+
if (includeLowercase) charset += "abcdefghijklmnopqrstuvwxyz";
|
|
497
|
+
if (includeNumbers) charset += "0123456789";
|
|
498
|
+
if (includeSymbols) charset += "!@#$%^&*()_+-=[]{}|;:,.<>?";
|
|
499
|
+
if (charset.length === 0) charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
500
|
+
let password = "";
|
|
501
|
+
for (let i = 0; i < validLength; i++) {
|
|
502
|
+
const randomIndex = randomInt(0, charset.length);
|
|
503
|
+
password += charset[randomIndex];
|
|
504
|
+
}
|
|
505
|
+
return password;
|
|
506
|
+
}
|
|
507
|
+
//#endregion
|
|
508
|
+
//#region src/functions/datetime/parse-datetime.ts
|
|
509
|
+
/**
|
|
510
|
+
* Utility function for parsing date strings into Date objects
|
|
511
|
+
* Provides natural language date parsing capabilities
|
|
512
|
+
*/
|
|
513
|
+
/**
|
|
514
|
+
* Parses a date string into a Date object using natural language processing
|
|
515
|
+
* Passes through Date objects unchanged
|
|
516
|
+
*
|
|
517
|
+
* @param str - The date string to parse or a Date object to pass through
|
|
518
|
+
* @returns A Date object, or null if parsing fails
|
|
519
|
+
*
|
|
520
|
+
* @example
|
|
521
|
+
* ```ts
|
|
522
|
+
* // Parse natural language date strings
|
|
523
|
+
* parseDateTime('tomorrow at 3pm')
|
|
524
|
+
* // Returns a Date object for tomorrow at 3:00 PM
|
|
525
|
+
*
|
|
526
|
+
* // Parse formal date strings
|
|
527
|
+
* parseDateTime('2023-06-15')
|
|
528
|
+
* // Returns a Date object for June 15, 2023
|
|
529
|
+
*
|
|
530
|
+
* // Parse relative dates
|
|
531
|
+
* parseDateTime('next Friday')
|
|
532
|
+
* // Returns a Date object for next Friday
|
|
533
|
+
*
|
|
534
|
+
* // Pass through existing Date objects
|
|
535
|
+
* const date = new Date('2023-06-15')
|
|
536
|
+
* parseDateTime(date)
|
|
537
|
+
* // Returns the same Date object
|
|
538
|
+
*
|
|
539
|
+
* // Parse time strings
|
|
540
|
+
* parseDateTime('3:30pm')
|
|
541
|
+
* // Returns a Date object for today at 3:30 PM
|
|
542
|
+
*
|
|
543
|
+
* // Parse complex date expressions
|
|
544
|
+
* parseDateTime('3 days after next Monday')
|
|
545
|
+
* // Returns the appropriate Date object
|
|
546
|
+
* ```
|
|
547
|
+
*/
|
|
548
|
+
const parseDateTime = (str) => {
|
|
549
|
+
if (str instanceof Date) return str;
|
|
550
|
+
return chrono.parseDate(str);
|
|
551
|
+
};
|
|
552
|
+
//#endregion
|
|
553
|
+
export { decodeJWT, generateJWT, generateRandomString, generateSecurePassword, getJWTTimeRemaining, hashPassword, isJWTExpired, jwt, needsRehash, parseDateTime, verifyJWT, verifyPassword };
|
|
554
|
+
|
|
555
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","names":[],"sources":["../src/functions/crypto/generate-random-string.ts","../src/functions/crypto/jwt.ts","../src/functions/crypto/password.ts","../src/functions/datetime/parse-datetime.ts"],"sourcesContent":["/**\n * Utility function for generating cryptographically secure random strings\n * Provides secure random string generation for tokens, IDs, and other security-sensitive uses\n */\n\nimport { randomBytes } from \"node:crypto\";\n\n/**\n * Generates a cryptographically secure random string of specified length\n * Uses Node.js crypto module for secure random number generation\n *\n * @param length - The length of the random string to generate\n * @returns A random string consisting of uppercase letters and numbers\n *\n * @example\n * ```ts\n * import { generateRandomString } from '@/utils/functions';\n *\n * // Generate a 6-character random string\n * const verificationCode = generateRandomString(6);\n * // Example output: \"A7B3C9\"\n *\n * // Generate a longer random string for tokens\n * const apiToken = generateRandomString(32);\n * // Example output: \"X7F9A2B5C8D1E4F7G0H3I6J9K2L5M8N1\"\n *\n * // Use as unique identifiers\n * const uniqueId = generateRandomString(12);\n * // Example output: \"B7C9D1E3F5G7\"\n * ```\n */\nexport function generateRandomString(length: number): string {\n const charset = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\";\n const randomBytesArray = randomBytes(length);\n let result = \"\";\n\n for (let i = 0; i < length; i++) {\n const randomIndex = randomBytesArray[i] % charset.length;\n result += charset[randomIndex];\n }\n\n return result;\n}\n","/**\n * JWT (JSON Web Token) utilities for SaaS applications\n * Provides secure token generation, verification, and management\n * Includes validation and error handling for production use\n */\n\nimport jwt from \"jsonwebtoken\";\n\nimport { logger } from \"../logging/logger\";\n\nexport { jwt };\n\n/**\n * Interface for JWT payload data\n */\ninterface JWTPayload {\n /** User ID */\n userId: string;\n /** User email */\n email: string;\n /** User role */\n role?: string;\n /** Workspace ID */\n workspaceId?: string;\n /** Token expiration time (Unix timestamp) */\n exp?: number;\n /** Token issued at time (Unix timestamp) */\n iat?: number;\n /** Additional custom claims */\n [key: string]: unknown;\n /** For Supabase */\n aud?: string;\n sub?: string;\n}\n\n/**\n * Interface for JWT options\n */\ninterface JWTOptions {\n /** Token expiration time (default: '7d') */\n expiresIn?: string;\n /** Token issuer */\n issuer?: string;\n /** Token audience */\n audience?: string;\n}\n\n/**\n * Interface for JWT verification result\n */\ninterface JWTVerificationResult {\n /** Whether the token is valid */\n isValid: boolean;\n /** Decoded payload if valid */\n payload?: JWTPayload;\n /** Error message if verification failed */\n error?: string;\n}\n\n/**\n * Generates a JWT token with the provided payload\n *\n * @param payload - The data to encode in the token\n * @param secret - The secret key for signing the token\n * @param options - Token generation options\n * @returns The generated JWT token or null if failed\n *\n * @example\n * ```typescript\n * import { generateJWT } from '@kumix/utils';\n *\n * // Basic usage\n * const token = generateJWT(\n * { userId: '123', email: 'user@example.com' },\n * process.env.JWT_SECRET\n * );\n *\n * // With custom expiration\n * const token = generateJWT(\n * { userId: '123', email: 'user@example.com', role: 'admin' },\n * process.env.JWT_SECRET,\n * { expiresIn: '1h' }\n * );\n *\n * // For API authentication\n * const authToken = generateJWT(\n * { userId: user.id, email: user.email, workspaceId: workspace.id },\n * process.env.JWT_SECRET,\n * { expiresIn: '24h', issuer: 'myapp.com' }\n * );\n * ```\n */\nexport function generateJWT(\n payload: JWTPayload,\n secret: string,\n options: JWTOptions = {},\n): string | null {\n // Validate inputs\n if (!payload || typeof payload !== \"object\") {\n logger.warn(\"JWT generation: Payload must be a valid object\");\n return null;\n }\n\n if (!payload.userId || typeof payload.userId !== \"string\") {\n logger.warn(\"JWT generation: userId is required in payload\");\n return null;\n }\n\n if (!payload.email || typeof payload.email !== \"string\") {\n logger.warn(\"JWT generation: email is required in payload\");\n return null;\n }\n\n if (!secret || typeof secret !== \"string\") {\n logger.warn(\"JWT generation: Secret must be a non-empty string\");\n return null;\n }\n\n const { expiresIn = \"7d\", issuer, audience } = options;\n\n const { exp, ...cleanPayload } = payload;\n\n try {\n const signOptions = {\n ...(typeof exp === \"undefined\" ? { expiresIn } : {}),\n ...(issuer && { issuer }),\n ...(audience && { audience }),\n } as jwt.SignOptions;\n\n const token = jwt.sign(cleanPayload, secret, signOptions);\n\n return token;\n } catch (error) {\n logger.error(\"JWT generation: Failed to generate token\", { error });\n return null;\n }\n}\n\n/**\n * Verifies and decodes a JWT token\n *\n * @param token - The JWT token to verify\n * @param secret - The secret key used to sign the token\n * @param options - Verification options\n * @returns Verification result with payload if valid\n *\n * @example\n * ```typescript\n * import { verifyJWT } from '@kumix/utils';\n *\n * // Basic usage\n * const result = verifyJWT(token, process.env.JWT_SECRET);\n * if (result.isValid && result.payload) {\n * const { userId, email } = result.payload;\n * // Proceed with authenticated user\n * }\n *\n * // With issuer verification\n * const result = verifyJWT(token, process.env.JWT_SECRET, {\n * issuer: 'myapp.com'\n * });\n *\n * // In authentication middleware\n * async function authenticateRequest(req, res, next) {\n * const token = req.headers.authorization?.replace('Bearer ', '');\n * const result = verifyJWT(token, process.env.JWT_SECRET);\n *\n * if (!result.isValid) {\n * return res.status(401).json({ error: 'Invalid token' });\n * }\n *\n * req.user = result.payload;\n * next();\n * }\n * ```\n */\nexport function verifyJWT(\n token: string,\n secret: string,\n options: { issuer?: string; audience?: string } = {},\n): JWTVerificationResult {\n // Validate inputs\n if (!token || typeof token !== \"string\") {\n return {\n isValid: false,\n error: \"Token must be a non-empty string\",\n };\n }\n\n if (!secret || typeof secret !== \"string\") {\n return {\n isValid: false,\n error: \"Secret must be a non-empty string\",\n };\n }\n\n const { issuer, audience } = options;\n\n try {\n const decoded = jwt.verify(token, secret, {\n ...(issuer && { issuer }),\n ...(audience && { audience }),\n }) as JWTPayload;\n\n return {\n isValid: true,\n payload: decoded,\n };\n } catch (error) {\n logger.warn(\"JWT verification: Token verification failed\", { error });\n\n if (error instanceof jwt.TokenExpiredError) {\n return {\n isValid: false,\n error: \"Token has expired\",\n };\n }\n\n if (error instanceof jwt.JsonWebTokenError) {\n return {\n isValid: false,\n error: \"Invalid token format\",\n };\n }\n\n return {\n isValid: false,\n error: \"Token verification failed\",\n };\n }\n}\n\n/**\n * Decodes a JWT token without verification (for debugging/inspection)\n *\n * @param token - The JWT token to decode\n * @returns Decoded payload or null if failed\n *\n * @example\n * ```typescript\n * import { decodeJWT } from '@kumix/utils';\n *\n * // Inspect token contents without verification\n * const payload = decodeJWT(token);\n * if (payload) {\n * console.log('Token expires at:', new Date(payload.exp * 1000));\n * console.log('User ID:', payload.userId);\n * }\n *\n * // For debugging purposes\n * function debugToken(token: string) {\n * const payload = decodeJWT(token);\n * if (payload) {\n * console.log('Token payload:', payload);\n * } else {\n * console.log('Invalid token format');\n * }\n * }\n * ```\n */\nexport function decodeJWT(token: string): JWTPayload | null {\n if (!token || typeof token !== \"string\") {\n return null;\n }\n\n try {\n const decoded = jwt.decode(token) as JWTPayload;\n return decoded;\n } catch (error) {\n logger.warn(\"JWT decode: Failed to decode token\", { error });\n return null;\n }\n}\n\n/**\n * Checks if a JWT token is expired\n *\n * @param token - The JWT token to check\n * @returns True if token is expired, false otherwise\n *\n * @example\n * ```typescript\n * import { isJWTExpired } from '@kumix/utils';\n *\n * // Check if token needs refresh\n * if (isJWTExpired(userToken)) {\n * // Redirect to login or refresh token\n * await refreshUserToken();\n * }\n *\n * // In token refresh logic\n * function shouldRefreshToken(token: string): boolean {\n * return isJWTExpired(token);\n * }\n * ```\n */\nexport function isJWTExpired(token: string): boolean {\n const payload = decodeJWT(token);\n if (!payload?.exp) {\n return true; // Consider invalid tokens as expired\n }\n\n const currentTime = Math.floor(Date.now() / 1000);\n return payload.exp < currentTime;\n}\n\n/**\n * Gets the remaining time until token expiration\n *\n * @param token - The JWT token to check\n * @returns Remaining time in seconds, or 0 if expired/invalid\n *\n * @example\n * ```typescript\n * import { getJWTTimeRemaining } from '@kumix/utils';\n *\n * // Check how much time is left\n * const timeLeft = getJWTTimeRemaining(userToken);\n * if (timeLeft < 300) { // Less than 5 minutes\n * // Show warning about token expiration\n * showTokenExpirationWarning();\n * }\n *\n * // Format time remaining for display\n * function formatTimeRemaining(token: string): string {\n * const seconds = getJWTTimeRemaining(token);\n * const minutes = Math.floor(seconds / 60);\n * const hours = Math.floor(minutes / 60);\n *\n * if (hours > 0) return `${hours}h ${minutes % 60}m`;\n * if (minutes > 0) return `${minutes}m`;\n * return `${seconds}s`;\n * }\n * ```\n */\nexport function getJWTTimeRemaining(token: string): number {\n const payload = decodeJWT(token);\n if (!payload?.exp) {\n return 0;\n }\n\n const currentTime = Math.floor(Date.now() / 1000);\n const timeRemaining = payload.exp - currentTime;\n\n return Math.max(0, timeRemaining);\n}\n","/**\n * Password hashing and verification utilities for SaaS applications\n * Provides secure password handling using bcrypt with proper salt rounds\n * Includes validation and error handling for production use\n */\n\nimport { randomInt } from \"node:crypto\";\n\nimport bcrypt from \"bcryptjs\";\n\nimport { logger } from \"../logging/logger\";\n\n/**\n * Default salt rounds for bcrypt hashing\n * 12 rounds provides good security while maintaining reasonable performance\n * Higher values increase security but also increase computation time\n */\nconst DEFAULT_SALT_ROUNDS = 12;\n\n/**\n * Interface for password hashing options\n */\ninterface HashPasswordOptions {\n /** Number of salt rounds for bcrypt (default: 12) */\n saltRounds?: number;\n}\n\n/**\n * Interface for password verification result\n */\ninterface VerifyPasswordResult {\n /** Whether the password is valid */\n isValid: boolean;\n /** Error message if verification failed */\n error?: string;\n}\n\n/**\n * Hashes a password using bcrypt with salt\n *\n * @param password - The plain text password to hash\n * @param options - Hashing options\n * @returns Promise that resolves to the hashed password or null if failed\n *\n * @example\n * ```typescript\n * import { hashPassword } from '@kumix/utils';\n *\n * // Basic usage\n * const hashedPassword = await hashPassword('userPassword123');\n * if (hashedPassword) {\n * // Store hashedPassword in database\n * await saveUser({ email, password: hashedPassword });\n * }\n *\n * // With custom salt rounds\n * const hashedPassword = await hashPassword('userPassword123', {\n * saltRounds: 14\n * });\n *\n * // Error handling\n * const hashedPassword = await hashPassword('userPassword123');\n * if (!hashedPassword) {\n * throw new Error('Failed to hash password');\n * }\n * ```\n */\nexport async function hashPassword(\n password: string,\n options: HashPasswordOptions = {},\n): Promise<string | null> {\n // Validate input\n if (!password || typeof password !== \"string\") {\n logger.warn(\"Password hashing: Password must be a non-empty string\");\n return null;\n }\n\n if (password.length < 1) {\n logger.warn(\"Password hashing: Password cannot be empty\");\n return null;\n }\n\n const { saltRounds = DEFAULT_SALT_ROUNDS } = options;\n\n // Validate salt rounds\n if (typeof saltRounds !== \"number\" || saltRounds < 4 || saltRounds > 20) {\n logger.warn(\"Password hashing: Salt rounds must be between 4 and 20\");\n return null;\n }\n\n try {\n // Generate salt and hash password\n const salt = await bcrypt.genSalt(saltRounds);\n const hashedPassword = await bcrypt.hash(password, salt);\n\n return hashedPassword;\n } catch (error) {\n logger.error(\"Password hashing: Failed to hash password\", { error });\n return null;\n }\n}\n\n/**\n * Verifies a password against its hash\n *\n * @param password - The plain text password to verify\n * @param hashedPassword - The hashed password to compare against\n * @returns Promise that resolves to verification result\n *\n * @example\n * ```typescript\n * import { verifyPassword } from '@kumix/utils';\n *\n * // Basic usage\n * const result = await verifyPassword('userPassword123', storedHashedPassword);\n * if (result.isValid) {\n * // Password is correct, proceed with login\n * await loginUser(user);\n * } else {\n * // Password is incorrect\n * throw new Error('Invalid credentials');\n * }\n *\n * // With error handling\n * const result = await verifyPassword('userPassword123', storedHashedPassword);\n * if (result.error) {\n * logger.error('Password verification failed:', result.error);\n * throw new Error('Authentication error');\n * }\n *\n * // In authentication middleware\n * async function authenticateUser(email: string, password: string) {\n * const user = await getUserByEmail(email);\n * if (!user) {\n * return { success: false, error: 'User not found' };\n * }\n *\n * const result = await verifyPassword(password, user.hashedPassword);\n * if (!result.isValid) {\n * return { success: false, error: 'Invalid password' };\n * }\n *\n * return { success: true, user };\n * }\n * ```\n */\nexport async function verifyPassword(\n password: string,\n hashedPassword: string,\n): Promise<VerifyPasswordResult> {\n // Validate inputs\n if (!password || typeof password !== \"string\") {\n return {\n isValid: false,\n error: \"Password must be a non-empty string\",\n };\n }\n\n if (!hashedPassword || typeof hashedPassword !== \"string\") {\n return {\n isValid: false,\n error: \"Hashed password must be a non-empty string\",\n };\n }\n\n // Basic bcrypt hash format validation\n if (\n !hashedPassword.startsWith(\"$2a$\") &&\n !hashedPassword.startsWith(\"$2b$\") &&\n !hashedPassword.startsWith(\"$2y$\")\n ) {\n return {\n isValid: false,\n error: \"Invalid hash format\",\n };\n }\n\n try {\n const isValid = await bcrypt.compare(password, hashedPassword);\n return { isValid };\n } catch (error) {\n logger.error(\"Password verification: Failed to verify password\", { error });\n return {\n isValid: false,\n error: \"Password verification failed\",\n };\n }\n}\n\n/**\n * Checks if a password needs to be rehashed (e.g., due to updated salt rounds)\n *\n * @param hashedPassword - The current hashed password\n * @param targetSaltRounds - The target salt rounds (default: 12)\n * @returns Whether the password needs to be rehashed\n *\n * @example\n * ```typescript\n * import { needsRehash, hashPassword } from '@kumix/utils';\n *\n * // Check if password needs rehashing during login\n * async function loginUser(email: string, password: string) {\n * const user = await getUserByEmail(email);\n * const result = await verifyPassword(password, user.hashedPassword);\n *\n * if (result.isValid) {\n * // Check if we need to rehash with updated salt rounds\n * if (needsRehash(user.hashedPassword, 14)) {\n * const newHash = await hashPassword(password, { saltRounds: 14 });\n * if (newHash) {\n * await updateUserPassword(user.id, newHash);\n * }\n * }\n *\n * return { success: true, user };\n * }\n *\n * return { success: false, error: 'Invalid credentials' };\n * }\n * ```\n */\nexport function needsRehash(\n hashedPassword: string,\n targetSaltRounds: number = DEFAULT_SALT_ROUNDS,\n): boolean {\n if (!hashedPassword || typeof hashedPassword !== \"string\") {\n return true; // Invalid hash should be rehashed\n }\n\n try {\n // Extract salt rounds from hash\n const parts = hashedPassword.split(\"$\");\n if (parts.length < 4) {\n return true; // Invalid format\n }\n\n const currentSaltRounds = parseInt(parts[2], 10);\n return currentSaltRounds !== targetSaltRounds;\n } catch (error) {\n logger.warn(\"Password rehash check: Failed to parse hash\", { error });\n return true; // If we can't parse, assume it needs rehashing\n }\n}\n\n/**\n * Generates a secure random password\n *\n * @param length - Length of the password (default: 16, min: 8, max: 128)\n * @param options - Password generation options\n * @returns Generated password string\n *\n * @example\n * ```typescript\n * import { generateSecurePassword } from '@kumix/utils';\n *\n * // Generate default password (16 characters)\n * const password = generateSecurePassword();\n *\n * // Generate longer password\n * const longPassword = generateSecurePassword(24);\n *\n * // Generate password without symbols\n * const simplePassword = generateSecurePassword(12, {\n * includeSymbols: false\n * });\n *\n * // For temporary passwords\n * const tempPassword = generateSecurePassword(12);\n * await sendPasswordResetEmail(user.email, tempPassword);\n * ```\n */\nexport function generateSecurePassword(\n length: number = 16,\n options: {\n includeUppercase?: boolean;\n includeLowercase?: boolean;\n includeNumbers?: boolean;\n includeSymbols?: boolean;\n } = {},\n): string {\n const {\n includeUppercase = true,\n includeLowercase = true,\n includeNumbers = true,\n includeSymbols = true,\n } = options;\n\n // Validate length\n const validLength = Math.max(8, Math.min(128, length));\n\n let charset = \"\";\n if (includeUppercase) charset += \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\";\n if (includeLowercase) charset += \"abcdefghijklmnopqrstuvwxyz\";\n if (includeNumbers) charset += \"0123456789\";\n if (includeSymbols) charset += \"!@#$%^&*()_+-=[]{}|;:,.<>?\";\n\n if (charset.length === 0) {\n // Fallback to alphanumeric if no character sets selected\n charset = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\";\n }\n\n let password = \"\";\n for (let i = 0; i < validLength; i++) {\n // Use a cryptographically secure RNG to avoid bias and predictability\n const randomIndex = randomInt(0, charset.length);\n password += charset[randomIndex];\n }\n\n return password;\n}\n","/**\n * Utility function for parsing date strings into Date objects\n * Provides natural language date parsing capabilities\n */\n\nimport * as chrono from \"chrono-node\";\n\n/**\n * Parses a date string into a Date object using natural language processing\n * Passes through Date objects unchanged\n *\n * @param str - The date string to parse or a Date object to pass through\n * @returns A Date object, or null if parsing fails\n *\n * @example\n * ```ts\n * // Parse natural language date strings\n * parseDateTime('tomorrow at 3pm')\n * // Returns a Date object for tomorrow at 3:00 PM\n *\n * // Parse formal date strings\n * parseDateTime('2023-06-15')\n * // Returns a Date object for June 15, 2023\n *\n * // Parse relative dates\n * parseDateTime('next Friday')\n * // Returns a Date object for next Friday\n *\n * // Pass through existing Date objects\n * const date = new Date('2023-06-15')\n * parseDateTime(date)\n * // Returns the same Date object\n *\n * // Parse time strings\n * parseDateTime('3:30pm')\n * // Returns a Date object for today at 3:30 PM\n *\n * // Parse complex date expressions\n * parseDateTime('3 days after next Monday')\n * // Returns the appropriate Date object\n * ```\n */\nexport const parseDateTime = (str: Date | string): Date | null => {\n if (str instanceof Date) return str;\n return chrono.parseDate(str);\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,SAAgB,qBAAqB,QAAwB;CAC3D,MAAM,UAAU;CAChB,MAAM,mBAAmB,YAAY,MAAM;CAC3C,IAAI,SAAS;CAEb,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,KAAK;EAC/B,MAAM,cAAc,iBAAiB,KAAK;EAC1C,UAAU,QAAQ;CACpB;CAEA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACkDA,SAAgB,YACd,SACA,QACA,UAAsB,CAAC,GACR;CAEf,IAAI,CAAC,WAAW,OAAO,YAAY,UAAU;EAC3C,OAAO,KAAK,gDAAgD;EAC5D,OAAO;CACT;CAEA,IAAI,CAAC,QAAQ,UAAU,OAAO,QAAQ,WAAW,UAAU;EACzD,OAAO,KAAK,+CAA+C;EAC3D,OAAO;CACT;CAEA,IAAI,CAAC,QAAQ,SAAS,OAAO,QAAQ,UAAU,UAAU;EACvD,OAAO,KAAK,8CAA8C;EAC1D,OAAO;CACT;CAEA,IAAI,CAAC,UAAU,OAAO,WAAW,UAAU;EACzC,OAAO,KAAK,mDAAmD;EAC/D,OAAO;CACT;CAEA,MAAM,EAAE,YAAY,MAAM,QAAQ,aAAa;CAE/C,MAAM,EAAE,KAAK,GAAG,iBAAiB;CAEjC,IAAI;EACF,MAAM,cAAc;GAClB,GAAI,OAAO,QAAQ,cAAc,EAAE,UAAU,IAAI,CAAC;GAClD,GAAI,UAAU,EAAE,OAAO;GACvB,GAAI,YAAY,EAAE,SAAS;EAC7B;EAIA,OAFc,IAAI,KAAK,cAAc,QAAQ,WAElC;CACb,SAAS,OAAO;EACd,OAAO,MAAM,4CAA4C,EAAE,MAAM,CAAC;EAClE,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,SAAgB,UACd,OACA,QACA,UAAkD,CAAC,GAC5B;CAEvB,IAAI,CAAC,SAAS,OAAO,UAAU,UAC7B,OAAO;EACL,SAAS;EACT,OAAO;CACT;CAGF,IAAI,CAAC,UAAU,OAAO,WAAW,UAC/B,OAAO;EACL,SAAS;EACT,OAAO;CACT;CAGF,MAAM,EAAE,QAAQ,aAAa;CAE7B,IAAI;EAMF,OAAO;GACL,SAAS;GACT,SAPc,IAAI,OAAO,OAAO,QAAQ;IACxC,GAAI,UAAU,EAAE,OAAO;IACvB,GAAI,YAAY,EAAE,SAAS;GAC7B,CAIiB;EACjB;CACF,SAAS,OAAO;EACd,OAAO,KAAK,+CAA+C,EAAE,MAAM,CAAC;EAEpE,IAAI,iBAAiB,IAAI,mBACvB,OAAO;GACL,SAAS;GACT,OAAO;EACT;EAGF,IAAI,iBAAiB,IAAI,mBACvB,OAAO;GACL,SAAS;GACT,OAAO;EACT;EAGF,OAAO;GACL,SAAS;GACT,OAAO;EACT;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,SAAgB,UAAU,OAAkC;CAC1D,IAAI,CAAC,SAAS,OAAO,UAAU,UAC7B,OAAO;CAGT,IAAI;EAEF,OADgB,IAAI,OAAO,KACd;CACf,SAAS,OAAO;EACd,OAAO,KAAK,sCAAsC,EAAE,MAAM,CAAC;EAC3D,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,aAAa,OAAwB;CACnD,MAAM,UAAU,UAAU,KAAK;CAC/B,IAAI,CAAC,SAAS,KACZ,OAAO;CAGT,MAAM,cAAc,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;CAChD,OAAO,QAAQ,MAAM;AACvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,SAAgB,oBAAoB,OAAuB;CACzD,MAAM,UAAU,UAAU,KAAK;CAC/B,IAAI,CAAC,SAAS,KACZ,OAAO;CAGT,MAAM,cAAc,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;CAChD,MAAM,gBAAgB,QAAQ,MAAM;CAEpC,OAAO,KAAK,IAAI,GAAG,aAAa;AAClC;;;;;;;;;;;;;ACxUA,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkD5B,eAAsB,aACpB,UACA,UAA+B,CAAC,GACR;CAExB,IAAI,CAAC,YAAY,OAAO,aAAa,UAAU;EAC7C,OAAO,KAAK,uDAAuD;EACnE,OAAO;CACT;CAEA,IAAI,SAAS,SAAS,GAAG;EACvB,OAAO,KAAK,4CAA4C;EACxD,OAAO;CACT;CAEA,MAAM,EAAE,aAAa,wBAAwB;CAG7C,IAAI,OAAO,eAAe,YAAY,aAAa,KAAK,aAAa,IAAI;EACvE,OAAO,KAAK,wDAAwD;EACpE,OAAO;CACT;CAEA,IAAI;EAEF,MAAM,OAAO,MAAM,OAAO,QAAQ,UAAU;EAG5C,OAAO,MAFsB,OAAO,KAAK,UAAU,IAAI;CAGzD,SAAS,OAAO;EACd,OAAO,MAAM,6CAA6C,EAAE,MAAM,CAAC;EACnE,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CA,eAAsB,eACpB,UACA,gBAC+B;CAE/B,IAAI,CAAC,YAAY,OAAO,aAAa,UACnC,OAAO;EACL,SAAS;EACT,OAAO;CACT;CAGF,IAAI,CAAC,kBAAkB,OAAO,mBAAmB,UAC/C,OAAO;EACL,SAAS;EACT,OAAO;CACT;CAIF,IACE,CAAC,eAAe,WAAW,MAAM,KACjC,CAAC,eAAe,WAAW,MAAM,KACjC,CAAC,eAAe,WAAW,MAAM,GAEjC,OAAO;EACL,SAAS;EACT,OAAO;CACT;CAGF,IAAI;EAEF,OAAO,EAAE,SAAA,MADa,OAAO,QAAQ,UAAU,cAAc,EAC5C;CACnB,SAAS,OAAO;EACd,OAAO,MAAM,oDAAoD,EAAE,MAAM,CAAC;EAC1E,OAAO;GACL,SAAS;GACT,OAAO;EACT;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,SAAgB,YACd,gBACA,mBAA2B,qBAClB;CACT,IAAI,CAAC,kBAAkB,OAAO,mBAAmB,UAC/C,OAAO;CAGT,IAAI;EAEF,MAAM,QAAQ,eAAe,MAAM,GAAG;EACtC,IAAI,MAAM,SAAS,GACjB,OAAO;EAIT,OAD0B,SAAS,MAAM,IAAI,EACtB,MAAM;CAC/B,SAAS,OAAO;EACd,OAAO,KAAK,+CAA+C,EAAE,MAAM,CAAC;EACpE,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,SAAgB,uBACd,SAAiB,IACjB,UAKI,CAAC,GACG;CACR,MAAM,EACJ,mBAAmB,MACnB,mBAAmB,MACnB,iBAAiB,MACjB,iBAAiB,SACf;CAGJ,MAAM,cAAc,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,MAAM,CAAC;CAErD,IAAI,UAAU;CACd,IAAI,kBAAkB,WAAW;CACjC,IAAI,kBAAkB,WAAW;CACjC,IAAI,gBAAgB,WAAW;CAC/B,IAAI,gBAAgB,WAAW;CAE/B,IAAI,QAAQ,WAAW,GAErB,UAAU;CAGZ,IAAI,WAAW;CACf,KAAK,IAAI,IAAI,GAAG,IAAI,aAAa,KAAK;EAEpC,MAAM,cAAc,UAAU,GAAG,QAAQ,MAAM;EAC/C,YAAY,QAAQ;CACtB;CAEA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC3QA,MAAa,iBAAiB,QAAoC;CAChE,IAAI,eAAe,MAAM,OAAO;CAChC,OAAO,OAAO,UAAU,GAAG;AAC7B"}
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kumix/utils",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A comprehensive library of utility functions for building modern SaaS applications.",
|
|
5
|
+
"author": "Kumix Labs <hai@kumix.io>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"module": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"sideEffects": false,
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./server": {
|
|
18
|
+
"types": "./dist/server.d.ts",
|
|
19
|
+
"import": "./dist/server.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"clean": "rimraf dist",
|
|
24
|
+
"clean:all": "rimraf .turbo coverage dist node_modules",
|
|
25
|
+
"dev": "tsdown --watch",
|
|
26
|
+
"build": "tsdown",
|
|
27
|
+
"types:check": "tsc --noEmit",
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"test:watch": "vitest",
|
|
30
|
+
"test:coverage": "vitest run --coverage"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/kumixlabs/toolkits",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/kumixlabs/toolkits.git",
|
|
36
|
+
"directory": "packages/utils"
|
|
37
|
+
},
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/kumixlabs/toolkits/issues"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist"
|
|
43
|
+
],
|
|
44
|
+
"keywords": [
|
|
45
|
+
"kumix",
|
|
46
|
+
"utils"
|
|
47
|
+
],
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@paralleldrive/cuid2": "^3.3.0",
|
|
50
|
+
"@sindresorhus/slugify": "^3.0.0",
|
|
51
|
+
"base-x": "^5.0.1",
|
|
52
|
+
"bcryptjs": "^3.0.3",
|
|
53
|
+
"chrono-node": "2.9.1",
|
|
54
|
+
"clsx": "^2.1.1",
|
|
55
|
+
"consola": "^3.4.2",
|
|
56
|
+
"jsonwebtoken": "^9.0.3",
|
|
57
|
+
"ms": "^2.1.3",
|
|
58
|
+
"nanoid": "^5.1.16",
|
|
59
|
+
"tailwind-merge": "^3.6.0"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@kumix/tsconfig": "^0.1.0",
|
|
63
|
+
"@types/jsonwebtoken": "^9.0.10",
|
|
64
|
+
"@types/ms": "^2.1.0",
|
|
65
|
+
"@types/node": "^26.1.0",
|
|
66
|
+
"tsdown": "^0.22.3",
|
|
67
|
+
"typescript": "^6.0.3"
|
|
68
|
+
},
|
|
69
|
+
"publishConfig": {
|
|
70
|
+
"registry": "https://registry.npmjs.org/",
|
|
71
|
+
"access": "public"
|
|
72
|
+
}
|
|
73
|
+
}
|