@happyvertical/utils 0.80.0 → 0.80.2
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/dist/browser.js +8 -59
- package/dist/browser.js.map +1 -1
- package/dist/chunks/universal-DOIuUel_.js +1415 -0
- package/dist/chunks/universal-DOIuUel_.js.map +1 -0
- package/dist/cli/claude-context.js +17 -17
- package/dist/cli/claude-context.js.map +1 -1
- package/dist/index.js +418 -360
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/dist/chunks/universal-BeseWxvS.js +0 -934
- package/dist/chunks/universal-BeseWxvS.js.map +0 -1
|
@@ -0,0 +1,1415 @@
|
|
|
1
|
+
import { createId, isCuid } from "@paralleldrive/cuid2";
|
|
2
|
+
import { add, format, isValid, parse, parseISO } from "date-fns";
|
|
3
|
+
import pluralize from "pluralize";
|
|
4
|
+
//#region src/config/env-config.ts
|
|
5
|
+
/**
|
|
6
|
+
* Convert snake_case to camelCase
|
|
7
|
+
* @param str - Snake case string
|
|
8
|
+
* @returns Camel case string
|
|
9
|
+
* @example
|
|
10
|
+
* toCamelCase('max_retries') → 'maxRetries'
|
|
11
|
+
* toCamelCase('api_key') → 'apiKey'
|
|
12
|
+
*/
|
|
13
|
+
function toCamelCase(str) {
|
|
14
|
+
return str.toLowerCase().replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Convert camelCase to SCREAMING_SNAKE_CASE
|
|
18
|
+
* @param str - Camel case string
|
|
19
|
+
* @returns Screaming snake case string
|
|
20
|
+
* @example
|
|
21
|
+
* toScreamingSnakeCase('maxRetries') → 'MAX_RETRIES'
|
|
22
|
+
* toScreamingSnakeCase('apiKey') → 'API_KEY'
|
|
23
|
+
*/
|
|
24
|
+
function toScreamingSnakeCase(str) {
|
|
25
|
+
return str.replace(/([A-Z])/g, "_$1").toUpperCase().replace(/^_/, "");
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Convert string value to specified type
|
|
29
|
+
* @param value - String value from environment variable
|
|
30
|
+
* @param type - Target type for conversion
|
|
31
|
+
* @returns Converted value
|
|
32
|
+
* @throws Error if JSON parsing fails
|
|
33
|
+
*/
|
|
34
|
+
function convertType(value, type) {
|
|
35
|
+
switch (type) {
|
|
36
|
+
case "number": {
|
|
37
|
+
const num = Number(value);
|
|
38
|
+
if (Number.isNaN(num)) throw new Error(`Cannot convert "${value}" to number: result is NaN`);
|
|
39
|
+
return num;
|
|
40
|
+
}
|
|
41
|
+
case "boolean": return value.toLowerCase() === "true" || value === "1" || value.toLowerCase() === "yes";
|
|
42
|
+
case "json": try {
|
|
43
|
+
return JSON.parse(value);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
throw new Error(`Cannot parse "${value}" as JSON: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
46
|
+
}
|
|
47
|
+
default: return value;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Load configuration from environment variables
|
|
52
|
+
*
|
|
53
|
+
* Scans environment variables matching the specified pattern and merges them
|
|
54
|
+
* with user-provided options. User options always take precedence over env vars.
|
|
55
|
+
*
|
|
56
|
+
* @template T - Type of configuration object
|
|
57
|
+
* @param userOptions - User-provided configuration (takes precedence)
|
|
58
|
+
* @param options - Configuration options for env loading
|
|
59
|
+
* @returns Merged configuration with env vars and user options
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* // Load HAVE_AI_* variables
|
|
64
|
+
* const config = loadEnvConfig({ provider: 'openai' }, {
|
|
65
|
+
* packageName: 'ai',
|
|
66
|
+
* schema: {
|
|
67
|
+
* provider: 'string',
|
|
68
|
+
* model: 'string',
|
|
69
|
+
* timeout: 'number',
|
|
70
|
+
* maxRetries: 'number'
|
|
71
|
+
* }
|
|
72
|
+
* });
|
|
73
|
+
* // Checks: HAVE_AI_PROVIDER, HAVE_AI_MODEL, HAVE_AI_TIMEOUT, HAVE_AI_MAX_RETRIES
|
|
74
|
+
*
|
|
75
|
+
* // Load SQLOO_* variables (backward compatibility)
|
|
76
|
+
* const sqlConfig = loadEnvConfig({}, {
|
|
77
|
+
* prefix: 'SQLOO',
|
|
78
|
+
* schema: {
|
|
79
|
+
* database: 'string',
|
|
80
|
+
* host: 'string',
|
|
81
|
+
* port: 'number'
|
|
82
|
+
* }
|
|
83
|
+
* });
|
|
84
|
+
* // Checks: SQLOO_DATABASE, SQLOO_HOST, SQLOO_PORT
|
|
85
|
+
*
|
|
86
|
+
* // Load without prefix
|
|
87
|
+
* const customConfig = loadEnvConfig({}, {
|
|
88
|
+
* prefix: '',
|
|
89
|
+
* schema: {
|
|
90
|
+
* OPENAI_API_KEY: 'string'
|
|
91
|
+
* }
|
|
92
|
+
* });
|
|
93
|
+
* // Checks: OPENAI_API_KEY
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
function loadEnvConfig(userOptions = {}, options = {}) {
|
|
97
|
+
const { prefix = "HAVE", packageName, schema = {}, transform = {}, allowUnknown = true } = options;
|
|
98
|
+
const envPrefix = packageName && prefix ? `${prefix}_${packageName.toUpperCase()}_` : prefix ? `${prefix}_` : "";
|
|
99
|
+
const config = { ...userOptions };
|
|
100
|
+
const fieldsToScan = allowUnknown ? /* @__PURE__ */ new Set([...Object.keys(schema), ...Object.keys(process.env).filter((key) => envPrefix && key.startsWith(envPrefix)).map((key) => {
|
|
101
|
+
return toCamelCase(key.slice(envPrefix.length));
|
|
102
|
+
})]) : new Set(Object.keys(schema));
|
|
103
|
+
for (const fieldName of fieldsToScan) {
|
|
104
|
+
if (fieldName in userOptions) continue;
|
|
105
|
+
const envVarName = envPrefix ? envPrefix + toScreamingSnakeCase(fieldName) : fieldName;
|
|
106
|
+
const envValue = process.env[envVarName];
|
|
107
|
+
if (envValue === void 0) continue;
|
|
108
|
+
const transformFn = transform[fieldName];
|
|
109
|
+
if (transformFn) {
|
|
110
|
+
try {
|
|
111
|
+
config[fieldName] = transformFn(envValue);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.warn(`Failed to transform ${envVarName}="${envValue}":`, error instanceof Error ? error.message : error);
|
|
114
|
+
}
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const fieldType = schema[fieldName];
|
|
118
|
+
if (fieldType) {
|
|
119
|
+
try {
|
|
120
|
+
config[fieldName] = convertType(envValue, fieldType);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.warn(`Failed to convert ${envVarName}="${envValue}" to ${fieldType}:`, error instanceof Error ? error.message : error);
|
|
123
|
+
}
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
config[fieldName] = envValue;
|
|
127
|
+
}
|
|
128
|
+
return config;
|
|
129
|
+
}
|
|
130
|
+
//#endregion
|
|
131
|
+
//#region src/shared/code/extraction.ts
|
|
132
|
+
/**
|
|
133
|
+
* Code extraction utilities for parsing code from text (e.g., AI responses)
|
|
134
|
+
*
|
|
135
|
+
* Provides functions to extract code blocks, JSON data, and function definitions
|
|
136
|
+
* from markdown-formatted text or AI-generated responses.
|
|
137
|
+
*/
|
|
138
|
+
/**
|
|
139
|
+
* Extracts a code block from markdown-formatted text
|
|
140
|
+
*
|
|
141
|
+
* @param text - The text containing markdown code blocks
|
|
142
|
+
* @param language - Optional language specifier to match (e.g., 'javascript', 'typescript', 'json')
|
|
143
|
+
* @returns The extracted code without markdown delimiters, or empty string if not found
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```typescript
|
|
147
|
+
* const code = extractCodeBlock(`
|
|
148
|
+
* Here's the function:
|
|
149
|
+
* \`\`\`javascript
|
|
150
|
+
* function hello() {
|
|
151
|
+
* return 'world';
|
|
152
|
+
* }
|
|
153
|
+
* \`\`\`
|
|
154
|
+
* `, 'javascript');
|
|
155
|
+
* // Returns: "function hello() {\n return 'world';\n}"
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
function extractCodeBlock(text, language) {
|
|
159
|
+
if (!text) return "";
|
|
160
|
+
const langPattern = language ? `${language}\\s*` : "(?:\\w+\\s*)?";
|
|
161
|
+
const codeBlockRegex = new RegExp(`\`\`\`${langPattern}\\r?\\n([\\s\\S]*?)\\r?\\n\`\`\``, "i");
|
|
162
|
+
const match = text.match(codeBlockRegex);
|
|
163
|
+
if (match?.[1]) return match[1].trim();
|
|
164
|
+
const inlineMatch = text.match(/`([^`]+)`/);
|
|
165
|
+
if (inlineMatch?.[1]) return inlineMatch[1].trim();
|
|
166
|
+
return "";
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Extracts and parses JSON from text, handling markdown code blocks
|
|
170
|
+
*
|
|
171
|
+
* @param text - The text containing JSON data
|
|
172
|
+
* @returns The parsed JSON object
|
|
173
|
+
* @throws {SyntaxError} If the JSON is invalid
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```typescript
|
|
177
|
+
* const data = extractJSON<{ name: string }>(`
|
|
178
|
+
* The result is:
|
|
179
|
+
* \`\`\`json
|
|
180
|
+
* {
|
|
181
|
+
* "name": "example"
|
|
182
|
+
* }
|
|
183
|
+
* \`\`\`
|
|
184
|
+
* `);
|
|
185
|
+
* // Returns: { name: "example" }
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
function extractJSON(text) {
|
|
189
|
+
if (!text) throw new SyntaxError("Cannot extract JSON from empty text");
|
|
190
|
+
let jsonText = extractCodeBlock(text, "json");
|
|
191
|
+
if (!jsonText) jsonText = extractCodeBlock(text);
|
|
192
|
+
if (!jsonText) {
|
|
193
|
+
const jsonObjectMatch = text.match(/\{[\s\S]*\}/);
|
|
194
|
+
const jsonArrayMatch = text.match(/\[[\s\S]*\]/);
|
|
195
|
+
if (jsonObjectMatch) jsonText = jsonObjectMatch[0];
|
|
196
|
+
else if (jsonArrayMatch) jsonText = jsonArrayMatch[0];
|
|
197
|
+
else jsonText = text.trim();
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
return JSON.parse(jsonText);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
throw new SyntaxError(`Failed to parse JSON: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Extracts all code blocks from markdown-formatted text
|
|
207
|
+
*
|
|
208
|
+
* @param text - The text containing markdown code blocks
|
|
209
|
+
* @param language - Optional language specifier to filter by
|
|
210
|
+
* @returns Array of extracted code blocks
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```typescript
|
|
214
|
+
* const blocks = extractAllCodeBlocks(`
|
|
215
|
+
* \`\`\`javascript
|
|
216
|
+
* const a = 1;
|
|
217
|
+
* \`\`\`
|
|
218
|
+
*
|
|
219
|
+
* \`\`\`typescript
|
|
220
|
+
* const b: number = 2;
|
|
221
|
+
* \`\`\`
|
|
222
|
+
* `);
|
|
223
|
+
* // Returns: ["const a = 1;", "const b: number = 2;"]
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
function extractAllCodeBlocks(text, language) {
|
|
227
|
+
if (!text) return [];
|
|
228
|
+
const langPattern = language ? `${language}\\s*` : "(?:\\w+\\s*)?";
|
|
229
|
+
const codeBlockRegex = new RegExp(`\`\`\`${langPattern}\\r?\\n([\\s\\S]*?)\\r?\\n\`\`\``, "gi");
|
|
230
|
+
const blocks = [];
|
|
231
|
+
const matches = text.matchAll(codeBlockRegex);
|
|
232
|
+
for (const match of matches) if (match[1]) blocks.push(match[1].trim());
|
|
233
|
+
return blocks;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Extracts a specific function definition from code
|
|
237
|
+
*
|
|
238
|
+
* @param code - The code containing function definitions
|
|
239
|
+
* @param functionName - The name of the function to extract
|
|
240
|
+
* @returns The function definition, or empty string if not found
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```typescript
|
|
244
|
+
* const code = `
|
|
245
|
+
* function foo() { return 1; }
|
|
246
|
+
* function bar() { return 2; }
|
|
247
|
+
* `;
|
|
248
|
+
*
|
|
249
|
+
* const fooFunc = extractFunctionDefinition(code, 'foo');
|
|
250
|
+
* // Returns: "function foo() { return 1; }"
|
|
251
|
+
* ```
|
|
252
|
+
*/
|
|
253
|
+
function extractFunctionDefinition(code, functionName) {
|
|
254
|
+
if (!code || !functionName) return "";
|
|
255
|
+
const patterns = [
|
|
256
|
+
{
|
|
257
|
+
regex: new RegExp(`function\\s+${functionName}\\s*\\([^)]*\\)\\s*\\{`, "i"),
|
|
258
|
+
hasBraces: true
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
regex: new RegExp(`(?:const|let|var)\\s+${functionName}\\s*=\\s*function\\s*\\([^)]*\\)\\s*\\{`, "i"),
|
|
262
|
+
hasBraces: true
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
regex: new RegExp(`(?:const|let|var)\\s+${functionName}\\s*=\\s*\\([^)]*\\)\\s*=>\\s*\\{`, "i"),
|
|
266
|
+
hasBraces: true
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
regex: new RegExp(`(?:const|let|var)\\s+${functionName}\\s*=\\s*\\([^)]*\\)\\s*=>\\s*[^;]+;?`, "i"),
|
|
270
|
+
hasBraces: false
|
|
271
|
+
}
|
|
272
|
+
];
|
|
273
|
+
for (const { regex, hasBraces } of patterns) {
|
|
274
|
+
const match = code.match(regex);
|
|
275
|
+
if (match && match.index !== void 0) {
|
|
276
|
+
const startIdx = match.index;
|
|
277
|
+
if (!hasBraces) return match[0].trim();
|
|
278
|
+
const braceIdx = code.indexOf("{", startIdx);
|
|
279
|
+
if (braceIdx === -1) continue;
|
|
280
|
+
let idx = braceIdx;
|
|
281
|
+
let depth = 0;
|
|
282
|
+
let endIdx = -1;
|
|
283
|
+
while (idx < code.length) {
|
|
284
|
+
const char = code[idx];
|
|
285
|
+
if (char === "{") depth++;
|
|
286
|
+
else if (char === "}") {
|
|
287
|
+
depth--;
|
|
288
|
+
if (depth === 0) {
|
|
289
|
+
endIdx = idx;
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
idx++;
|
|
294
|
+
}
|
|
295
|
+
if (endIdx !== -1) return code.slice(startIdx, endIdx + 1).trim();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return "";
|
|
299
|
+
}
|
|
300
|
+
//#endregion
|
|
301
|
+
//#region src/shared/code/validation.ts
|
|
302
|
+
/**
|
|
303
|
+
* Default dangerous patterns that should be disallowed
|
|
304
|
+
*/
|
|
305
|
+
var DANGEROUS_PATTERNS = [
|
|
306
|
+
/require\s*\(/i,
|
|
307
|
+
/import\s+/i,
|
|
308
|
+
/eval\s*\(/i,
|
|
309
|
+
/Function\s*\(/i,
|
|
310
|
+
/process\./i,
|
|
311
|
+
/fs\./i,
|
|
312
|
+
/child_process/i,
|
|
313
|
+
/__dirname/i,
|
|
314
|
+
/__filename/i,
|
|
315
|
+
/global\./i
|
|
316
|
+
];
|
|
317
|
+
/**
|
|
318
|
+
* Validates code before execution in a sandbox
|
|
319
|
+
*
|
|
320
|
+
* @param code - The code to validate
|
|
321
|
+
* @param options - Validation options
|
|
322
|
+
* @returns Validation result with errors and warnings
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* ```typescript
|
|
326
|
+
* const result = validateCode(`
|
|
327
|
+
* function parse(data) {
|
|
328
|
+
* return JSON.parse(data);
|
|
329
|
+
* }
|
|
330
|
+
* `, {
|
|
331
|
+
* allowedGlobals: ['JSON'],
|
|
332
|
+
* maxLength: 10000
|
|
333
|
+
* });
|
|
334
|
+
*
|
|
335
|
+
* if (!result.valid) {
|
|
336
|
+
* console.error('Code validation failed:', result.errors);
|
|
337
|
+
* }
|
|
338
|
+
* ```
|
|
339
|
+
*/
|
|
340
|
+
function validateCode(code, options = {}) {
|
|
341
|
+
const { allowedGlobals, disallowedPatterns = DANGEROUS_PATTERNS, maxLength = 5e4, allowRequire = false, allowImport = false, allowEval = false, checkSyntax = true } = options;
|
|
342
|
+
const errors = [];
|
|
343
|
+
const warnings = [];
|
|
344
|
+
if (!code || code.trim().length === 0) {
|
|
345
|
+
errors.push("Code is empty");
|
|
346
|
+
return {
|
|
347
|
+
valid: false,
|
|
348
|
+
errors,
|
|
349
|
+
warnings
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
if (code.length > maxLength) errors.push(`Code exceeds maximum length (${code.length} > ${maxLength} characters)`);
|
|
353
|
+
const patternFlags = /* @__PURE__ */ new Map([
|
|
354
|
+
[DANGEROUS_PATTERNS[0], "require"],
|
|
355
|
+
[DANGEROUS_PATTERNS[1], "import"],
|
|
356
|
+
[DANGEROUS_PATTERNS[2], "eval"],
|
|
357
|
+
[DANGEROUS_PATTERNS[3], "eval"]
|
|
358
|
+
]);
|
|
359
|
+
const effectivePatterns = disallowedPatterns.filter((_pattern, index) => {
|
|
360
|
+
const patternType = patternFlags.get(DANGEROUS_PATTERNS[index]);
|
|
361
|
+
if (patternType === "require" && allowRequire) return false;
|
|
362
|
+
if (patternType === "import" && allowImport) return false;
|
|
363
|
+
if (patternType === "eval" && allowEval) return false;
|
|
364
|
+
return true;
|
|
365
|
+
});
|
|
366
|
+
for (const pattern of effectivePatterns) if (pattern.test(code)) errors.push(`Code contains disallowed pattern: ${pattern.source.replace(/\\/g, "")}`);
|
|
367
|
+
if (allowedGlobals) {
|
|
368
|
+
const undeclaredVars = findUndeclaredVariables(code, allowedGlobals);
|
|
369
|
+
if (undeclaredVars.length > 0) warnings.push(`Potentially undeclared variables: ${undeclaredVars.join(", ")}`);
|
|
370
|
+
}
|
|
371
|
+
if (checkSyntax) {
|
|
372
|
+
const syntaxErrors = checkCodeSyntax(code);
|
|
373
|
+
errors.push(...syntaxErrors);
|
|
374
|
+
}
|
|
375
|
+
const stats = {
|
|
376
|
+
length: code.length,
|
|
377
|
+
lines: code.split("\n").length,
|
|
378
|
+
hasAsync: /\basync\b\s*(function|\([\w\s,={}[\]]*\)\s*=>|\w+\s*\()/m.test(code),
|
|
379
|
+
hasArrowFunctions: /=>/.test(code),
|
|
380
|
+
hasClasses: /\bclass\s+\w+/.test(code)
|
|
381
|
+
};
|
|
382
|
+
if (stats.lines > 100) warnings.push(`Code is long (${stats.lines} lines) - consider breaking into smaller functions`);
|
|
383
|
+
return {
|
|
384
|
+
valid: errors.length === 0,
|
|
385
|
+
errors,
|
|
386
|
+
warnings,
|
|
387
|
+
stats
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Checks code syntax without executing it
|
|
392
|
+
*
|
|
393
|
+
* @param code - The code to check
|
|
394
|
+
* @returns Array of syntax error messages (empty if valid)
|
|
395
|
+
*/
|
|
396
|
+
function checkCodeSyntax(code) {
|
|
397
|
+
const errors = [];
|
|
398
|
+
try {
|
|
399
|
+
new Function(code);
|
|
400
|
+
} catch (error) {
|
|
401
|
+
if (error instanceof SyntaxError) {
|
|
402
|
+
const message = error.message;
|
|
403
|
+
if (message.includes("await is only valid") && /\bawait\b/.test(code)) try {
|
|
404
|
+
new Function(`(async function() { ${code} })()`);
|
|
405
|
+
return errors;
|
|
406
|
+
} catch (asyncError) {
|
|
407
|
+
if (asyncError instanceof SyntaxError) errors.push(`Syntax error: ${asyncError.message}`);
|
|
408
|
+
}
|
|
409
|
+
else errors.push(`Syntax error: ${message}`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return errors;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Finds potentially undeclared variables in code
|
|
416
|
+
*
|
|
417
|
+
* **Important Limitations:**
|
|
418
|
+
* This is a basic regex-based static analysis with known limitations:
|
|
419
|
+
* - Cannot detect variables in destructuring assignments
|
|
420
|
+
* - May miss variables declared in nested scopes
|
|
421
|
+
* - Cannot handle dynamic variable access (e.g., obj[varName])
|
|
422
|
+
* - Will have false positives for method calls and object properties
|
|
423
|
+
* - Does not understand scope chains or closures
|
|
424
|
+
*
|
|
425
|
+
* For production use cases requiring accurate variable analysis, consider using
|
|
426
|
+
* a proper AST parser like @babel/parser or acorn.
|
|
427
|
+
*
|
|
428
|
+
* @param code - The code to analyze
|
|
429
|
+
* @param allowedGlobals - List of allowed global variables
|
|
430
|
+
* @returns Array of potentially undeclared variable names (may include false positives)
|
|
431
|
+
*/
|
|
432
|
+
function findUndeclaredVariables(code, allowedGlobals) {
|
|
433
|
+
const undeclaredVars = /* @__PURE__ */ new Set();
|
|
434
|
+
const matches = code.matchAll(/\b([a-zA-Z_$][a-zA-Z0-9_$]*)\b/g);
|
|
435
|
+
const declaredVars = /* @__PURE__ */ new Set();
|
|
436
|
+
const declarations = code.matchAll(/\b(?:var|let|const|function)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g);
|
|
437
|
+
for (const match of declarations) if (match[1]) declaredVars.add(match[1]);
|
|
438
|
+
for (const match of matches) {
|
|
439
|
+
const identifier = match[1];
|
|
440
|
+
if (isJavaScriptKeyword(identifier)) continue;
|
|
441
|
+
if (declaredVars.has(identifier)) continue;
|
|
442
|
+
if (allowedGlobals.includes(identifier)) continue;
|
|
443
|
+
if (isCommonBuiltin(identifier)) continue;
|
|
444
|
+
undeclaredVars.add(identifier);
|
|
445
|
+
}
|
|
446
|
+
return Array.from(undeclaredVars);
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Checks if a word is a JavaScript keyword
|
|
450
|
+
*/
|
|
451
|
+
function isJavaScriptKeyword(word) {
|
|
452
|
+
return [
|
|
453
|
+
"break",
|
|
454
|
+
"case",
|
|
455
|
+
"catch",
|
|
456
|
+
"class",
|
|
457
|
+
"const",
|
|
458
|
+
"continue",
|
|
459
|
+
"debugger",
|
|
460
|
+
"default",
|
|
461
|
+
"delete",
|
|
462
|
+
"do",
|
|
463
|
+
"else",
|
|
464
|
+
"export",
|
|
465
|
+
"extends",
|
|
466
|
+
"finally",
|
|
467
|
+
"for",
|
|
468
|
+
"function",
|
|
469
|
+
"if",
|
|
470
|
+
"import",
|
|
471
|
+
"in",
|
|
472
|
+
"instanceof",
|
|
473
|
+
"let",
|
|
474
|
+
"new",
|
|
475
|
+
"return",
|
|
476
|
+
"super",
|
|
477
|
+
"switch",
|
|
478
|
+
"this",
|
|
479
|
+
"throw",
|
|
480
|
+
"try",
|
|
481
|
+
"typeof",
|
|
482
|
+
"var",
|
|
483
|
+
"void",
|
|
484
|
+
"while",
|
|
485
|
+
"with",
|
|
486
|
+
"yield",
|
|
487
|
+
"async",
|
|
488
|
+
"await"
|
|
489
|
+
].includes(word);
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Checks if a word is a common JavaScript built-in
|
|
493
|
+
*/
|
|
494
|
+
function isCommonBuiltin(word) {
|
|
495
|
+
return [
|
|
496
|
+
"Array",
|
|
497
|
+
"Object",
|
|
498
|
+
"String",
|
|
499
|
+
"Number",
|
|
500
|
+
"Boolean",
|
|
501
|
+
"Date",
|
|
502
|
+
"Math",
|
|
503
|
+
"JSON",
|
|
504
|
+
"RegExp",
|
|
505
|
+
"Error",
|
|
506
|
+
"Map",
|
|
507
|
+
"Set",
|
|
508
|
+
"Promise",
|
|
509
|
+
"Symbol",
|
|
510
|
+
"undefined",
|
|
511
|
+
"null",
|
|
512
|
+
"true",
|
|
513
|
+
"false",
|
|
514
|
+
"console",
|
|
515
|
+
"parseInt",
|
|
516
|
+
"parseFloat",
|
|
517
|
+
"isNaN",
|
|
518
|
+
"isFinite",
|
|
519
|
+
"decodeURI",
|
|
520
|
+
"decodeURIComponent",
|
|
521
|
+
"encodeURI",
|
|
522
|
+
"encodeURIComponent"
|
|
523
|
+
].includes(word);
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Quick validation to check if code is safe for execution
|
|
527
|
+
* Returns true if code passes basic safety checks
|
|
528
|
+
*
|
|
529
|
+
* @param code - The code to check
|
|
530
|
+
* @returns true if code is safe, false otherwise
|
|
531
|
+
*
|
|
532
|
+
* @example
|
|
533
|
+
* ```typescript
|
|
534
|
+
* if (isSafeCode('return x + y')) {
|
|
535
|
+
* // Safe to execute
|
|
536
|
+
* }
|
|
537
|
+
* ```
|
|
538
|
+
*/
|
|
539
|
+
function isSafeCode(code) {
|
|
540
|
+
return validateCode(code, {
|
|
541
|
+
maxLength: 5e4,
|
|
542
|
+
checkSyntax: true
|
|
543
|
+
}).valid;
|
|
544
|
+
}
|
|
545
|
+
//#endregion
|
|
546
|
+
//#region src/shared/logger.ts
|
|
547
|
+
/**
|
|
548
|
+
* Console-based logger implementation
|
|
549
|
+
*
|
|
550
|
+
* Routes all log messages to the appropriate console methods with optional context.
|
|
551
|
+
*/
|
|
552
|
+
var ConsoleLogger = class {
|
|
553
|
+
debug(message, context) {
|
|
554
|
+
if (context) console.debug(message, context);
|
|
555
|
+
else console.debug(message);
|
|
556
|
+
}
|
|
557
|
+
info(message, context) {
|
|
558
|
+
if (context) console.info(message, context);
|
|
559
|
+
else console.info(message);
|
|
560
|
+
}
|
|
561
|
+
warn(message, context) {
|
|
562
|
+
if (context) console.warn(message, context);
|
|
563
|
+
else console.warn(message);
|
|
564
|
+
}
|
|
565
|
+
error(message, context) {
|
|
566
|
+
if (context) console.error(message, context);
|
|
567
|
+
else console.error(message);
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
/**
|
|
571
|
+
* No-operation logger that discards all log messages
|
|
572
|
+
*
|
|
573
|
+
* Useful for disabling logging in production or testing environments.
|
|
574
|
+
*/
|
|
575
|
+
var NoOpLogger = class {
|
|
576
|
+
debug() {}
|
|
577
|
+
info() {}
|
|
578
|
+
warn() {}
|
|
579
|
+
error() {}
|
|
580
|
+
};
|
|
581
|
+
/** Global logger instance used throughout the SDK */
|
|
582
|
+
var globalLogger = new ConsoleLogger();
|
|
583
|
+
/**
|
|
584
|
+
* Replace the global logger with a custom implementation
|
|
585
|
+
*
|
|
586
|
+
* @param logger - Custom logger implementation
|
|
587
|
+
* @example
|
|
588
|
+
* ```typescript
|
|
589
|
+
* setLogger(new CustomLogger());
|
|
590
|
+
* ```
|
|
591
|
+
*/
|
|
592
|
+
var setLogger = (logger) => {
|
|
593
|
+
globalLogger = logger;
|
|
594
|
+
};
|
|
595
|
+
/**
|
|
596
|
+
* Get the current global logger instance
|
|
597
|
+
*
|
|
598
|
+
* @returns Current logger implementation
|
|
599
|
+
* @example
|
|
600
|
+
* ```typescript
|
|
601
|
+
* const logger = getLogger();
|
|
602
|
+
* logger.info('Application started');
|
|
603
|
+
* ```
|
|
604
|
+
*/
|
|
605
|
+
var getLogger = () => {
|
|
606
|
+
return globalLogger;
|
|
607
|
+
};
|
|
608
|
+
/**
|
|
609
|
+
* Disable all logging by switching to no-op logger
|
|
610
|
+
*
|
|
611
|
+
* @example
|
|
612
|
+
* ```typescript
|
|
613
|
+
* if (process.env.NODE_ENV === 'production') {
|
|
614
|
+
* disableLogging();
|
|
615
|
+
* }
|
|
616
|
+
* ```
|
|
617
|
+
*/
|
|
618
|
+
var disableLogging = () => {
|
|
619
|
+
globalLogger = new NoOpLogger();
|
|
620
|
+
};
|
|
621
|
+
/**
|
|
622
|
+
* Enable console logging by switching to console logger
|
|
623
|
+
*
|
|
624
|
+
* @example
|
|
625
|
+
* ```typescript
|
|
626
|
+
* enableLogging(); // Re-enable after disabling
|
|
627
|
+
* ```
|
|
628
|
+
*/
|
|
629
|
+
var enableLogging = () => {
|
|
630
|
+
globalLogger = new ConsoleLogger();
|
|
631
|
+
};
|
|
632
|
+
//#endregion
|
|
633
|
+
//#region src/shared/types.ts
|
|
634
|
+
/**
|
|
635
|
+
* Shared type definitions and interfaces for universal use
|
|
636
|
+
*
|
|
637
|
+
* This module provides standardized error classes and logging interfaces
|
|
638
|
+
* used throughout the HAVE SDK. All error classes extend BaseError to
|
|
639
|
+
* provide consistent error handling with context and timestamps.
|
|
640
|
+
*/
|
|
641
|
+
/**
|
|
642
|
+
* Standardized error codes used across the HAVE SDK
|
|
643
|
+
*
|
|
644
|
+
* These codes provide consistent error categorization for better error handling
|
|
645
|
+
* and monitoring across all packages in the SDK.
|
|
646
|
+
*/
|
|
647
|
+
var ErrorCode = /* @__PURE__ */ function(ErrorCode) {
|
|
648
|
+
/** Input validation failed */
|
|
649
|
+
ErrorCode["VALIDATION_ERROR"] = "VALIDATION_ERROR";
|
|
650
|
+
/** API request/response error */
|
|
651
|
+
ErrorCode["API_ERROR"] = "API_ERROR";
|
|
652
|
+
/** File system operation error */
|
|
653
|
+
ErrorCode["FILE_ERROR"] = "FILE_ERROR";
|
|
654
|
+
/** Network connectivity or HTTP error */
|
|
655
|
+
ErrorCode["NETWORK_ERROR"] = "NETWORK_ERROR";
|
|
656
|
+
/** Database operation error */
|
|
657
|
+
ErrorCode["DATABASE_ERROR"] = "DATABASE_ERROR";
|
|
658
|
+
/** Data parsing or format error */
|
|
659
|
+
ErrorCode["PARSING_ERROR"] = "PARSING_ERROR";
|
|
660
|
+
/** Operation timeout error */
|
|
661
|
+
ErrorCode["TIMEOUT_ERROR"] = "TIMEOUT_ERROR";
|
|
662
|
+
/** Unspecified or unexpected error */
|
|
663
|
+
ErrorCode["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
|
|
664
|
+
return ErrorCode;
|
|
665
|
+
}({});
|
|
666
|
+
/**
|
|
667
|
+
* Base error class providing standardized error handling across the HAVE SDK
|
|
668
|
+
*
|
|
669
|
+
* All custom errors extend this class to ensure consistent error structure,
|
|
670
|
+
* context preservation, and debugging capabilities.
|
|
671
|
+
*
|
|
672
|
+
* @example
|
|
673
|
+
* ```typescript
|
|
674
|
+
* throw new BaseError('Something went wrong', ErrorCode.UNKNOWN_ERROR, {
|
|
675
|
+
* userId: 123,
|
|
676
|
+
* operation: 'processData'
|
|
677
|
+
* });
|
|
678
|
+
* ```
|
|
679
|
+
*/
|
|
680
|
+
var BaseError = class extends Error {
|
|
681
|
+
/** Error classification code */
|
|
682
|
+
code;
|
|
683
|
+
/** Additional context data for debugging */
|
|
684
|
+
context;
|
|
685
|
+
/** When the error occurred */
|
|
686
|
+
timestamp;
|
|
687
|
+
constructor(message, code = "UNKNOWN_ERROR", context) {
|
|
688
|
+
super(message);
|
|
689
|
+
this.name = this.constructor.name;
|
|
690
|
+
this.code = code;
|
|
691
|
+
this.context = context;
|
|
692
|
+
this.timestamp = /* @__PURE__ */ new Date();
|
|
693
|
+
Error.captureStackTrace?.(this, this.constructor);
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Serializes the error to a JSON-compatible object
|
|
697
|
+
* @returns Object containing all error properties
|
|
698
|
+
*/
|
|
699
|
+
toJSON() {
|
|
700
|
+
return {
|
|
701
|
+
name: this.name,
|
|
702
|
+
message: this.message,
|
|
703
|
+
code: this.code,
|
|
704
|
+
context: this.context,
|
|
705
|
+
timestamp: this.timestamp.toISOString(),
|
|
706
|
+
stack: this.stack
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
/**
|
|
711
|
+
* Error thrown when input validation fails
|
|
712
|
+
*
|
|
713
|
+
* @example
|
|
714
|
+
* ```typescript
|
|
715
|
+
* throw new ValidationError('Email format invalid', {
|
|
716
|
+
* email: 'invalid-email',
|
|
717
|
+
* field: 'userEmail'
|
|
718
|
+
* });
|
|
719
|
+
* ```
|
|
720
|
+
*/
|
|
721
|
+
var ValidationError = class extends BaseError {
|
|
722
|
+
constructor(message, context) {
|
|
723
|
+
super(message, "VALIDATION_ERROR", context);
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
/**
|
|
727
|
+
* Error thrown for API-related failures
|
|
728
|
+
*
|
|
729
|
+
* @example
|
|
730
|
+
* ```typescript
|
|
731
|
+
* throw new ApiError('HTTP 404 Not Found', {
|
|
732
|
+
* url: 'https://api.example.com/users/123',
|
|
733
|
+
* status: 404
|
|
734
|
+
* });
|
|
735
|
+
* ```
|
|
736
|
+
*/
|
|
737
|
+
var ApiError = class extends BaseError {
|
|
738
|
+
constructor(message, context) {
|
|
739
|
+
super(message, "API_ERROR", context);
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
/**
|
|
743
|
+
* Error thrown for file system operation failures
|
|
744
|
+
*
|
|
745
|
+
* @example
|
|
746
|
+
* ```typescript
|
|
747
|
+
* throw new FileError('File not found', {
|
|
748
|
+
* path: '/path/to/missing/file.txt',
|
|
749
|
+
* operation: 'read'
|
|
750
|
+
* });
|
|
751
|
+
* ```
|
|
752
|
+
*/
|
|
753
|
+
var FileError = class extends BaseError {
|
|
754
|
+
constructor(message, context) {
|
|
755
|
+
super(message, "FILE_ERROR", context);
|
|
756
|
+
}
|
|
757
|
+
};
|
|
758
|
+
/**
|
|
759
|
+
* Error thrown for network connectivity or HTTP failures
|
|
760
|
+
*
|
|
761
|
+
* @example
|
|
762
|
+
* ```typescript
|
|
763
|
+
* throw new NetworkError('Connection timeout', {
|
|
764
|
+
* host: 'example.com',
|
|
765
|
+
* timeout: 5000,
|
|
766
|
+
* attempt: 3
|
|
767
|
+
* });
|
|
768
|
+
* ```
|
|
769
|
+
*/
|
|
770
|
+
var NetworkError = class extends BaseError {
|
|
771
|
+
constructor(message, context) {
|
|
772
|
+
super(message, "NETWORK_ERROR", context);
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
/**
|
|
776
|
+
* Error thrown for database operation failures
|
|
777
|
+
*
|
|
778
|
+
* @example
|
|
779
|
+
* ```typescript
|
|
780
|
+
* throw new DatabaseError('Connection failed', {
|
|
781
|
+
* database: 'production',
|
|
782
|
+
* query: 'SELECT * FROM users',
|
|
783
|
+
* errorCode: 'ECONNREFUSED'
|
|
784
|
+
* });
|
|
785
|
+
* ```
|
|
786
|
+
*/
|
|
787
|
+
var DatabaseError = class extends BaseError {
|
|
788
|
+
constructor(message, context) {
|
|
789
|
+
super(message, "DATABASE_ERROR", context);
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
/**
|
|
793
|
+
* Error thrown for data parsing or format failures
|
|
794
|
+
*
|
|
795
|
+
* @example
|
|
796
|
+
* ```typescript
|
|
797
|
+
* throw new ParsingError('Invalid JSON format', {
|
|
798
|
+
* input: '{invalid json}',
|
|
799
|
+
* parser: 'JSON.parse',
|
|
800
|
+
* position: 1
|
|
801
|
+
* });
|
|
802
|
+
* ```
|
|
803
|
+
*/
|
|
804
|
+
var ParsingError = class extends BaseError {
|
|
805
|
+
constructor(message, context) {
|
|
806
|
+
super(message, "PARSING_ERROR", context);
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
/**
|
|
810
|
+
* Error thrown when operations exceed their timeout duration
|
|
811
|
+
*
|
|
812
|
+
* @example
|
|
813
|
+
* ```typescript
|
|
814
|
+
* throw new TimeoutError('Operation timed out', {
|
|
815
|
+
* timeout: 5000,
|
|
816
|
+
* operation: 'fetchData',
|
|
817
|
+
* elapsedTime: 5234
|
|
818
|
+
* });
|
|
819
|
+
* ```
|
|
820
|
+
*/
|
|
821
|
+
var TimeoutError = class extends BaseError {
|
|
822
|
+
constructor(message, context) {
|
|
823
|
+
super(message, "TIMEOUT_ERROR", context);
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
//#endregion
|
|
827
|
+
//#region src/shared/universal.ts
|
|
828
|
+
/**
|
|
829
|
+
* Universal utilities that work in both browser and Node.js environments
|
|
830
|
+
*/
|
|
831
|
+
/**
|
|
832
|
+
* Generates a unique identifier using CUID2 (preferred) or UUID fallback
|
|
833
|
+
*
|
|
834
|
+
* CUID2 is more secure and collision-resistant than UUIDs, but UUID is provided
|
|
835
|
+
* as a fallback for RFC4122 compliance requirements.
|
|
836
|
+
*
|
|
837
|
+
* @param type - ID type: 'cuid2' (default) or 'uuid'
|
|
838
|
+
* @returns A unique identifier string
|
|
839
|
+
* @example
|
|
840
|
+
* ```typescript
|
|
841
|
+
* const id = makeId(); // CUID2: "ckx5f8h3z0000qzrmn831i7rn"
|
|
842
|
+
* const uuid = makeId('uuid'); // UUID: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
|
|
843
|
+
* ```
|
|
844
|
+
*/
|
|
845
|
+
var makeId = (type = "cuid2") => {
|
|
846
|
+
if (type === "cuid2") return createId();
|
|
847
|
+
if (crypto?.randomUUID) return crypto.randomUUID();
|
|
848
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
849
|
+
const r = Math.random() * 16 | 0;
|
|
850
|
+
return (c === "x" ? r : r & 3 | 8).toString(16);
|
|
851
|
+
});
|
|
852
|
+
};
|
|
853
|
+
/**
|
|
854
|
+
* Generates a CUID2 identifier (collision-resistant, more secure than UUID)
|
|
855
|
+
*
|
|
856
|
+
* CUID2 provides better entropy and security compared to UUID v4, making it
|
|
857
|
+
* ideal for distributed systems and user-facing identifiers.
|
|
858
|
+
*
|
|
859
|
+
* @returns A CUID2 identifier string
|
|
860
|
+
* @example
|
|
861
|
+
* ```typescript
|
|
862
|
+
* const id = createId(); // "ckx5f8h3z0000qzrmn831i7rn"
|
|
863
|
+
* ```
|
|
864
|
+
*/
|
|
865
|
+
var createId$1 = createId;
|
|
866
|
+
/**
|
|
867
|
+
* Converts a string to a URL-friendly slug
|
|
868
|
+
*
|
|
869
|
+
* Handles international characters, removes special characters, and replaces
|
|
870
|
+
* spaces with hyphens. Ampersands are converted to "-38-" for uniqueness.
|
|
871
|
+
*
|
|
872
|
+
* @param str - The string to convert to a slug
|
|
873
|
+
* @returns A URL-friendly slug string
|
|
874
|
+
* @example
|
|
875
|
+
* ```typescript
|
|
876
|
+
* makeSlug("My Example Title & Co."); // "my-example-title-38-co"
|
|
877
|
+
* makeSlug("Café España"); // "cafe-espana"
|
|
878
|
+
* ```
|
|
879
|
+
*/
|
|
880
|
+
var makeSlug = (str) => {
|
|
881
|
+
const from = "àáâäæãåāăąçćčđďèéêëēėęěğǵḧîïíīįìıİłḿñńǹňôöòóœøōõőṕŕřßśšşșťțûüùúūǘůűųẃẍÿýžźż+·/_,:;";
|
|
882
|
+
const to = "aaaaaaaaaacccddeeeeeeeegghiiiiiiiilmnnnnoooooooooprrsssssttuuuuuuuuuwxyyzzz--------------";
|
|
883
|
+
const textToCompare = new RegExp(from.split("").join("|").replace(/\+/g, "\\+"), "g");
|
|
884
|
+
return str.toString().toLowerCase().replace("&", "-38-").replace(/\s+/g, "-").replace(textToCompare, (c) => to.charAt(from.indexOf(c))).replace(/[&.]/g, "-").replace(/[^\w-º+]+/g, "").replace(/--+/g, "-").replace(/^-+/, "").replace(/-+$/, "");
|
|
885
|
+
};
|
|
886
|
+
/**
|
|
887
|
+
* Extracts the filename from a URL's pathname
|
|
888
|
+
*
|
|
889
|
+
* Returns the last segment of the URL path. If no filename is found,
|
|
890
|
+
* defaults to 'index.html'.
|
|
891
|
+
*
|
|
892
|
+
* @param url - The URL to extract filename from
|
|
893
|
+
* @returns The filename from the URL
|
|
894
|
+
* @throws {TypeError} When URL is invalid
|
|
895
|
+
* @example
|
|
896
|
+
* ```typescript
|
|
897
|
+
* urlFilename("https://example.com/path/file.pdf"); // "file.pdf"
|
|
898
|
+
* urlFilename("https://example.com/path/"); // "index.html"
|
|
899
|
+
* ```
|
|
900
|
+
*/
|
|
901
|
+
var urlFilename = (url) => {
|
|
902
|
+
const pathSegments = new URL(url).pathname.split("/");
|
|
903
|
+
return pathSegments[pathSegments.length - 1] || "index.html";
|
|
904
|
+
};
|
|
905
|
+
/**
|
|
906
|
+
* Converts a URL to a file path by joining hostname and pathname
|
|
907
|
+
*
|
|
908
|
+
* Creates a file system compatible path from a URL by combining the hostname
|
|
909
|
+
* with the pathname segments, useful for creating local file structures.
|
|
910
|
+
*
|
|
911
|
+
* @param url - The URL to convert to a path
|
|
912
|
+
* @returns A file path string with hostname and path segments
|
|
913
|
+
* @throws {TypeError} When URL is invalid
|
|
914
|
+
* @example
|
|
915
|
+
* ```typescript
|
|
916
|
+
* urlPath("https://example.com/path/to/resource"); // "example.com/path/to/resource"
|
|
917
|
+
* ```
|
|
918
|
+
*/
|
|
919
|
+
var urlPath = (url) => {
|
|
920
|
+
const parsedUrl = new URL(url);
|
|
921
|
+
return [parsedUrl.hostname, ...parsedUrl.pathname.split("/").filter(Boolean)].join("/");
|
|
922
|
+
};
|
|
923
|
+
/**
|
|
924
|
+
* Creates a Promise that resolves after a specified duration
|
|
925
|
+
*
|
|
926
|
+
* Useful for adding delays in async functions or rate limiting operations.
|
|
927
|
+
*
|
|
928
|
+
* @param duration - Time to wait in milliseconds
|
|
929
|
+
* @returns Promise that resolves after the specified duration
|
|
930
|
+
* @example
|
|
931
|
+
* ```typescript
|
|
932
|
+
* await sleep(1000); // Wait 1 second
|
|
933
|
+
* console.log('1 second has passed');
|
|
934
|
+
* ```
|
|
935
|
+
*/
|
|
936
|
+
var sleep = (duration) => {
|
|
937
|
+
return new Promise((resolve) => {
|
|
938
|
+
setTimeout(resolve, duration);
|
|
939
|
+
});
|
|
940
|
+
};
|
|
941
|
+
/**
|
|
942
|
+
* Repeatedly calls a function until it returns a defined value or times out
|
|
943
|
+
*
|
|
944
|
+
* Polls an async function at regular intervals until it returns a defined value
|
|
945
|
+
* or the timeout is reached. Useful for waiting for conditions to be met.
|
|
946
|
+
*
|
|
947
|
+
* @param it - Async function to poll
|
|
948
|
+
* @param options - Configuration options
|
|
949
|
+
* @param options.timeout - Maximum time to wait in milliseconds (0 = no timeout)
|
|
950
|
+
* @param options.delay - Time between polling attempts in milliseconds
|
|
951
|
+
* @returns Promise that resolves with the function result or rejects on timeout
|
|
952
|
+
* @throws {TimeoutError} When timeout is reached before function returns defined value
|
|
953
|
+
* @example
|
|
954
|
+
* ```typescript
|
|
955
|
+
* // Wait for a file to exist
|
|
956
|
+
* const fileExists = await waitFor(
|
|
957
|
+
* async () => {
|
|
958
|
+
* try {
|
|
959
|
+
* await fs.access('/path/to/file');
|
|
960
|
+
* return true;
|
|
961
|
+
* } catch {
|
|
962
|
+
* return undefined; // Keep polling
|
|
963
|
+
* }
|
|
964
|
+
* },
|
|
965
|
+
* { timeout: 10000, delay: 500 }
|
|
966
|
+
* );
|
|
967
|
+
* ```
|
|
968
|
+
*/
|
|
969
|
+
function waitFor(it, { timeout = 0, delay = 1e3 } = {}) {
|
|
970
|
+
return new Promise((resolve, reject) => {
|
|
971
|
+
const beginTime = Date.now();
|
|
972
|
+
(async function waitATick() {
|
|
973
|
+
try {
|
|
974
|
+
const result = await it();
|
|
975
|
+
if (typeof result !== "undefined") return resolve(result);
|
|
976
|
+
if (timeout > 0) {
|
|
977
|
+
if (Date.now() > beginTime + timeout) return reject(new TimeoutError("Function call timed out", {
|
|
978
|
+
timeout,
|
|
979
|
+
delay,
|
|
980
|
+
elapsedTime: Date.now() - beginTime
|
|
981
|
+
}));
|
|
982
|
+
}
|
|
983
|
+
setTimeout(waitATick, delay);
|
|
984
|
+
} catch (error) {
|
|
985
|
+
reject(error);
|
|
986
|
+
}
|
|
987
|
+
})();
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* Type guard to check if a value is an array
|
|
992
|
+
*
|
|
993
|
+
* @param obj - Value to check
|
|
994
|
+
* @returns True if value is an array, with TypeScript type narrowing
|
|
995
|
+
* @example
|
|
996
|
+
* ```typescript
|
|
997
|
+
* if (isArray(data)) {
|
|
998
|
+
* // TypeScript knows data is unknown[]
|
|
999
|
+
* data.forEach(item => console.log(item));
|
|
1000
|
+
* }
|
|
1001
|
+
* ```
|
|
1002
|
+
*/
|
|
1003
|
+
var isArray = (obj) => {
|
|
1004
|
+
return Array.isArray(obj);
|
|
1005
|
+
};
|
|
1006
|
+
/**
|
|
1007
|
+
* Type guard to check if a value is a plain object
|
|
1008
|
+
*
|
|
1009
|
+
* Checks for objects that are not null, arrays, or other non-plain object types.
|
|
1010
|
+
*
|
|
1011
|
+
* @param obj - Value to check
|
|
1012
|
+
* @returns True if value is a plain object, with TypeScript type narrowing
|
|
1013
|
+
* @example
|
|
1014
|
+
* ```typescript
|
|
1015
|
+
* if (isPlainObject(data)) {
|
|
1016
|
+
* // TypeScript knows data is Record<string, unknown>
|
|
1017
|
+
* Object.keys(data).forEach(key => console.log(key, data[key]));
|
|
1018
|
+
* }
|
|
1019
|
+
* ```
|
|
1020
|
+
*/
|
|
1021
|
+
var isPlainObject = (obj) => {
|
|
1022
|
+
return typeof obj === "object" && obj !== null && !Array.isArray(obj);
|
|
1023
|
+
};
|
|
1024
|
+
/**
|
|
1025
|
+
* Checks if a string is a valid URL
|
|
1026
|
+
*
|
|
1027
|
+
* Uses the URL constructor to validate the string format. Returns false
|
|
1028
|
+
* for any string that cannot be parsed as a valid URL.
|
|
1029
|
+
*
|
|
1030
|
+
* @param url - String to validate as URL
|
|
1031
|
+
* @returns True if string is a valid URL
|
|
1032
|
+
* @example
|
|
1033
|
+
* ```typescript
|
|
1034
|
+
* isUrl("https://example.com"); // true
|
|
1035
|
+
* isUrl("not-a-url"); // false
|
|
1036
|
+
* ```
|
|
1037
|
+
*/
|
|
1038
|
+
var isUrl = (url) => {
|
|
1039
|
+
try {
|
|
1040
|
+
new URL(url);
|
|
1041
|
+
return true;
|
|
1042
|
+
} catch {
|
|
1043
|
+
return false;
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
/**
|
|
1047
|
+
* Converts a string to camelCase
|
|
1048
|
+
*
|
|
1049
|
+
* Handles kebab-case, snake_case, and space-separated strings.
|
|
1050
|
+
* Removes special characters and ensures proper camelCase formatting.
|
|
1051
|
+
*
|
|
1052
|
+
* @param str - String to convert
|
|
1053
|
+
* @returns camelCase formatted string
|
|
1054
|
+
* @example
|
|
1055
|
+
* ```typescript
|
|
1056
|
+
* camelCase("hello-world"); // "helloWorld"
|
|
1057
|
+
* camelCase("snake_case_string"); // "snakeCaseString"
|
|
1058
|
+
* camelCase("Some Title"); // "someTitle"
|
|
1059
|
+
* ```
|
|
1060
|
+
*/
|
|
1061
|
+
var camelCase = (str) => {
|
|
1062
|
+
return str.toLowerCase().replace(/[-_]+/g, " ").replace(/[^\w\s]/g, "").replace(/\s(.)/g, (_, char) => char.toUpperCase()).replace(/\s/g, "").replace(/^(.)/, (_, char) => char.toLowerCase());
|
|
1063
|
+
};
|
|
1064
|
+
/**
|
|
1065
|
+
* Converts a string to snake_case
|
|
1066
|
+
*
|
|
1067
|
+
* Handles camelCase, kebab-case, and space-separated strings.
|
|
1068
|
+
* Converts to lowercase with underscore separators.
|
|
1069
|
+
*
|
|
1070
|
+
* @param str - String to convert
|
|
1071
|
+
* @returns snake_case formatted string
|
|
1072
|
+
* @example
|
|
1073
|
+
* ```typescript
|
|
1074
|
+
* snakeCase("helloWorld"); // "hello_world"
|
|
1075
|
+
* snakeCase("kebab-case-string"); // "kebab_case_string"
|
|
1076
|
+
* snakeCase("Some Title"); // "some_title"
|
|
1077
|
+
* ```
|
|
1078
|
+
*/
|
|
1079
|
+
var snakeCase = (str) => {
|
|
1080
|
+
return str.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "").replace(/[-\s]+/g, "_");
|
|
1081
|
+
};
|
|
1082
|
+
/**
|
|
1083
|
+
* Recursively converts all object keys to camelCase
|
|
1084
|
+
*
|
|
1085
|
+
* Deeply traverses an object or array and converts all keys to camelCase.
|
|
1086
|
+
* Preserves the structure and values, only transforming key names.
|
|
1087
|
+
*
|
|
1088
|
+
* @param obj - Object or array to transform
|
|
1089
|
+
* @returns New object/array with camelCase keys
|
|
1090
|
+
* @example
|
|
1091
|
+
* ```typescript
|
|
1092
|
+
* keysToCamel({
|
|
1093
|
+
* user_name: "john",
|
|
1094
|
+
* user_details: { first_name: "John" }
|
|
1095
|
+
* }); // { userName: "john", userDetails: { firstName: "John" } }
|
|
1096
|
+
* ```
|
|
1097
|
+
*/
|
|
1098
|
+
var keysToCamel = (obj) => {
|
|
1099
|
+
if (isPlainObject(obj)) {
|
|
1100
|
+
const n = {};
|
|
1101
|
+
Object.keys(obj).forEach((k) => {
|
|
1102
|
+
n[camelCase(k)] = keysToCamel(obj[k]);
|
|
1103
|
+
});
|
|
1104
|
+
return n;
|
|
1105
|
+
}
|
|
1106
|
+
if (isArray(obj)) return obj.map((i) => keysToCamel(i));
|
|
1107
|
+
return obj;
|
|
1108
|
+
};
|
|
1109
|
+
/**
|
|
1110
|
+
* Recursively converts all object keys to snake_case
|
|
1111
|
+
*
|
|
1112
|
+
* Deeply traverses an object or array and converts all keys to snake_case.
|
|
1113
|
+
* Preserves the structure and values, only transforming key names.
|
|
1114
|
+
*
|
|
1115
|
+
* @param obj - Object or array to transform
|
|
1116
|
+
* @returns New object/array with snake_case keys
|
|
1117
|
+
* @example
|
|
1118
|
+
* ```typescript
|
|
1119
|
+
* keysToSnake({
|
|
1120
|
+
* userName: "john",
|
|
1121
|
+
* userDetails: { firstName: "John" }
|
|
1122
|
+
* }); // { user_name: "john", user_details: { first_name: "John" } }
|
|
1123
|
+
* ```
|
|
1124
|
+
*/
|
|
1125
|
+
var keysToSnake = (obj) => {
|
|
1126
|
+
if (isPlainObject(obj)) {
|
|
1127
|
+
const n = {};
|
|
1128
|
+
Object.keys(obj).forEach((k) => {
|
|
1129
|
+
n[snakeCase(k)] = keysToSnake(obj[k]);
|
|
1130
|
+
});
|
|
1131
|
+
return n;
|
|
1132
|
+
}
|
|
1133
|
+
if (isArray(obj)) return obj.map((i) => keysToSnake(i));
|
|
1134
|
+
return obj;
|
|
1135
|
+
};
|
|
1136
|
+
/**
|
|
1137
|
+
* Converts a domain string to camelCase
|
|
1138
|
+
*
|
|
1139
|
+
* Convenience function that applies camelCase conversion to domain strings.
|
|
1140
|
+
* Useful for converting API service names or domain identifiers.
|
|
1141
|
+
*
|
|
1142
|
+
* @param domain - Domain string to convert
|
|
1143
|
+
* @returns camelCase formatted domain string
|
|
1144
|
+
* @example
|
|
1145
|
+
* ```typescript
|
|
1146
|
+
* domainToCamel("api-service"); // "apiService"
|
|
1147
|
+
* domainToCamel("user_management"); // "userManagement"
|
|
1148
|
+
* ```
|
|
1149
|
+
*/
|
|
1150
|
+
var domainToCamel = (domain) => camelCase(domain);
|
|
1151
|
+
/**
|
|
1152
|
+
* Creates a visual progress indicator by cycling through a sequence of characters
|
|
1153
|
+
*
|
|
1154
|
+
* Useful for creating animated progress indicators in CLI applications.
|
|
1155
|
+
* Cycles through provided characters or defaults to dot sequences.
|
|
1156
|
+
*
|
|
1157
|
+
* @param tick - Current tick state (null to start)
|
|
1158
|
+
* @param options - Configuration options
|
|
1159
|
+
* @param options.chars - Array of characters to cycle through
|
|
1160
|
+
* @returns Next character in the sequence
|
|
1161
|
+
* @example
|
|
1162
|
+
* ```typescript
|
|
1163
|
+
* let tick = null;
|
|
1164
|
+
* setInterval(() => {
|
|
1165
|
+
* tick = logTicker(tick);
|
|
1166
|
+
* process.stdout.write(`\rProcessing ${tick}`);
|
|
1167
|
+
* }, 500);
|
|
1168
|
+
* // Outputs: "Processing ." → "Processing .." → "Processing ..."
|
|
1169
|
+
* ```
|
|
1170
|
+
*/
|
|
1171
|
+
var logTicker = (tick, options = {}) => {
|
|
1172
|
+
const { chars = [
|
|
1173
|
+
".",
|
|
1174
|
+
"..",
|
|
1175
|
+
"..."
|
|
1176
|
+
] } = options;
|
|
1177
|
+
if (tick) {
|
|
1178
|
+
const index = chars.indexOf(tick);
|
|
1179
|
+
return index + 1 >= chars.length ? chars[0] : chars[index + 1];
|
|
1180
|
+
}
|
|
1181
|
+
return chars[0];
|
|
1182
|
+
};
|
|
1183
|
+
/**
|
|
1184
|
+
* Parses an Amazon date string format (YYYYMMDDTHHMMSSZ) to a Date object
|
|
1185
|
+
*
|
|
1186
|
+
* Specifically handles the compact date format used by Amazon AWS services.
|
|
1187
|
+
* Validates the format and throws detailed errors for invalid inputs.
|
|
1188
|
+
*
|
|
1189
|
+
* @param dateStr - Amazon date string in format YYYYMMDDTHHMMSSZ
|
|
1190
|
+
* @returns Parsed Date object
|
|
1191
|
+
* @throws {ParsingError} When date string format is invalid
|
|
1192
|
+
* @example
|
|
1193
|
+
* ```typescript
|
|
1194
|
+
* parseAmazonDateString('20220223T215409Z');
|
|
1195
|
+
* // Returns: Date object for February 23, 2022, 21:54:09 UTC
|
|
1196
|
+
* ```
|
|
1197
|
+
*/
|
|
1198
|
+
var parseAmazonDateString = (dateStr) => {
|
|
1199
|
+
const match = dateStr.match(/^([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2})([0-9]{2})([0-9]{2})([A-Z0-9]+)/);
|
|
1200
|
+
if (!match) throw new ParsingError("Could not parse Amazon date string", {
|
|
1201
|
+
dateString: dateStr,
|
|
1202
|
+
expectedFormat: "YYYYMMDDTHHMMSSZ"
|
|
1203
|
+
});
|
|
1204
|
+
const [matched, year, month, day, hour, minutes, seconds, timezone] = match;
|
|
1205
|
+
if (matched !== dateStr) throw new ParsingError("Invalid Amazon date string format", {
|
|
1206
|
+
dateString: dateStr,
|
|
1207
|
+
matched,
|
|
1208
|
+
expectedFormat: "YYYYMMDDTHHMMSSZ"
|
|
1209
|
+
});
|
|
1210
|
+
return /* @__PURE__ */ new Date(`${year}-${month}-${day}T${hour}:${minutes}:${seconds}${timezone}`);
|
|
1211
|
+
};
|
|
1212
|
+
/**
|
|
1213
|
+
* Extracts and parses a date from a string
|
|
1214
|
+
*
|
|
1215
|
+
* Intelligently extracts dates from filenames or text strings by looking for
|
|
1216
|
+
* common date patterns. Supports multiple formats including:
|
|
1217
|
+
* - ISO dates (2023-01-15, 2023/01/15)
|
|
1218
|
+
* - US dates (01/15/2023, 01-15-2023)
|
|
1219
|
+
* - Natural language (January 15, 2023, Oct 14 2025)
|
|
1220
|
+
* - Filenames with dates (Report_January_15_2023.pdf)
|
|
1221
|
+
*
|
|
1222
|
+
* @param str - String containing date information (filename, title, or text)
|
|
1223
|
+
* @returns Parsed Date object or null if no valid date found
|
|
1224
|
+
* @example
|
|
1225
|
+
* ```typescript
|
|
1226
|
+
* dateInString("Report_January_15_2023.pdf"); // Date(2023, 0, 15)
|
|
1227
|
+
* dateInString("Regular Council Meeting October 14, 2025"); // Date(2025, 9, 14)
|
|
1228
|
+
* dateInString("financial-report-dec-2023.pdf"); // Date(2023, 11, 1)
|
|
1229
|
+
* dateInString("2023-01-15"); // Date(2023, 0, 15)
|
|
1230
|
+
* dateInString("no-date-here.pdf"); // null
|
|
1231
|
+
* ```
|
|
1232
|
+
*/
|
|
1233
|
+
var dateInString = (str) => {
|
|
1234
|
+
const cleanStr = str.toLowerCase();
|
|
1235
|
+
const underscoreMatch = str.match(/(\d{4})_(\d{1,2})_(\d{1,2})/);
|
|
1236
|
+
if (underscoreMatch) {
|
|
1237
|
+
const [, year, month, day] = underscoreMatch;
|
|
1238
|
+
const date = new Date(Number.parseInt(year, 10), Number.parseInt(month, 10) - 1, Number.parseInt(day, 10));
|
|
1239
|
+
if (!Number.isNaN(date.getTime())) return date;
|
|
1240
|
+
}
|
|
1241
|
+
const dotMatch = str.match(/(\d{1,2})\.(\d{1,2})\.(\d{4})/);
|
|
1242
|
+
if (dotMatch) {
|
|
1243
|
+
const [, month, day, year] = dotMatch;
|
|
1244
|
+
const date = new Date(Number.parseInt(year, 10), Number.parseInt(month, 10) - 1, Number.parseInt(day, 10));
|
|
1245
|
+
if (!Number.isNaN(date.getTime())) return date;
|
|
1246
|
+
}
|
|
1247
|
+
const isoMatch = cleanStr.match(/(\d{4})[-/](\d{1,2})[-/](\d{1,2})/);
|
|
1248
|
+
if (isoMatch) {
|
|
1249
|
+
const [, year, month, day] = isoMatch;
|
|
1250
|
+
const date = new Date(Number.parseInt(year, 10), Number.parseInt(month, 10) - 1, Number.parseInt(day, 10));
|
|
1251
|
+
if (!Number.isNaN(date.getTime())) return date;
|
|
1252
|
+
}
|
|
1253
|
+
const usMatch = cleanStr.match(/(\d{1,2})[-/](\d{1,2})[-/](\d{4})/);
|
|
1254
|
+
if (usMatch) {
|
|
1255
|
+
const [, month, day, year] = usMatch;
|
|
1256
|
+
const date = new Date(Number.parseInt(year, 10), Number.parseInt(month, 10) - 1, Number.parseInt(day, 10));
|
|
1257
|
+
if (!Number.isNaN(date.getTime())) return date;
|
|
1258
|
+
}
|
|
1259
|
+
const yearMatch = cleanStr.match(/20\d{2}/);
|
|
1260
|
+
if (!yearMatch) return null;
|
|
1261
|
+
const year = Number.parseInt(yearMatch[0], 10);
|
|
1262
|
+
const monthPatterns = {
|
|
1263
|
+
january: 1,
|
|
1264
|
+
jan: 1,
|
|
1265
|
+
february: 2,
|
|
1266
|
+
feb: 2,
|
|
1267
|
+
march: 3,
|
|
1268
|
+
mar: 3,
|
|
1269
|
+
april: 4,
|
|
1270
|
+
apr: 4,
|
|
1271
|
+
may: 5,
|
|
1272
|
+
june: 6,
|
|
1273
|
+
jun: 6,
|
|
1274
|
+
july: 7,
|
|
1275
|
+
jul: 7,
|
|
1276
|
+
august: 8,
|
|
1277
|
+
aug: 8,
|
|
1278
|
+
september: 9,
|
|
1279
|
+
sep: 9,
|
|
1280
|
+
sept: 9,
|
|
1281
|
+
october: 10,
|
|
1282
|
+
oct: 10,
|
|
1283
|
+
november: 11,
|
|
1284
|
+
nov: 11,
|
|
1285
|
+
december: 12,
|
|
1286
|
+
dec: 12
|
|
1287
|
+
};
|
|
1288
|
+
let foundMonth = null;
|
|
1289
|
+
let monthStart = -1;
|
|
1290
|
+
let monthName = "";
|
|
1291
|
+
for (const [name, monthNum] of Object.entries(monthPatterns)) {
|
|
1292
|
+
const monthIndex = cleanStr.indexOf(name);
|
|
1293
|
+
if (monthIndex !== -1) {
|
|
1294
|
+
foundMonth = monthNum;
|
|
1295
|
+
monthStart = monthIndex;
|
|
1296
|
+
monthName = name;
|
|
1297
|
+
break;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
if (!foundMonth) return null;
|
|
1301
|
+
const yearIndex = cleanStr.indexOf(yearMatch[0]);
|
|
1302
|
+
const beforeMonth = cleanStr.substring(Math.max(0, monthStart - 15), monthStart);
|
|
1303
|
+
const afterMonth = cleanStr.substring(monthStart + monthName.length, Math.min(cleanStr.length, monthStart + monthName.length + 15));
|
|
1304
|
+
const beforeYear = cleanStr.substring(Math.max(0, yearIndex - 15), yearIndex);
|
|
1305
|
+
const afterYear = cleanStr.substring(yearIndex + 4, Math.min(cleanStr.length, yearIndex + 19));
|
|
1306
|
+
const dayMatch = beforeMonth.match(/(?<!\d)(\d{1,2})\s*$/) || afterMonth.match(/^\s*(\d{1,2})(?!\d)/) || beforeYear.match(/(?<!\d)(\d{1,2})\s*$/) || afterYear.match(/^\s*(\d{1,2})(?!\d)/) || afterMonth.match(/[^\d](\d{1,2})(?!\d)/);
|
|
1307
|
+
const day = dayMatch ? Number.parseInt(dayMatch[1], 10) : 1;
|
|
1308
|
+
if (day < 1 || day > 31) return null;
|
|
1309
|
+
const date = new Date(year, foundMonth - 1, day);
|
|
1310
|
+
return !Number.isNaN(date.getTime()) ? date : null;
|
|
1311
|
+
};
|
|
1312
|
+
/**
|
|
1313
|
+
* Formats a date string into a human-readable format using the system locale
|
|
1314
|
+
*
|
|
1315
|
+
* Uses the Intl.DateTimeFormat API to create localized, human-readable date strings.
|
|
1316
|
+
* Automatically adapts to the user's system locale settings.
|
|
1317
|
+
*
|
|
1318
|
+
* @param dateString - ISO date string or any valid date string
|
|
1319
|
+
* @returns Human-readable date string in system locale
|
|
1320
|
+
* @example
|
|
1321
|
+
* ```typescript
|
|
1322
|
+
* prettyDate("2023-01-15T12:00:00Z"); // "January 15, 2023" (in English locale)
|
|
1323
|
+
* ```
|
|
1324
|
+
*/
|
|
1325
|
+
var prettyDate = (dateString) => {
|
|
1326
|
+
const date = new Date(dateString);
|
|
1327
|
+
return new Intl.DateTimeFormat(void 0, {
|
|
1328
|
+
year: "numeric",
|
|
1329
|
+
month: "long",
|
|
1330
|
+
day: "numeric"
|
|
1331
|
+
}).format(date);
|
|
1332
|
+
};
|
|
1333
|
+
/**
|
|
1334
|
+
* String pluralization utilities using the pluralize library
|
|
1335
|
+
*
|
|
1336
|
+
* Re-exports the pluralize library function for word pluralization.
|
|
1337
|
+
* Handles English pluralization rules including irregular forms.
|
|
1338
|
+
*
|
|
1339
|
+
* @example
|
|
1340
|
+
* ```typescript
|
|
1341
|
+
* pluralizeWord("cat"); // "cats"
|
|
1342
|
+
* pluralizeWord("mouse"); // "mice"
|
|
1343
|
+
* singularize("cats"); // "cat"
|
|
1344
|
+
* isPlural("cats"); // true
|
|
1345
|
+
* isSingular("cat"); // true
|
|
1346
|
+
* ```
|
|
1347
|
+
*/
|
|
1348
|
+
var pluralizeWord = pluralize;
|
|
1349
|
+
var singularize = pluralize.singular;
|
|
1350
|
+
var isPlural = pluralize.isPlural;
|
|
1351
|
+
var isSingular = pluralize.isSingular;
|
|
1352
|
+
/**
|
|
1353
|
+
* Enhanced date utilities using date-fns library
|
|
1354
|
+
*
|
|
1355
|
+
* Re-exports commonly used date-fns functions for date manipulation and formatting.
|
|
1356
|
+
* Provides more reliable date handling than native Date methods.
|
|
1357
|
+
*
|
|
1358
|
+
* @param date - Date object or ISO string to format
|
|
1359
|
+
* @param formatStr - Format string (defaults to 'yyyy-MM-dd')
|
|
1360
|
+
* @returns Formatted date string
|
|
1361
|
+
* @example
|
|
1362
|
+
* ```typescript
|
|
1363
|
+
* formatDate(new Date(), 'yyyy-MM-dd'); // "2023-01-15"
|
|
1364
|
+
* formatDate(new Date(), 'MM/dd/yyyy'); // "01/15/2023"
|
|
1365
|
+
* parseDate('2023-01-15'); // Date object
|
|
1366
|
+
* isValidDate(new Date()); // true
|
|
1367
|
+
* addInterval(new Date(), { days: 7 }); // Date 7 days from now
|
|
1368
|
+
* ```
|
|
1369
|
+
*/
|
|
1370
|
+
var formatDate = (date, formatStr = "yyyy-MM-dd") => {
|
|
1371
|
+
return format(typeof date === "string" ? new Date(date) : date, formatStr);
|
|
1372
|
+
};
|
|
1373
|
+
/**
|
|
1374
|
+
* Parses a date string into a Date object
|
|
1375
|
+
*
|
|
1376
|
+
* When a format string is provided, uses date-fns `parse` with that format.
|
|
1377
|
+
* Otherwise falls back to `parseISO` for ISO 8601 strings.
|
|
1378
|
+
*
|
|
1379
|
+
* @param dateStr - The date string to parse
|
|
1380
|
+
* @param formatStr - Optional format pattern (e.g., 'MM/dd/yyyy', 'dd-MMM-yyyy')
|
|
1381
|
+
* @returns Parsed Date object
|
|
1382
|
+
* @example
|
|
1383
|
+
* ```typescript
|
|
1384
|
+
* parseDate('2023-01-15'); // ISO parse
|
|
1385
|
+
* parseDate('01/15/2023', 'MM/dd/yyyy'); // Custom format parse
|
|
1386
|
+
* ```
|
|
1387
|
+
*/
|
|
1388
|
+
var parseDate = (dateStr, formatStr) => {
|
|
1389
|
+
if (formatStr) return parse(dateStr, formatStr, /* @__PURE__ */ new Date());
|
|
1390
|
+
return parseISO(dateStr);
|
|
1391
|
+
};
|
|
1392
|
+
var isValidDate = isValid;
|
|
1393
|
+
var addInterval = add;
|
|
1394
|
+
/**
|
|
1395
|
+
* Gets a temporary directory path (cross-platform)
|
|
1396
|
+
*
|
|
1397
|
+
* Creates a platform-appropriate temporary directory path under the .have-sdk namespace.
|
|
1398
|
+
* Uses environment variables in Node.js or falls back to /tmp.
|
|
1399
|
+
*
|
|
1400
|
+
* @param subfolder - Optional subfolder name within the temp directory
|
|
1401
|
+
* @returns Cross-platform temporary directory path
|
|
1402
|
+
* @example
|
|
1403
|
+
* ```typescript
|
|
1404
|
+
* getTempDirectory(); // "/tmp/.have-sdk" (Unix) or equivalent
|
|
1405
|
+
* getTempDirectory("cache"); // "/tmp/.have-sdk/cache"
|
|
1406
|
+
* ```
|
|
1407
|
+
*/
|
|
1408
|
+
var getTempDirectory = (subfolder) => {
|
|
1409
|
+
const basePath = `${process?.env ? process.env.TMPDIR || process.env.TMP || process.env.TEMP || "/tmp" : "/tmp"}/.have-sdk`;
|
|
1410
|
+
return subfolder ? `${basePath}/${subfolder}` : basePath;
|
|
1411
|
+
};
|
|
1412
|
+
//#endregion
|
|
1413
|
+
export { ApiError as A, enableLogging as B, prettyDate as C, urlFilename as D, snakeCase as E, NetworkError as F, extractAllCodeBlocks as G, setLogger as H, ParsingError as I, extractJSON as J, extractCodeBlock as K, TimeoutError as L, DatabaseError as M, ErrorCode as N, urlPath as O, FileError as P, toScreamingSnakeCase as Q, ValidationError as R, pluralizeWord as S, sleep as T, isSafeCode as U, getLogger as V, validateCode as W, loadEnvConfig as X, convertType as Y, toCamelCase as Z, logTicker as _, domainToCamel as a, parseAmazonDateString as b, isArray as c, isPlural as d, isSingular as f, keysToSnake as g, keysToCamel as h, dateInString as i, BaseError as j, waitFor as k, isCuid as l, isValidDate as m, camelCase as n, formatDate as o, isUrl as p, extractFunctionDefinition as q, createId$1 as r, getTempDirectory as s, addInterval as t, isPlainObject as u, makeId as v, singularize as w, parseDate as x, makeSlug as y, disableLogging as z };
|
|
1414
|
+
|
|
1415
|
+
//# sourceMappingURL=universal-DOIuUel_.js.map
|