@revealui/utils 0.2.0 → 0.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.
package/README.md CHANGED
@@ -1,60 +1,85 @@
1
1
  # @revealui/utils
2
2
 
3
- Shared utilities for RevealUI - logger, SSL config, and common helpers.
3
+ Zero-dependency shared utilities for RevealUI structured logging, database helpers, and validation.
4
4
 
5
- ## Purpose
5
+ ## Why This Package Exists
6
+
7
+ Extracting shared utilities to `@revealui/utils` breaks circular dependencies between core, db, and contracts:
6
8
 
7
- This package contains zero-dependency utilities that are used across multiple packages in the RevealUI monorepo. It was created to break circular dependencies between `@revealui/core`, `@revealui/db`, and `@revealui/contracts`.
9
+ ```
10
+ utils (no dependencies)
11
+ ^
12
+ |-- contracts
13
+ |-- db
14
+ |-- core
15
+ ```
8
16
 
9
- ## Contents
17
+ ## Features
10
18
 
11
- ### Logger
12
- Structured logging infrastructure with support for:
13
- - Multiple log levels (debug, info, warn, error, fatal)
14
- - Context propagation
15
- - Pretty printing for development
16
- - Remote logging support
17
- - Child loggers with inherited context
19
+ - **Structured Logger** — Multiple log levels, context propagation, child loggers, pluggable handlers
20
+ - **Database Utilities** SSL configuration, connection string parsing, security validation
21
+ - **Validation** Common validation helpers
18
22
 
19
- ### Database Utilities
20
- - SSL configuration for PostgreSQL connections
21
- - Connection string parsing
22
- - Security validation for production environments
23
+ ## Installation
24
+
25
+ ```bash
26
+ pnpm add @revealui/utils
27
+ ```
23
28
 
24
29
  ## Usage
25
30
 
31
+ ### Logger
32
+
26
33
  ```typescript
27
- // Import logger
28
34
  import { logger, createLogger } from '@revealui/utils'
29
35
 
30
- // Use default logger
36
+ // Default logger
31
37
  logger.info('Application started')
38
+ logger.error('Something failed', { error })
32
39
 
33
- // Create child logger with context
34
- const requestLogger = createLogger({ requestId: '123' })
40
+ // Child logger with context
41
+ const requestLogger = createLogger({ requestId: '123', service: 'api' })
35
42
  requestLogger.info('Processing request')
36
43
 
37
- // Import database utilities
44
+ // Add custom log handler (e.g. database transport)
45
+ logger.addLogHandler(async (entry) => {
46
+ await db.insert(appLogs).values(entry)
47
+ })
48
+ ```
49
+
50
+ ### Database Utilities
51
+
52
+ ```typescript
38
53
  import { getSSLConfig } from '@revealui/utils/database'
39
54
 
40
- const sslConfig = getSSLConfig(process.env.DATABASE_URL)
55
+ // Auto-configure SSL for PostgreSQL connections
56
+ const sslConfig = getSSLConfig(process.env.POSTGRES_URL)
41
57
  ```
42
58
 
43
- ## Why This Package Exists
59
+ ### Validation
44
60
 
45
- Previously, these utilities lived in `@revealui/core`, which created a circular dependency:
46
- ```
47
- contracts → db → core → contracts (circular!)
61
+ ```typescript
62
+ import { validate } from '@revealui/utils/validation'
48
63
  ```
49
64
 
50
- By extracting shared utilities to `@revealui/utils`, we get a clean dependency graph:
51
- ```
52
- utils (no dependencies)
53
-
54
- ├── contracts
55
- ├── db
56
- └── core
57
- ```
65
+ ## Exports
66
+
67
+ | Subpath | Contents |
68
+ |---------|----------|
69
+ | `@revealui/utils` | Logger and main utilities |
70
+ | `@revealui/utils/logger` | Logger module (createLogger, log levels, handlers) |
71
+ | `@revealui/utils/database` | SSL config, connection string parsing |
72
+ | `@revealui/utils/validation` | Validation helpers |
73
+
74
+ ## Logger Features
75
+
76
+ | Feature | Description |
77
+ |---------|-------------|
78
+ | Log levels | `debug`, `info`, `warn`, `error`, `fatal` |
79
+ | Context propagation | Attach metadata (requestId, service, etc.) |
80
+ | Child loggers | Inherit parent context with additional fields |
81
+ | Log handlers | Pluggable async handlers (DB, remote, etc.) |
82
+ | Pretty printing | Colored output in development |
58
83
 
59
84
  ## Development
60
85
 
@@ -65,10 +90,18 @@ pnpm build
65
90
  # Type check
66
91
  pnpm typecheck
67
92
 
93
+ # Run tests
94
+ pnpm test
95
+
68
96
  # Watch mode
69
97
  pnpm dev
70
98
  ```
71
99
 
100
+ ## Related
101
+
102
+ - [Core Package](../core/README.md) — Uses utils for logging and error handling
103
+ - [DB Package](../db/README.md) — Uses utils for SSL and connection helpers
104
+
72
105
  ## License
73
106
 
74
107
  MIT
