@slbdn/mcp-tester 1.1.0 → 1.3.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.
@@ -1,8 +1,12 @@
1
1
  /**
2
2
  * Logging utilities for MCP client.
3
3
  *
4
- * Provides configurable logging with multiple output levels and formats.
4
+ * Provides configurable logging with multiple output levels, secret masking,
5
+ * request timing, and JSON pretty-printing for protocol debugging.
6
+ *
7
+ * All log output is automatically masked to prevent sensitive data leakage.
5
8
  */
9
+ import { maskSecrets } from './masking.js';
6
10
  const LOG_LEVELS = {
7
11
  debug: 0,
8
12
  info: 1,
@@ -11,27 +15,117 @@ const LOG_LEVELS = {
11
15
  none: 99,
12
16
  };
13
17
  /**
14
- * Logger implementation that outputs to stderr.
18
+ * ANSI color codes for terminal output.
19
+ * Only used when output is a TTY.
20
+ */
21
+ const COLORS = {
22
+ debug: '\x1b[36m', // cyan
23
+ info: '\x1b[32m', // green
24
+ warn: '\x1b[33m', // yellow
25
+ error: '\x1b[31m', // red
26
+ dim: '\x1b[2m', // dim
27
+ reset: '\x1b[0m', // reset
28
+ };
29
+ /**
30
+ * Check if stderr is a TTY (terminal).
31
+ */
32
+ function isTTY() {
33
+ return process.stderr?.isTTY ?? false;
34
+ }
35
+ /**
36
+ * Colorize a string for terminal output.
37
+ */
38
+ function colorize(text, color, useColors) {
39
+ if (!useColors || !isTTY())
40
+ return text;
41
+ return `${color}${text}${COLORS.reset}`;
42
+ }
43
+ /**
44
+ * Format a timestamp for log messages.
45
+ */
46
+ function formatTimestamp() {
47
+ const now = new Date();
48
+ const ms = String(now.getMilliseconds()).padStart(3, '0');
49
+ return `${now.toISOString().slice(11, 23)}.${ms}`;
50
+ }
51
+ /**
52
+ * Pretty-print a JSON-serializable value with indentation and masking.
53
+ */
54
+ export function prettyPrint(value, shouldMask = true) {
55
+ if (value === null || value === undefined)
56
+ return String(value);
57
+ if (typeof value === 'string') {
58
+ return shouldMask ? maskSecrets(value) : value;
59
+ }
60
+ try {
61
+ const json = JSON.stringify(value, null, 2);
62
+ return shouldMask ? maskSecrets(json) : json;
63
+ }
64
+ catch {
65
+ const str = String(value);
66
+ return shouldMask ? maskSecrets(str) : str;
67
+ }
68
+ }
69
+ /**
70
+ * Start a timer and return a function that returns elapsed milliseconds.
15
71
  *
16
72
  * @example
17
73
  * ```typescript
18
- * const logger = new ConsoleLogger({ level: 'debug', prefix: 'MyApp' });
19
- * logger.info('Starting...'); // [MyApp] INFO: Starting...
74
+ * const elapsed = startTimer();
75
+ * await someOperation();
76
+ * logger.debug(`Operation completed in ${elapsed()}ms`);
77
+ * ```
78
+ */
79
+ export function startTimer() {
80
+ const start = performance.now();
81
+ return () => Math.round(performance.now() - start);
82
+ }
83
+ /**
84
+ * Mask all arguments in an array by converting to strings and masking secrets.
85
+ */
86
+ function maskArgs(args, shouldMask) {
87
+ if (!shouldMask)
88
+ return args;
89
+ return args.map((arg) => (typeof arg === 'string' ? maskSecrets(arg) : maskSecrets(String(arg))));
90
+ }
91
+ /**
92
+ * Logger implementation that outputs to stderr with automatic secret masking,
93
+ * timestamps, colors, and JSON pretty-printing.
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * const logger = new ConsoleLogger({ level: 'debug', prefix: 'MCPClient' });
98
+ * logger.info('Starting...'); // [MCPClient] INFO: Starting...
99
+ * logger.info('API key:', 'sk-abc123def456ghi789'); // [MCPClient] INFO: API key: sk-ab...789
20
100
  * ```
21
101
  */
22
102
  export class ConsoleLogger {
23
103
  level;
24
104
  prefix;
105
+ shouldMask;
106
+ useTimestamps;
107
+ useColors;
25
108
  constructor(options = {}) {
26
109
  this.level = LOG_LEVELS[options.level || 'info'];
27
110
  this.prefix = options.prefix || '';
111
+ this.shouldMask = options.maskSecrets !== false; // default true
112
+ this.useTimestamps = options.timestamps ?? false;
113
+ this.useColors = options.colors ?? true;
28
114
  }
29
115
  shouldLog(messageLevel) {
30
116
  return LOG_LEVELS[messageLevel] >= this.level;
31
117
  }
32
118
  format(level, args) {
33
119
  const prefix = this.prefix ? `[${this.prefix}] ` : '';
34
- return [`${prefix}${level.toUpperCase()}:`, ...args];
120
+ const levelStr = level.toUpperCase();
121
+ const masked = maskArgs(args, this.shouldMask);
122
+ const parts = [];
123
+ if (this.useTimestamps) {
124
+ parts.push(colorize(`[${formatTimestamp()}]`, COLORS.dim, this.useColors));
125
+ }
126
+ parts.push(colorize(`${prefix}${levelStr}:`, COLORS[level] || COLORS.reset, this.useColors));
127
+ parts.push(...masked);
128
+ return parts.length > 1 ? parts : [`${prefix}${levelStr}:`, ...masked];
35
129
  }
36
130
  debug(...args) {
37
131
  if (this.shouldLog('debug')) {
@@ -70,9 +164,9 @@ export class ConsoleLogger {
70
164
  * ```
71
165
  */
72
166
  export class NoOpLogger {
73
- debug() { }
74
- info() { }
75
- warn() { }
76
- error() { }
167
+ debug(..._args) { }
168
+ info(..._args) { }
169
+ warn(..._args) { }
170
+ error(..._args) { }
77
171
  }
78
172
  //# sourceMappingURL=logger.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAgDH,MAAM,UAAU,GAA6B;IAC3C,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,EAAE;CACT,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,OAAO,aAAa;IAChB,KAAK,CAAS;IACd,MAAM,CAAS;IAEvB,YAAY,UAAyB,EAAE;QACrC,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,KAAK,IAAI,MAAM,CAAC,CAAC;QACjD,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC;IACrC,CAAC;IAEO,SAAS,CAAC,YAAsB;QACtC,OAAO,UAAU,CAAC,YAAY,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC;IAChD,CAAC;IAEO,MAAM,CAAC,KAAe,EAAE,IAAe;QAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QACtD,OAAO,CAAC,GAAG,MAAM,GAAG,KAAK,CAAC,WAAW,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IACvD,CAAC;IAED,KAAK,CAAC,GAAG,IAAe;QACtB,IAAI,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;YAC5B,sCAAsC;YACtC,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IAED,IAAI,CAAC,GAAG,IAAe;QACrB,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3B,sCAAsC;YACtC,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IAED,IAAI,CAAC,GAAG,IAAe;QACrB,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3B,sCAAsC;YACtC,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IAED,KAAK,CAAC,GAAG,IAAe;QACtB,IAAI,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;YAC5B,sCAAsC;YACtC,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;CACF;AAED;;;;;;;;;;GAUG;AACH,MAAM,OAAO,UAAU;IACrB,KAAK,KAAU,CAAC;IAChB,IAAI,KAAU,CAAC;IACf,IAAI,KAAU,CAAC;IACf,KAAK,KAAU,CAAC;CACjB"}
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Secret masking utility for preventing sensitive data from appearing in logs.
3
+ *
4
+ * Automatically detects and masks common secret patterns like API keys,
5
+ * tokens, passwords, and connection strings. Also masks values of known
6
+ * sensitive environment variable keys.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { maskSecrets, addSecretPattern } from '@slbdn/mcp-tester';
11
+ *
12
+ * // Built-in patterns are applied automatically
13
+ * maskSecrets('API key: sk-abc123def456ghi789');
14
+ * // → 'API key: sk-ab...789'
15
+ *
16
+ * // Add custom patterns
17
+ * addSecretPattern(/my-secret-key-[a-zA-Z0-9]+/g, 'MyOrg API key');
18
+ * ```
19
+ */
20
+ /**
21
+ * A secret masking pattern with a regex and human-readable name.
22
+ */
23
+ export interface SecretPattern {
24
+ /** Regex pattern to match secrets. */
25
+ pattern: RegExp;
26
+ /** Human-readable name for the pattern (used in diagnostics). */
27
+ name: string;
28
+ }
29
+ /**
30
+ * Mask a string value, keeping the first few and last few characters visible
31
+ * and replacing the rest with `...`.
32
+ *
33
+ * @param value - The secret value to mask
34
+ * @param visibleChars - How many characters to keep visible at start and end
35
+ * @returns Masked string like "sk-ab...789"
36
+ */
37
+ export declare function maskValue(value: string, visibleChars?: number): string;
38
+ /**
39
+ * Mask secrets in a string by applying all active secret patterns
40
+ * and masking values of known sensitive environment variable keys.
41
+ *
42
+ * @param input - The value to mask (any type — converted to string)
43
+ * @returns String with secrets replaced by masked versions
44
+ */
45
+ export declare function maskSecrets(input: unknown): string;
46
+ /**
47
+ * Add a custom secret pattern to the masking rules.
48
+ *
49
+ * @param pattern - Regex pattern to match secrets
50
+ * @param name - Human-readable name for the pattern
51
+ */
52
+ export declare function addSecretPattern(pattern: RegExp, name?: string): void;
53
+ /**
54
+ * Reset all secret patterns to the built-in defaults.
55
+ * Removes any patterns added via `addSecretPattern()`.
56
+ */
57
+ export declare function resetSecretPatterns(): void;
58
+ /**
59
+ * Get the list of currently active secret patterns.
60
+ */
61
+ export declare function getSecretPatterns(): SecretPattern[];
62
+ /**
63
+ * Get the set of sensitive environment variable key names.
64
+ */
65
+ export declare function getSensitiveEnvKeys(): ReadonlySet<string>;
66
+ //# sourceMappingURL=masking.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"masking.d.ts","sourceRoot":"","sources":["../../src/utils/masking.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAC;CACd;AAuFD;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,GAAE,MAAsB,GAAG,MAAM,CAOrF;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CA2BlD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAiB,GAAG,IAAI,CAE/E;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,aAAa,EAAE,CAEnD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,WAAW,CAAC,MAAM,CAAC,CAEzD"}
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Secret masking utility for preventing sensitive data from appearing in logs.
3
+ *
4
+ * Automatically detects and masks common secret patterns like API keys,
5
+ * tokens, passwords, and connection strings. Also masks values of known
6
+ * sensitive environment variable keys.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { maskSecrets, addSecretPattern } from '@slbdn/mcp-tester';
11
+ *
12
+ * // Built-in patterns are applied automatically
13
+ * maskSecrets('API key: sk-abc123def456ghi789');
14
+ * // → 'API key: sk-ab...789'
15
+ *
16
+ * // Add custom patterns
17
+ * addSecretPattern(/my-secret-key-[a-zA-Z0-9]+/g, 'MyOrg API key');
18
+ * ```
19
+ */
20
+ /**
21
+ * Environment variable keys that commonly contain secrets.
22
+ * Values for these keys will be masked in log output.
23
+ */
24
+ const SENSITIVE_ENV_KEYS = new Set([
25
+ 'API_KEY',
26
+ 'API_SECRET',
27
+ 'SECRET_KEY',
28
+ 'SECRET',
29
+ 'PRIVATE_KEY',
30
+ 'ACCESS_KEY',
31
+ 'ACCESS_TOKEN',
32
+ 'ACCESS_SECRET',
33
+ 'AUTH_TOKEN',
34
+ 'AUTHORIZATION',
35
+ 'BEARER_TOKEN',
36
+ 'TOKEN',
37
+ 'REFRESH_TOKEN',
38
+ 'SESSION_TOKEN',
39
+ 'SESSION_ID',
40
+ 'ID_TOKEN',
41
+ 'PASSWORD',
42
+ 'PASSWD',
43
+ 'DB_PASSWORD',
44
+ 'DATABASE_PASSWORD',
45
+ 'REDIS_PASSWORD',
46
+ 'MONGO_PASSWORD',
47
+ 'DATABASE_URL',
48
+ 'DB_URL',
49
+ 'MONGODB_URI',
50
+ 'POSTGRES_URL',
51
+ 'MYSQL_URL',
52
+ 'REDIS_URL',
53
+ 'AWS_ACCESS_KEY_ID',
54
+ 'AWS_SECRET_ACCESS_KEY',
55
+ 'AWS_SESSION_TOKEN',
56
+ 'AZURE_CLIENT_SECRET',
57
+ 'GCP_API_KEY',
58
+ 'GOOGLE_API_KEY',
59
+ 'OPENAI_API_KEY',
60
+ 'ANTHROPIC_API_KEY',
61
+ 'GITHUB_TOKEN',
62
+ 'GITLAB_TOKEN',
63
+ 'NPM_TOKEN',
64
+ 'NPM_AUTH_TOKEN',
65
+ 'SLACK_TOKEN',
66
+ 'SLACK_WEBHOOK',
67
+ 'SENTRY_DSN',
68
+ 'ENCRYPTION_KEY',
69
+ 'SIGNING_KEY',
70
+ 'JWT_SECRET',
71
+ 'CSRF_SECRET',
72
+ 'APP_SECRET',
73
+ ]);
74
+ const OPENAI_KEY_REGEX = /\bsk-(?:proj|ant)-[a-zA-Z0-9_-]{10,}/g;
75
+ const SK_KEY_REGEX = /\bsk-[a-zA-Z0-9]{12,}/g;
76
+ const AWS_KEY_REGEX = /\bAKIA[A-Z0-9]{16}\b/g;
77
+ const BEARER_REGEX = /\bBearer\s+[a-zA-Z0-9._-]{8,}/gi;
78
+ const JWT_REGEX = /\beyJ[a-zA-Z0-9_-]{10,}/g;
79
+ const HEX_REGEX = /\b[a-f0-9]{40,}\b/g;
80
+ const URL_CREDS_REGEX = /:[a-zA-Z0-9._-]{4,}@[a-zA-Z0-9.-]+/g;
81
+ const KV_REGEX = /\b(?:password|passwd|secret|token|key|auth|credential)s?\s*[:=]\s*["']?[a-zA-Z0-9._/@+-]{8,}["']?/gi;
82
+ /**
83
+ * Built-in secret patterns that are automatically masked.
84
+ */
85
+ const DEFAULT_SECRET_PATTERNS = [
86
+ { pattern: OPENAI_KEY_REGEX, name: 'OpenAI/Anthropic API key' },
87
+ { pattern: SK_KEY_REGEX, name: 'API key (sk-)' },
88
+ { pattern: AWS_KEY_REGEX, name: 'AWS access key' },
89
+ { pattern: BEARER_REGEX, name: 'Bearer token' },
90
+ { pattern: JWT_REGEX, name: 'JWT token' },
91
+ { pattern: HEX_REGEX, name: 'Hex token' },
92
+ { pattern: URL_CREDS_REGEX, name: 'URL credentials' },
93
+ { pattern: KV_REGEX, name: 'Key-value secret' },
94
+ ];
95
+ /** Active secret patterns (built-in + user-added). */
96
+ let activePatterns = [...DEFAULT_SECRET_PATTERNS];
97
+ /** Number of characters to keep visible at start/end when masking. */
98
+ const VISIBLE_CHARS = 3;
99
+ /**
100
+ * Mask a string value, keeping the first few and last few characters visible
101
+ * and replacing the rest with `...`.
102
+ *
103
+ * @param value - The secret value to mask
104
+ * @param visibleChars - How many characters to keep visible at start and end
105
+ * @returns Masked string like "sk-ab...789"
106
+ */
107
+ export function maskValue(value, visibleChars = VISIBLE_CHARS) {
108
+ if (visibleChars <= 0 || value.length <= visibleChars * 2 + 3) {
109
+ return '***';
110
+ }
111
+ const start = value.slice(0, visibleChars);
112
+ const end = value.slice(-visibleChars);
113
+ return `${start}...${end}`;
114
+ }
115
+ /**
116
+ * Mask secrets in a string by applying all active secret patterns
117
+ * and masking values of known sensitive environment variable keys.
118
+ *
119
+ * @param input - The value to mask (any type — converted to string)
120
+ * @returns String with secrets replaced by masked versions
121
+ */
122
+ export function maskSecrets(input) {
123
+ if (input === null || input === undefined) {
124
+ return String(input);
125
+ }
126
+ let result = String(input);
127
+ // 1. First mask values of known sensitive environment variable keys
128
+ // Matches: KEY=value, KEY="value", KEY: value, KEY: "value"
129
+ result = result.replace(/\b([A-Z_][A-Z0-9_]{2,})(\s*[:=]\s*)(["']?)([^\s"']{4,})\3/g, (_match, key, separator, quote, _value) => {
130
+ if (SENSITIVE_ENV_KEYS.has(key.toUpperCase())) {
131
+ return `${key}${separator}${quote}***${quote}`;
132
+ }
133
+ return _match;
134
+ });
135
+ // 2. Then apply built-in and user-added secret patterns
136
+ for (const { pattern } of activePatterns) {
137
+ // Create fresh regex to avoid lastIndex issues from global flag
138
+ const regex = new RegExp(pattern.source, pattern.flags);
139
+ result = result.replace(regex, (match) => maskValue(match));
140
+ }
141
+ return result;
142
+ }
143
+ /**
144
+ * Add a custom secret pattern to the masking rules.
145
+ *
146
+ * @param pattern - Regex pattern to match secrets
147
+ * @param name - Human-readable name for the pattern
148
+ */
149
+ export function addSecretPattern(pattern, name = 'custom') {
150
+ activePatterns.push({ pattern, name });
151
+ }
152
+ /**
153
+ * Reset all secret patterns to the built-in defaults.
154
+ * Removes any patterns added via `addSecretPattern()`.
155
+ */
156
+ export function resetSecretPatterns() {
157
+ activePatterns = [...DEFAULT_SECRET_PATTERNS];
158
+ }
159
+ /**
160
+ * Get the list of currently active secret patterns.
161
+ */
162
+ export function getSecretPatterns() {
163
+ return [...activePatterns];
164
+ }
165
+ /**
166
+ * Get the set of sensitive environment variable key names.
167
+ */
168
+ export function getSensitiveEnvKeys() {
169
+ return SENSITIVE_ENV_KEYS;
170
+ }
171
+ //# sourceMappingURL=masking.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"masking.js","sourceRoot":"","sources":["../../src/utils/masking.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Input validation utilities for MCPClient.
3
+ *
4
+ * Validates all user-facing inputs and throws descriptive `MCPClientError`
5
+ * with actionable messages when validation fails.
6
+ */
7
+ /**
8
+ * Validate `start()` config — command is required and must be a non-empty string.
9
+ */
10
+ export declare function validateServerConfig(config: unknown): asserts config is {
11
+ command: string;
12
+ args?: string[];
13
+ env?: Record<string, string | undefined>;
14
+ startupDelay?: number;
15
+ };
16
+ /**
17
+ * Validate `callTool()` options — tool name is required.
18
+ */
19
+ export declare function validateToolCallOptions(options: unknown): asserts options is {
20
+ name: string;
21
+ arguments?: Record<string, unknown>;
22
+ timeout?: number;
23
+ retries?: number;
24
+ };
25
+ /**
26
+ * Validate a resource URI — must be a non-empty string.
27
+ */
28
+ export declare function validateResourceUri(uri: unknown): asserts uri is string;
29
+ /**
30
+ * Validate prompt name — must be a non-empty string.
31
+ */
32
+ export declare function validatePromptName(name: unknown): asserts name is string;
33
+ /**
34
+ * Validate prompt arguments — must be an object with string values if provided.
35
+ */
36
+ export declare function validatePromptArgs(args: unknown): asserts args is Record<string, string>;
37
+ /**
38
+ * Validate sampling request params.
39
+ */
40
+ export declare function validateSamplingRequest(request: unknown): asserts request is {
41
+ messages: unknown[];
42
+ maxTokens: number;
43
+ };
44
+ /**
45
+ * Validate client constructor options.
46
+ */
47
+ export declare function validateClientOptions(options: unknown): void;
48
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,IAAI;IACvE,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAwGA;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAqFA;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,GAAG,IAAI,MAAM,CAsBvE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,IAAI,MAAM,CAqBxE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAsBxF;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI;IAC5E,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB,CA+BA;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAyG5D"}