@@ -29,4 +29,4 @@ export {
29
29
  getSSLConfig,
30
30
  validateSSLConfig
31
31
  };
32
- //# sourceMappingURL=chunk-KUMOGLHP.js.map
32
+ //# sourceMappingURL=chunk-U7HNVJST.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/database/ssl-config.ts"],"sourcesContent":["/**\n * SSL Configuration Utility\n *\n * Provides centralized SSL configuration for PostgreSQL connections based on\n * connection string sslmode parameter and environment variables.\n *\n * Extracted from @revealui/core to break circular dependencies\n *\n * @module ssl-config\n */\n\n/**\n * PostgreSQL SSL configuration options\n */\nexport interface SSLConfig {\n rejectUnauthorized: boolean;\n}\n\n/**\n * Determines the appropriate SSL configuration for a PostgreSQL connection\n * based on the connection string's sslmode parameter.\n *\n * SSL Modes (aligned with PostgreSQL libpq standards):\n * - `disable`: No SSL connection\n * - `require`: SSL connection with certificate verification\n * - `verify-full`: Full SSL verification (same as require in pg v9+)\n * - `verify-ca`: CA verification (treated as verify-full)\n *\n * Environment Override:\n * - `DATABASE_SSL_REJECT_UNAUTHORIZED=false`: Force skip certificate verification\n * (DEVELOPMENT ONLY - use for self-signed certificates in local environments)\n *\n * @param connectionString - PostgreSQL connection string (may include sslmode parameter)\n * @returns SSL configuration object or false to disable SSL\n *\n * @example\n * ```typescript\n * // Connection string with SSL required\n * const ssl = getSSLConfig('postgresql://user:pass@host/db?sslmode=require')\n * // Returns: { rejectUnauthorized: true }\n *\n * // Connection string without SSL\n * const ssl = getSSLConfig('postgresql://localhost:5432/db')\n * // Returns: false\n *\n * // Development override for self-signed certificates\n * process.env.DATABASE_SSL_REJECT_UNAUTHORIZED = 'false'\n * const ssl = getSSLConfig('postgresql://user:pass@host/db?sslmode=require')\n * // Returns: { rejectUnauthorized: false }\n * ```\n */\nexport function getSSLConfig(connectionString: string): SSLConfig | false {\n try {\n // Parse connection string\n const url = new URL(connectionString);\n const sslmode = url.searchParams.get('sslmode');\n\n // No SSL if sslmode is explicitly disabled or not specified\n if (!sslmode || sslmode === 'disable') {\n return false;\n }\n\n // Environment override for local development with self-signed certificates\n // This should ONLY be used in development environments\n // SECURITY: Explicitly check NODE_ENV to prevent accidental use in production\n if (\n process.env.NODE_ENV !== 'production' &&\n process.env.DATABASE_SSL_REJECT_UNAUTHORIZED === 'false'\n ) {\n return { rejectUnauthorized: false };\n }\n\n // Default: verify certificates (security best practice)\n // Handles: require, verify-full, verify-ca, prefer\n // In pg v9+, these will all use verify-full semantics\n return { rejectUnauthorized: true };\n } catch (_error) {\n // If URL parsing fails, assume local connection without SSL\n // Silently fallback to no SSL for invalid connection strings\n return false;\n }\n}\n\n/**\n * Validates SSL configuration for production environments.\n * Warns if insecure SSL settings are detected in production.\n *\n * @param connectionString - PostgreSQL connection string\n * @param environment - Current environment (e.g., 'production', 'development')\n */\nexport function validateSSLConfig(\n connectionString: string,\n environment: string = process.env.NODE_ENV || 'development',\n): boolean {\n const sslConfig = getSSLConfig(connectionString);\n\n // Check if SSL is disabled in production\n if (environment === 'production' && sslConfig === false) {\n return false; // SSL disabled in production - security risk\n }\n\n // Check if certificate verification is disabled in production\n if (environment === 'production' && sslConfig && !sslConfig.rejectUnauthorized) {\n return false; // Certificate verification disabled - security risk\n }\n\n return true; // SSL configuration is valid\n}\n"],"mappings":";AAmDO,SAAS,aAAa,kBAA6C;AACxE,MAAI;AAEF,UAAM,MAAM,IAAI,IAAI,gBAAgB;AACpC,UAAM,UAAU,IAAI,aAAa,IAAI,SAAS;AAG9C,QAAI,CAAC,WAAW,YAAY,WAAW;AACrC,aAAO;AAAA,IACT;AAKA,QACE,QAAQ,IAAI,aAAa,gBACzB,QAAQ,IAAI,qCAAqC,SACjD;AACA,aAAO,EAAE,oBAAoB,MAAM;AAAA,IACrC;AAKA,WAAO,EAAE,oBAAoB,KAAK;AAAA,EACpC,SAAS,QAAQ;AAGf,WAAO;AAAA,EACT;AACF;AASO,SAAS,kBACd,kBACA,cAAsB,QAAQ,IAAI,YAAY,eACrC;AACT,QAAM,YAAY,aAAa,gBAAgB;AAG/C,MAAI,gBAAgB,gBAAgB,cAAc,OAAO;AACvD,WAAO;AAAA,EACT;AAGA,MAAI,gBAAgB,gBAAgB,aAAa,CAAC,UAAU,oBAAoB;AAC9E,WAAO;AAAA,EACT;AAEA,SAAO;AACT;","names":[]}
@@ -155,11 +155,15 @@ var Logger = class _Logger {
155
155
  outputFile(entry) {
156
156
  console.log(JSON.stringify(entry));
157
157
  }
158
+ /** Circuit breaker state for remote transport */
159
+ remoteFailures = 0;
160
+ maxRemoteFailures = 5;
158
161
  /**
159
- * Output to remote service
162
+ * Output to remote service (with circuit breaker)
160
163
  */
161
164
  async outputRemote(entry) {
162
165
  if (!this.config.remoteUrl) return;
166
+ if (this.remoteFailures >= this.maxRemoteFailures) return;
163
167
  try {
164
168
  await fetch(this.config.remoteUrl, {
165
169
  method: "POST",
@@ -168,8 +172,9 @@ var Logger = class _Logger {
168
172
  },
169
173
  body: JSON.stringify(entry)
170
174
  });
171
- } catch (error) {
172
- console.error("Failed to send log to remote:", error);
175
+ this.remoteFailures = 0;
176
+ } catch {
177
+ this.remoteFailures++;
173
178
  this.outputConsole(entry);
174
179
  }
175
180
  }
@@ -261,4 +266,4 @@ export {
261
266
  logAudit,
262
267
  logQuery
263
268
  };
264
- //# sourceMappingURL=chunk-QR4YAGWO.js.map
269
+ //# sourceMappingURL=chunk-X2ZL53G2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/logger/index.ts"],"sourcesContent":["/**\n * Structured Logging Infrastructure\n *\n * Provides consistent, structured logging across the application\n * Extracted from @revealui/core to break circular dependencies\n */\n\n/* console-allowed */\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal';\n\nexport interface LogContext {\n [key: string]: unknown;\n userId?: string;\n requestId?: string;\n sessionId?: string;\n traceId?: string;\n spanId?: string;\n}\n\nexport interface LogEntry {\n timestamp: string;\n level: LogLevel;\n message: string;\n context?: LogContext;\n error?: {\n name: string;\n message: string;\n stack?: string;\n cause?: unknown;\n };\n metadata?: Record<string, unknown>;\n}\n\nexport interface LoggerConfig {\n level?: LogLevel;\n enabled?: boolean;\n pretty?: boolean;\n includeTimestamp?: boolean;\n includeStack?: boolean;\n destination?: 'console' | 'file' | 'remote';\n remoteUrl?: string;\n onLog?: (entry: LogEntry) => void;\n}\n\nconst LOG_LEVELS: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n fatal: 4,\n};\n\nexport class Logger {\n private config: Required<LoggerConfig>;\n private context: LogContext = {};\n private extraHandlers: Array<(entry: LogEntry) => void> = [];\n\n constructor(config: LoggerConfig = {}) {\n this.config = {\n level: config.level || 'info',\n enabled: config.enabled !== undefined ? config.enabled : true,\n pretty: config.pretty !== undefined ? config.pretty : process.env.NODE_ENV !== 'production',\n includeTimestamp: config.includeTimestamp !== undefined ? config.includeTimestamp : true,\n includeStack: config.includeStack !== undefined ? config.includeStack : true,\n destination: config.destination || 'console',\n remoteUrl: config.remoteUrl || '',\n onLog:\n config.onLog ||\n (() => {\n // No-op function\n }),\n };\n }\n\n /**\n * Register an additional log handler (e.g. DB transport).\n * Called after every log entry, fire-and-forget. Must not throw.\n */\n addLogHandler(handler: (entry: LogEntry) => void): void {\n this.extraHandlers.push(handler);\n }\n\n /**\n * Set global context\n */\n setContext(context: LogContext): void {\n this.context = { ...this.context, ...context };\n }\n\n /**\n * Clear global context\n */\n clearContext(): void {\n this.context = {};\n }\n\n /**\n * Debug log\n */\n debug(message: string, context?: LogContext): void {\n this.log('debug', message, context);\n }\n\n /**\n * Info log\n */\n info(message: string, context?: LogContext): void {\n this.log('info', message, context);\n }\n\n /**\n * Warning log\n */\n warn(message: string, context?: LogContext): void {\n this.log('warn', message, context);\n }\n\n /**\n * Error log\n */\n error(message: string, error?: Error, context?: LogContext): void {\n const errorContext = error\n ? {\n error: {\n name: error.name,\n message: error.message,\n stack: this.config.includeStack ? error.stack : undefined,\n cause: error.cause,\n },\n }\n : {};\n\n this.log('error', message, { ...context, ...errorContext });\n }\n\n /**\n * Fatal log\n */\n fatal(message: string, error?: Error, context?: LogContext): void {\n const errorContext = error\n ? {\n error: {\n name: error.name,\n message: error.message,\n stack: this.config.includeStack ? error.stack : undefined,\n cause: error.cause,\n },\n }\n : {};\n\n this.log('fatal', message, { ...context, ...errorContext });\n }\n\n /**\n * Core logging method\n */\n private log(level: LogLevel, message: string, context?: LogContext): void {\n if (!this.config.enabled) return;\n\n if (LOG_LEVELS[level] < LOG_LEVELS[this.config.level]) {\n return;\n }\n\n const entry: LogEntry = {\n timestamp: this.config.includeTimestamp ? new Date().toISOString() : '',\n level,\n message,\n context: { ...this.context, ...context },\n };\n\n // Extract error if in context\n if (entry.context?.error) {\n entry.error = entry.context.error as LogEntry['error'];\n entry.context.error = undefined;\n }\n\n // Call custom handler\n this.config.onLog(entry);\n\n // Call additional handlers (e.g. DB transport)\n for (const handler of this.extraHandlers) {\n handler(entry);\n }\n\n // Output log\n this.output(entry);\n }\n\n /**\n * Output log entry\n */\n private output(entry: LogEntry): void {\n switch (this.config.destination) {\n case 'console':\n this.outputConsole(entry);\n break;\n case 'file':\n this.outputFile(entry);\n break;\n case 'remote':\n this.outputRemote(entry);\n break;\n }\n }\n\n /**\n * Output to console\n */\n private outputConsole(entry: LogEntry): void {\n const output = this.config.pretty ? this.formatPretty(entry) : JSON.stringify(entry);\n\n switch (entry.level) {\n case 'debug':\n console.debug(output);\n break;\n case 'info':\n console.info(output);\n break;\n case 'warn':\n console.warn(output);\n break;\n case 'error':\n case 'fatal':\n console.error(output);\n break;\n }\n }\n\n /**\n * Output to file\n */\n private outputFile(entry: LogEntry): void {\n // Would require fs module - placeholder for server-side logging\n console.log(JSON.stringify(entry));\n }\n\n /** Circuit breaker state for remote transport */\n private remoteFailures = 0;\n private readonly maxRemoteFailures = 5;\n\n /**\n * Output to remote service (with circuit breaker)\n */\n private async outputRemote(entry: LogEntry): Promise<void> {\n if (!this.config.remoteUrl) return;\n if (this.remoteFailures >= this.maxRemoteFailures) return;\n\n try {\n await fetch(this.config.remoteUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(entry),\n });\n this.remoteFailures = 0;\n } catch {\n this.remoteFailures++;\n this.outputConsole(entry);\n }\n }\n\n /**\n * Format log entry for pretty printing\n */\n private formatPretty(entry: LogEntry): string {\n const levelColors: Record<LogLevel, string> = {\n debug: '\\x1b[36m', // Cyan\n info: '\\x1b[32m', // Green\n warn: '\\x1b[33m', // Yellow\n error: '\\x1b[31m', // Red\n fatal: '\\x1b[35m', // Magenta\n };\n\n const reset = '\\x1b[0m';\n const color = levelColors[entry.level];\n\n const parts: string[] = [];\n\n if (entry.timestamp) {\n parts.push(`\\x1b[90m${entry.timestamp}${reset}`);\n }\n\n parts.push(`${color}${entry.level.toUpperCase()}${reset}`);\n parts.push(entry.message);\n\n if (entry.context && Object.keys(entry.context).length > 0) {\n parts.push(JSON.stringify(entry.context, null, 2));\n }\n\n if (entry.error) {\n parts.push(`\\n${color}Error: ${entry.error.message}${reset}`);\n if (entry.error.stack) {\n parts.push(entry.error.stack);\n }\n }\n\n return parts.join(' ');\n }\n\n /**\n * Create child logger with additional context\n */\n child(context: LogContext): Logger {\n const childLogger = new Logger(this.config);\n childLogger.setContext({ ...this.context, ...context });\n // Share parent's extra handlers so DB transport applies to child loggers too\n for (const handler of this.extraHandlers) {\n childLogger.addLogHandler(handler);\n }\n return childLogger;\n }\n}\n\n/**\n * Default logger instance\n */\nexport const logger = new Logger({\n level: (process.env.LOG_LEVEL as LogLevel) || 'info',\n pretty: process.env.NODE_ENV !== 'production',\n});\n\n/**\n * Create logger with context\n */\nexport function createLogger(context: LogContext): Logger {\n return logger.child(context);\n}\n\n/**\n * Error logger\n */\nexport function logError(error: Error, context?: LogContext): void {\n logger.error(error.message, error, context);\n}\n\n/**\n * Audit logger for security-sensitive operations\n */\nexport function logAudit(action: string, context?: LogContext): void {\n logger.info(`Audit: ${action}`, {\n ...context,\n audit: true,\n action,\n });\n}\n\n/**\n * Database query logger\n */\nexport function logQuery(query: string, duration: number, context?: LogContext): void {\n logger.debug('Database query', {\n ...context,\n query,\n duration,\n });\n\n if (duration > 1000) {\n logger.warn('Slow query detected', {\n ...context,\n query,\n duration,\n });\n }\n}\n"],"mappings":"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","names":[]}
@@ -5,4 +5,4 @@ var passwordSchema = z.string().min(8, "Password must be at least 8 characters")
5
5
  export {
6
6
  passwordSchema
7
7
  };
8
- //# sourceMappingURL=chunk-UEYUNTMY.js.map
8
+ //# sourceMappingURL=chunk-XSQPIKCD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/validation/password-schema.ts"],"sourcesContent":["import { z } from 'zod';\n\n/**\n * Password validation schema\n *\n * Requirements:\n * - Minimum 8 characters\n * - Maximum 128 characters\n * - At least one uppercase letter\n * - At least one lowercase letter\n * - At least one number\n */\nexport const passwordSchema = z\n .string()\n .min(8, 'Password must be at least 8 characters')\n .max(128, 'Password is too long')\n .regex(/[A-Z]/, 'Password must contain at least one uppercase letter')\n .regex(/[a-z]/, 'Password must contain at least one lowercase letter')\n .regex(/[0-9]/, 'Password must contain at least one number');\n\nexport type Password = z.infer<typeof passwordSchema>;\n"],"mappings":";AAAA,SAAS,SAAS;AAYX,IAAM,iBAAiB,EAC3B,OAAO,EACP,IAAI,GAAG,wCAAwC,EAC/C,IAAI,KAAK,sBAAsB,EAC/B,MAAM,SAAS,qDAAqD,EACpE,MAAM,SAAS,qDAAqD,EACpE,MAAM,SAAS,2CAA2C;","names":[]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  getSSLConfig,
3
3
  validateSSLConfig
4
- } from "../chunk-KUMOGLHP.js";
4
+ } from "../chunk-U7HNVJST.js";
5
5
  export {
6
6
  getSSLConfig,
7
7
  validateSSLConfig
package/dist/index.js CHANGED
@@ -1,7 +1,10 @@
1
1
  import {
2
2
  getSSLConfig,
3
3
  validateSSLConfig
4
- } from "./chunk-KUMOGLHP.js";
4
+ } from "./chunk-U7HNVJST.js";
5
+ import {
6
+ passwordSchema
7
+ } from "./chunk-XSQPIKCD.js";
5
8
  import {
6
9
  Logger,
7
10
  createLogger,
@@ -9,10 +12,7 @@ import {
9
12
  logError,
10
13
  logQuery,
11
14
  logger
12
- } from "./chunk-QR4YAGWO.js";
13
- import {
14
- passwordSchema
15
- } from "./chunk-UEYUNTMY.js";
15
+ } from "./chunk-X2ZL53G2.js";
16
16
  export {
17
17
  Logger,
18
18
  createLogger,
@@ -90,8 +90,11 @@ declare class Logger {
90
90
  * Output to file
91
91
  */
92
92
  private outputFile;
93
+ /** Circuit breaker state for remote transport */
94
+ private remoteFailures;
95
+ private readonly maxRemoteFailures;
93
96
  /**
94
- * Output to remote service
97
+ * Output to remote service (with circuit breaker)
95
98
  */
96
99
  private outputRemote;
97
100
  /**
@@ -5,7 +5,7 @@ import {
5
5
  logError,
6
6
  logQuery,
7
7
  logger
8
- } from "../chunk-QR4YAGWO.js";
8
+ } from "../chunk-X2ZL53G2.js";
9
9
  export {
10
10
  Logger,
11
11
  createLogger,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  passwordSchema
3
- } from "../chunk-UEYUNTMY.js";
3
+ } from "../chunk-XSQPIKCD.js";
4
4
  export {
5
5
  passwordSchema
6
6
  };
package/package.json CHANGED
@@ -1,9 +1,21 @@
1
1
  {
2
2
  "name": "@revealui/utils",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Shared utilities for RevealUI - logger, SSL config, and common helpers",
5
5
  "license": "MIT",
6
- "type": "module",
6
+ "dependencies": {
7
+ "zod": "^4.3.6"
8
+ },
9
+ "devDependencies": {
10
+ "@types/node": "^25.3.0",
11
+ "tsup": "^8.5.1",
12
+ "typescript": "^5.9.3",
13
+ "vitest": "^4.0.18",
14
+ "dev": "0.0.1"
15
+ },
16
+ "engines": {
17
+ "node": ">=24.13.0"
18
+ },
7
19
  "exports": {
8
20
  ".": {
9
21
  "types": "./dist/index.d.ts",
@@ -25,28 +37,22 @@
25
37
  "files": [
26
38
  "dist"
27
39
  ],
40
+ "main": "./dist/index.js",
28
41
  "publishConfig": {
29
42
  "access": "public",
30
43
  "registry": "https://registry.npmjs.org"
31
44
  },
32
- "dependencies": {
33
- "zod": "^4.3.6"
34
- },
35
- "devDependencies": {
36
- "@types/node": "^25.3.0",
37
- "tsup": "^8.5.1",
38
- "typescript": "^5.9.3",
39
- "vitest": "^4.0.18",
40
- "dev": "0.0.1"
41
- },
45
+ "type": "module",
46
+ "types": "./dist/index.d.ts",
42
47
  "scripts": {
43
48
  "build": "tsup",
49
+ "clean": "rm -rf dist",
44
50
  "dev": "tsup --watch",
45
- "typecheck": "tsc --noEmit",
46
- "test": "vitest run --passWithNoTests",
47
- "test:watch": "vitest",
48
51
  "lint": "biome check .",
49
52
  "lint:fix": "biome check --write .",
50
- "clean": "rm -rf dist"
53
+ "test": "vitest run --passWithNoTests",
54
+ "test:coverage": "vitest run --coverage --coverage.reporter=json-summary --coverage.reporter=html --coverage.reporter=text --passWithNoTests",
55
+ "test:watch": "vitest",
56
+ "typecheck": "tsc --noEmit"
51
57
  }
52
58
  }
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/database/ssl-config.ts"],"sourcesContent":["/**\n * SSL Configuration Utility\n *\n * Provides centralized SSL configuration for PostgreSQL connections based on\n * connection string sslmode parameter and environment variables.\n *\n * Extracted from @revealui/core to break circular dependencies\n *\n * @module ssl-config\n */\n\n/**\n * PostgreSQL SSL configuration options\n */\nexport interface SSLConfig {\n rejectUnauthorized: boolean\n}\n\n/**\n * Determines the appropriate SSL configuration for a PostgreSQL connection\n * based on the connection string's sslmode parameter.\n *\n * SSL Modes (aligned with PostgreSQL libpq standards):\n * - `disable`: No SSL connection\n * - `require`: SSL connection with certificate verification\n * - `verify-full`: Full SSL verification (same as require in pg v9+)\n * - `verify-ca`: CA verification (treated as verify-full)\n *\n * Environment Override:\n * - `DATABASE_SSL_REJECT_UNAUTHORIZED=false`: Force skip certificate verification\n * (DEVELOPMENT ONLY - use for self-signed certificates in local environments)\n *\n * @param connectionString - PostgreSQL connection string (may include sslmode parameter)\n * @returns SSL configuration object or false to disable SSL\n *\n * @example\n * ```typescript\n * // Connection string with SSL required\n * const ssl = getSSLConfig('postgresql://user:pass@host/db?sslmode=require')\n * // Returns: { rejectUnauthorized: true }\n *\n * // Connection string without SSL\n * const ssl = getSSLConfig('postgresql://localhost:5432/db')\n * // Returns: false\n *\n * // Development override for self-signed certificates\n * process.env.DATABASE_SSL_REJECT_UNAUTHORIZED = 'false'\n * const ssl = getSSLConfig('postgresql://user:pass@host/db?sslmode=require')\n * // Returns: { rejectUnauthorized: false }\n * ```\n */\nexport function getSSLConfig(connectionString: string): SSLConfig | false {\n try {\n // Parse connection string\n const url = new URL(connectionString)\n const sslmode = url.searchParams.get('sslmode')\n\n // No SSL if sslmode is explicitly disabled or not specified\n if (!sslmode || sslmode === 'disable') {\n return false\n }\n\n // Environment override for local development with self-signed certificates\n // This should ONLY be used in development environments\n // SECURITY: Explicitly check NODE_ENV to prevent accidental use in production\n if (\n process.env.NODE_ENV !== 'production' &&\n process.env.DATABASE_SSL_REJECT_UNAUTHORIZED === 'false'\n ) {\n return { rejectUnauthorized: false }\n }\n\n // Default: verify certificates (security best practice)\n // Handles: require, verify-full, verify-ca, prefer\n // In pg v9+, these will all use verify-full semantics\n return { rejectUnauthorized: true }\n } catch (_error) {\n // If URL parsing fails, assume local connection without SSL\n // Silently fallback to no SSL for invalid connection strings\n return false\n }\n}\n\n/**\n * Validates SSL configuration for production environments.\n * Warns if insecure SSL settings are detected in production.\n *\n * @param connectionString - PostgreSQL connection string\n * @param environment - Current environment (e.g., 'production', 'development')\n */\nexport function validateSSLConfig(\n connectionString: string,\n environment: string = process.env.NODE_ENV || 'development',\n): boolean {\n const sslConfig = getSSLConfig(connectionString)\n\n // Check if SSL is disabled in production\n if (environment === 'production' && sslConfig === false) {\n return false // SSL disabled in production - security risk\n }\n\n // Check if certificate verification is disabled in production\n if (environment === 'production' && sslConfig && !sslConfig.rejectUnauthorized) {\n return false // Certificate verification disabled - security risk\n }\n\n return true // SSL configuration is valid\n}\n"],"mappings":";AAmDO,SAAS,aAAa,kBAA6C;AACxE,MAAI;AAEF,UAAM,MAAM,IAAI,IAAI,gBAAgB;AACpC,UAAM,UAAU,IAAI,aAAa,IAAI,SAAS;AAG9C,QAAI,CAAC,WAAW,YAAY,WAAW;AACrC,aAAO;AAAA,IACT;AAKA,QACE,QAAQ,IAAI,aAAa,gBACzB,QAAQ,IAAI,qCAAqC,SACjD;AACA,aAAO,EAAE,oBAAoB,MAAM;AAAA,IACrC;AAKA,WAAO,EAAE,oBAAoB,KAAK;AAAA,EACpC,SAAS,QAAQ;AAGf,WAAO;AAAA,EACT;AACF;AASO,SAAS,kBACd,kBACA,cAAsB,QAAQ,IAAI,YAAY,eACrC;AACT,QAAM,YAAY,aAAa,gBAAgB;AAG/C,MAAI,gBAAgB,gBAAgB,cAAc,OAAO;AACvD,WAAO;AAAA,EACT;AAGA,MAAI,gBAAgB,gBAAgB,aAAa,CAAC,UAAU,oBAAoB;AAC9E,WAAO;AAAA,EACT;AAEA,SAAO;AACT;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/logger/index.ts"],"sourcesContent":["/**\n * Structured Logging Infrastructure\n *\n * Provides consistent, structured logging across the application\n * Extracted from @revealui/core to break circular dependencies\n */\n\n/* eslint-disable no-console */\n/* console-allowed */\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal'\n\nexport interface LogContext {\n [key: string]: unknown\n userId?: string\n requestId?: string\n sessionId?: string\n traceId?: string\n spanId?: string\n}\n\nexport interface LogEntry {\n timestamp: string\n level: LogLevel\n message: string\n context?: LogContext\n error?: {\n name: string\n message: string\n stack?: string\n cause?: unknown\n }\n metadata?: Record<string, unknown>\n}\n\nexport interface LoggerConfig {\n level?: LogLevel\n enabled?: boolean\n pretty?: boolean\n includeTimestamp?: boolean\n includeStack?: boolean\n destination?: 'console' | 'file' | 'remote'\n remoteUrl?: string\n onLog?: (entry: LogEntry) => void\n}\n\nconst LOG_LEVELS: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n fatal: 4,\n}\n\nexport class Logger {\n private config: Required<LoggerConfig>\n private context: LogContext = {}\n private extraHandlers: Array<(entry: LogEntry) => void> = []\n\n constructor(config: LoggerConfig = {}) {\n this.config = {\n level: config.level || 'info',\n enabled: config.enabled !== undefined ? config.enabled : true,\n pretty: config.pretty !== undefined ? config.pretty : process.env.NODE_ENV !== 'production',\n includeTimestamp: config.includeTimestamp !== undefined ? config.includeTimestamp : true,\n includeStack: config.includeStack !== undefined ? config.includeStack : true,\n destination: config.destination || 'console',\n remoteUrl: config.remoteUrl || '',\n onLog:\n config.onLog ||\n (() => {\n // No-op function\n }),\n }\n }\n\n /**\n * Register an additional log handler (e.g. DB transport).\n * Called after every log entry, fire-and-forget. Must not throw.\n */\n addLogHandler(handler: (entry: LogEntry) => void): void {\n this.extraHandlers.push(handler)\n }\n\n /**\n * Set global context\n */\n setContext(context: LogContext): void {\n this.context = { ...this.context, ...context }\n }\n\n /**\n * Clear global context\n */\n clearContext(): void {\n this.context = {}\n }\n\n /**\n * Debug log\n */\n debug(message: string, context?: LogContext): void {\n this.log('debug', message, context)\n }\n\n /**\n * Info log\n */\n info(message: string, context?: LogContext): void {\n this.log('info', message, context)\n }\n\n /**\n * Warning log\n */\n warn(message: string, context?: LogContext): void {\n this.log('warn', message, context)\n }\n\n /**\n * Error log\n */\n error(message: string, error?: Error, context?: LogContext): void {\n const errorContext = error\n ? {\n error: {\n name: error.name,\n message: error.message,\n stack: this.config.includeStack ? error.stack : undefined,\n cause: error.cause,\n },\n }\n : {}\n\n this.log('error', message, { ...context, ...errorContext })\n }\n\n /**\n * Fatal log\n */\n fatal(message: string, error?: Error, context?: LogContext): void {\n const errorContext = error\n ? {\n error: {\n name: error.name,\n message: error.message,\n stack: this.config.includeStack ? error.stack : undefined,\n cause: error.cause,\n },\n }\n : {}\n\n this.log('fatal', message, { ...context, ...errorContext })\n }\n\n /**\n * Core logging method\n */\n private log(level: LogLevel, message: string, context?: LogContext): void {\n if (!this.config.enabled) return\n\n if (LOG_LEVELS[level] < LOG_LEVELS[this.config.level]) {\n return\n }\n\n const entry: LogEntry = {\n timestamp: this.config.includeTimestamp ? new Date().toISOString() : '',\n level,\n message,\n context: { ...this.context, ...context },\n }\n\n // Extract error if in context\n if (entry.context?.error) {\n entry.error = entry.context.error as LogEntry['error']\n entry.context.error = undefined\n }\n\n // Call custom handler\n this.config.onLog(entry)\n\n // Call additional handlers (e.g. DB transport)\n for (const handler of this.extraHandlers) {\n handler(entry)\n }\n\n // Output log\n this.output(entry)\n }\n\n /**\n * Output log entry\n */\n private output(entry: LogEntry): void {\n switch (this.config.destination) {\n case 'console':\n this.outputConsole(entry)\n break\n case 'file':\n this.outputFile(entry)\n break\n case 'remote':\n this.outputRemote(entry)\n break\n }\n }\n\n /**\n * Output to console\n */\n private outputConsole(entry: LogEntry): void {\n const output = this.config.pretty ? this.formatPretty(entry) : JSON.stringify(entry)\n\n switch (entry.level) {\n case 'debug':\n console.debug(output)\n break\n case 'info':\n console.info(output)\n break\n case 'warn':\n console.warn(output)\n break\n case 'error':\n case 'fatal':\n console.error(output)\n break\n }\n }\n\n /**\n * Output to file\n */\n private outputFile(entry: LogEntry): void {\n // Would require fs module - placeholder for server-side logging\n console.log(JSON.stringify(entry))\n }\n\n /**\n * Output to remote service\n */\n private async outputRemote(entry: LogEntry): Promise<void> {\n if (!this.config.remoteUrl) return\n\n try {\n await fetch(this.config.remoteUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(entry),\n })\n } catch (error) {\n // Fallback to console if remote fails\n console.error('Failed to send log to remote:', error)\n this.outputConsole(entry)\n }\n }\n\n /**\n * Format log entry for pretty printing\n */\n private formatPretty(entry: LogEntry): string {\n const levelColors: Record<LogLevel, string> = {\n debug: '\\x1b[36m', // Cyan\n info: '\\x1b[32m', // Green\n warn: '\\x1b[33m', // Yellow\n error: '\\x1b[31m', // Red\n fatal: '\\x1b[35m', // Magenta\n }\n\n const reset = '\\x1b[0m'\n const color = levelColors[entry.level]\n\n const parts: string[] = []\n\n if (entry.timestamp) {\n parts.push(`\\x1b[90m${entry.timestamp}${reset}`)\n }\n\n parts.push(`${color}${entry.level.toUpperCase()}${reset}`)\n parts.push(entry.message)\n\n if (entry.context && Object.keys(entry.context).length > 0) {\n parts.push(JSON.stringify(entry.context, null, 2))\n }\n\n if (entry.error) {\n parts.push(`\\n${color}Error: ${entry.error.message}${reset}`)\n if (entry.error.stack) {\n parts.push(entry.error.stack)\n }\n }\n\n return parts.join(' ')\n }\n\n /**\n * Create child logger with additional context\n */\n child(context: LogContext): Logger {\n const childLogger = new Logger(this.config)\n childLogger.setContext({ ...this.context, ...context })\n // Share parent's extra handlers so DB transport applies to child loggers too\n for (const handler of this.extraHandlers) {\n childLogger.addLogHandler(handler)\n }\n return childLogger\n }\n}\n\n/**\n * Default logger instance\n */\nexport const logger = new Logger({\n level: (process.env.LOG_LEVEL as LogLevel) || 'info',\n pretty: process.env.NODE_ENV !== 'production',\n})\n\n/**\n * Create logger with context\n */\nexport function createLogger(context: LogContext): Logger {\n return logger.child(context)\n}\n\n/**\n * Error logger\n */\nexport function logError(error: Error, context?: LogContext): void {\n logger.error(error.message, error, context)\n}\n\n/**\n * Audit logger for security-sensitive operations\n */\nexport function logAudit(action: string, context?: LogContext): void {\n logger.info(`Audit: ${action}`, {\n ...context,\n audit: true,\n action,\n })\n}\n\n/**\n * Database query logger\n */\nexport function logQuery(query: string, duration: number, context?: LogContext): void {\n logger.debug('Database query', {\n ...context,\n query,\n duration,\n })\n\n if (duration > 1000) {\n logger.warn('Slow query detected', {\n ...context,\n query,\n duration,\n })\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/validation/password-schema.ts"],"sourcesContent":["import { z } from 'zod'\n\n/**\n * Password validation schema\n *\n * Requirements:\n * - Minimum 8 characters\n * - Maximum 128 characters\n * - At least one uppercase letter\n * - At least one lowercase letter\n * - At least one number\n */\nexport const passwordSchema = z\n .string()\n .min(8, 'Password must be at least 8 characters')\n .max(128, 'Password is too long')\n .regex(/[A-Z]/, 'Password must contain at least one uppercase letter')\n .regex(/[a-z]/, 'Password must contain at least one lowercase letter')\n .regex(/[0-9]/, 'Password must contain at least one number')\n\nexport type Password = z.infer<typeof passwordSchema>\n"],"mappings":";AAAA,SAAS,SAAS;AAYX,IAAM,iBAAiB,EAC3B,OAAO,EACP,IAAI,GAAG,wCAAwC,EAC/C,IAAI,KAAK,sBAAsB,EAC/B,MAAM,SAAS,qDAAqD,EACpE,MAAM,SAAS,qDAAqD,EACpE,MAAM,SAAS,2CAA2C;","names":[]}