@spfn/core 0.3.0-beta.16 → 0.3.0-beta.18
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/env/index.d.ts +1 -1
- package/dist/env/index.js.map +1 -1
- package/dist/nextjs/index.d.ts +17 -4
- package/dist/nextjs/index.js +85 -48
- package/dist/nextjs/index.js.map +1 -1
- package/dist/nextjs/server.d.ts +1 -1
- package/dist/{registry-DZHXQ134.d.ts → registry-C2KQCh0m.d.ts} +7 -6
- package/dist/server/index.d.ts +1 -1
- package/dist/{types-QoyjaJWw.d.ts → types-CxhPH4s_.d.ts} +2 -2
- package/package.json +2 -2
- package/src/env/README.md +9 -7
- package/src/nextjs/README.md +39 -7
package/dist/env/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { E as EnvLayer, a as EnvRegistry, b as EnvSchemaCollection, c as EnvValidationResult, d as EnvVarSchema, I as InferEnvType, e as createEnvRegistry, f as defineEnvSchema, g as envBoolean, h as envEnum, i as envJson, j as envNumber, k as envSecret, l as envString, m as envUrl, n as isClientAccessible, o as isNextjsAccessible, p as isServerOnly, q as isSpfnServerOnly, v as validateAllEnv } from '../registry-
|
|
1
|
+
export { E as EnvLayer, a as EnvRegistry, b as EnvSchemaCollection, c as EnvValidationResult, d as EnvVarSchema, I as InferEnvType, e as createEnvRegistry, f as defineEnvSchema, g as envBoolean, h as envEnum, i as envJson, j as envNumber, k as envSecret, l as envString, m as envUrl, n as isClientAccessible, o as isNextjsAccessible, p as isServerOnly, q as isSpfnServerOnly, v as validateAllEnv } from '../registry-C2KQCh0m.js';
|
|
2
2
|
export { L as LogLevel } from '../types-BGl4QL1w.js';
|
|
3
3
|
|
|
4
4
|
/**
|
package/dist/env/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/env/validator.ts","../../src/env/schema.ts","../../src/env/registry.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Environment Variable Management - Parsers\n *\n * Parser functions that transform and validate environment variable strings.\n * All parsers follow the pattern: (value: string) => T or throw Error\n *\n * An error message never contains the input. Messages are logged — by the\n * registry and by the server's boot check — and the input may be a secret or a\n * URL carrying credentials. Describe the expected shape instead.\n */\n\n// ============================================================================\n// Core Types\n// ============================================================================\n\n/**\n * Parser function that transforms and validates a string value\n * @throws Error if validation fails\n */\nexport type Parser<T> = (value: string) => T;\n\n// ============================================================================\n// String Parsers\n// ============================================================================\n\n/**\n * Parse a non-empty string\n *\n * @param value - Value to parse\n * @returns Trimmed string\n * @throws Error if string is empty after trimming\n *\n * @example\n * ```typescript\n * const name = getEnvVar('APP_NAME', {\n * validator: parseString,\n * });\n * ```\n */\nexport function parseString(value: string): string\n{\n const trimmed = value.trim();\n\n if (trimmed.length === 0)\n {\n throw new Error('Value cannot be empty');\n }\n\n return trimmed;\n}\n\n/**\n * Create a string parser with validation rules\n *\n * @param options - Validation options\n * @returns Parser function\n *\n * @example\n * ```typescript\n * const apiKey = getEnvVar('API_KEY', {\n * validator: createStringParser({\n * minLength: 32,\n * pattern: /^[A-Za-z0-9_-]+$/,\n * }),\n * });\n * ```\n */\nexport function createStringParser(\n options: {\n minLength?: number;\n maxLength?: number;\n pattern?: RegExp;\n trim?: boolean;\n } = {},\n): Parser<string>\n{\n return (value: string): string =>\n {\n const { minLength, maxLength, pattern, trim = true } = options;\n\n let result = trim ? value.trim() : value;\n\n // Empty check\n if (result.length === 0)\n {\n throw new Error('Value cannot be empty');\n }\n\n // Length validation\n if (minLength !== undefined && result.length < minLength)\n {\n throw new Error(`Must be at least ${minLength} characters long (current: ${result.length})`);\n }\n\n if (maxLength !== undefined && result.length > maxLength)\n {\n throw new Error(`Must be at most ${maxLength} characters long (current: ${result.length})`);\n }\n\n // Pattern validation\n if (pattern && !pattern.test(result))\n {\n throw new Error(`Must match pattern ${pattern}`);\n }\n\n return result;\n };\n}\n\n// ============================================================================\n// Boolean Parser\n// ============================================================================\n\n/**\n * Parse a boolean environment variable\n *\n * Accepts: 'true', '1', 'yes' (case-insensitive) → true\n * 'false', '0', 'no' (case-insensitive) → false\n *\n * @param value - Value to parse\n * @returns Boolean value\n * @throws Error if value is not a valid boolean string\n *\n * @example\n * ```typescript\n * const debug = getEnvVar('DEBUG', {\n * default: 'false',\n * validator: parseBoolean,\n * });\n * ```\n */\nexport function parseBoolean(value: string): boolean\n{\n const normalized = value.toLowerCase().trim();\n\n if (['true', '1', 'yes'].includes(normalized))\n {\n return true;\n }\n\n if (['false', '0', 'no'].includes(normalized))\n {\n return false;\n }\n\n throw new Error('Must be a boolean value (true/false, 1/0, yes/no)');\n}\n\n// ============================================================================\n// Number Parsers\n// ============================================================================\n\n/**\n * Parse and validate number\n *\n * @param value - Value to parse\n * @param options - Validation options\n * @returns Parsed number\n * @throws Error if invalid number or constraint violation\n *\n * @example\n * ```typescript\n * const port = getEnvVar('PORT', {\n * default: '3000',\n * validator: (val) => parseNumber(val, { min: 1, max: 65535, integer: true }),\n * });\n * ```\n */\nexport function parseNumber(\n value: string,\n options: { min?: number; max?: number; integer?: boolean } = {},\n): number\n{\n const { min, max, integer = false } = options;\n\n // Reject empty strings\n if (value.trim() === '')\n {\n throw new Error('Value cannot be empty');\n }\n\n const num = Number(value);\n\n if (isNaN(num))\n {\n throw new Error('Must be a valid number');\n }\n\n if (integer && !Number.isInteger(num))\n {\n throw new Error('Must be an integer');\n }\n\n if (min !== undefined && num < min)\n {\n throw new Error(`Must be at least ${min}`);\n }\n\n if (max !== undefined && num > max)\n {\n throw new Error(`Must be at most ${max}`);\n }\n\n return num;\n}\n\n/**\n * Create a number parser with specific constraints\n *\n * @param options - Validation constraints\n * @returns Parser function\n *\n * @example\n * ```typescript\n * const port = getEnvVar('PORT', {\n * default: '3000',\n * validator: createNumberParser({ min: 1, max: 65535, integer: true }),\n * });\n * ```\n */\nexport function createNumberParser(\n options: { min?: number; max?: number; integer?: boolean } = {},\n): Parser<number>\n{\n return (value: string) => parseNumber(value, options);\n}\n\n/**\n * Parse integer with optional constraints\n *\n * @param value - Value to parse\n * @param options - Min/max constraints\n * @returns Parsed integer\n * @throws Error if invalid or out of range\n *\n * @example\n * ```typescript\n * const retries = getEnvVar('MAX_RETRIES', {\n * default: '3',\n * validator: (val) => parseInteger(val, { min: 1, max: 10 }),\n * });\n * ```\n */\nexport function parseInteger(\n value: string,\n options: { min?: number; max?: number } = {},\n): number\n{\n return parseNumber(value, { ...options, integer: true });\n}\n\n/**\n * Parse float/decimal number with optional constraints\n *\n * @param value - Value to parse\n * @param options - Min/max constraints\n * @returns Parsed decimal number\n * @throws Error if invalid or out of range\n *\n * @example\n * ```typescript\n * const ratio = getEnvVar('CACHE_RATIO', {\n * default: '0.75',\n * validator: (val) => parseDecimal(val, { min: 0, max: 1 }),\n * });\n * ```\n */\nexport function parseDecimal(\n value: string,\n options: { min?: number; max?: number } = {},\n): number\n{\n return parseNumber(value, { ...options, integer: false });\n}\n\n// ============================================================================\n// URL Parsers\n// ============================================================================\n\n/**\n * Parse and validate URL\n *\n * @param value - Value to parse\n * @param options - Validation options\n * @returns Validated URL string\n * @throws Error if invalid URL or protocol mismatch\n *\n * @example\n * ```typescript\n * const apiUrl = getEnvVar('API_URL', {\n * validator: (val) => parseUrl(val, { protocol: 'https' }),\n * });\n * ```\n */\nexport function parseUrl(\n value: string,\n options: { protocol?: 'http' | 'https' | 'any' } = {},\n): string\n{\n const { protocol = 'any' } = options;\n\n // Parse URL (may throw TypeError)\n let url: URL;\n try\n {\n url = new URL(value);\n }\n catch (error)\n {\n if (error instanceof TypeError)\n {\n throw new Error('Invalid URL');\n }\n throw error;\n }\n\n // Validate protocol\n if (protocol === 'http' && url.protocol !== 'http:')\n {\n throw new Error(`URL must use HTTP protocol, got ${url.protocol}`);\n }\n\n if (protocol === 'https' && url.protocol !== 'https:')\n {\n throw new Error(`URL must use HTTPS protocol, got ${url.protocol}`);\n }\n\n return value;\n}\n\n/**\n * Create a URL parser with specific protocol requirement\n *\n * @param protocol - Required protocol ('http', 'https', or 'any')\n * @returns Parser function\n *\n * @example\n * ```typescript\n * const apiUrl = getEnvVar('API_URL', {\n * validator: createUrlParser('https'),\n * });\n * ```\n */\nexport function createUrlParser(protocol: 'http' | 'https' | 'any' = 'any'): Parser<string>\n{\n return (value: string) => parseUrl(value, { protocol });\n}\n\n/**\n * Parse PostgreSQL connection string\n *\n * @param value - Value to parse\n * @returns Validated PostgreSQL URL string\n * @throws Error if invalid PostgreSQL URL\n *\n * @example\n * ```typescript\n * const dbUrl = getEnvVar('DATABASE_URL', {\n * required: true,\n * validator: parsePostgresUrl,\n * });\n * ```\n */\nexport function parsePostgresUrl(value: string): string\n{\n // Parse URL (may throw TypeError)\n let url: URL;\n try\n {\n url = new URL(value);\n }\n catch (error)\n {\n if (error instanceof TypeError)\n {\n // Never echo the value — these URLs carry embedded credentials\n throw new Error('Invalid PostgreSQL URL');\n }\n throw error;\n }\n\n // Validate protocol\n if (url.protocol !== 'postgres:' && url.protocol !== 'postgresql:')\n {\n throw new Error(\n `Must be a PostgreSQL URL (postgres:// or postgresql://), got ${url.protocol}`,\n );\n }\n\n return value;\n}\n\n/**\n * Parse Redis connection string\n *\n * @param value - Value to parse\n * @returns Validated Redis URL string\n * @throws Error if invalid Redis URL\n *\n * @example\n * ```typescript\n * const redisUrl = getEnvVar('REDIS_URL', {\n * required: true,\n * validator: parseRedisUrl,\n * });\n * ```\n */\nexport function parseRedisUrl(value: string): string\n{\n // Parse URL (may throw TypeError)\n let url: URL;\n try\n {\n url = new URL(value);\n }\n catch (error)\n {\n if (error instanceof TypeError)\n {\n // Never echo the value — these URLs carry embedded credentials\n throw new Error('Invalid Redis URL');\n }\n throw error;\n }\n\n // Validate protocol\n if (url.protocol !== 'redis:' && url.protocol !== 'rediss:')\n {\n throw new Error(\n `Must be a Redis URL (redis:// or rediss://), got ${url.protocol}`,\n );\n }\n\n return value;\n}\n\n// ============================================================================\n// Enum Parser\n// ============================================================================\n\n/**\n * Parse and validate enum value\n *\n * @param value - Value to parse\n * @param allowed - Array of allowed values\n * @param caseInsensitive - Whether to perform case-insensitive comparison\n * @returns Validated enum value\n * @throws Error if value not in allowed list\n *\n * @example\n * ```typescript\n * const env = getEnvVar('NODE_ENV', {\n * validator: (val) => parseEnum(val, ['development', 'production', 'test']),\n * });\n * ```\n */\nexport function parseEnum(\n value: string,\n allowed: string[],\n caseInsensitive = false,\n): string\n{\n if (caseInsensitive)\n {\n const normalizedValue = value.toLowerCase();\n const normalizedAllowed = allowed.map((v) => v.toLowerCase());\n const index = normalizedAllowed.indexOf(normalizedValue);\n\n if (index === -1)\n {\n throw new Error(\n `Must be one of [${allowed.join(', ')}]`,\n );\n }\n\n return allowed[index]; // Return original case from allowed list\n }\n\n if (!allowed.includes(value))\n {\n throw new Error(\n `Must be one of [${allowed.join(', ')}]`,\n );\n }\n\n return value;\n}\n\n/**\n * Create an enum parser with specific allowed values\n *\n * @param allowed - Array of allowed values\n * @param caseInsensitive - Whether to perform case-insensitive comparison\n * @returns Parser function\n *\n * @example\n * ```typescript\n * const logLevel = getEnvVar('LOG_LEVEL', {\n * default: 'info',\n * validator: createEnumParser(['debug', 'info', 'warn', 'error']),\n * });\n * ```\n */\nexport function createEnumParser(allowed: string[], caseInsensitive = false): Parser<string>\n{\n return (value: string) => parseEnum(value, allowed, caseInsensitive);\n}\n\n// ============================================================================\n// JSON Parser\n// ============================================================================\n\n/**\n * Parse JSON string\n *\n * @param value - JSON string to parse\n * @returns Parsed JSON value\n * @throws Error if invalid JSON\n *\n * @example\n * ```typescript\n * const config = getEnvVar('CONFIG_JSON', {\n * validator: parseJson,\n * });\n * ```\n */\nexport function parseJson<T = any>(value: string): T\n{\n try\n {\n return JSON.parse(value) as T;\n }\n catch\n {\n // JSON.parse quotes the input in its message; the input may be a secret.\n throw new Error('Invalid JSON');\n }\n}\n\n/**\n * Create a typed JSON parser\n *\n * @returns Parser function\n *\n * @example\n * ```typescript\n * interface Config {\n * host: string;\n * port: number;\n * }\n *\n * const config = getEnvVar('CONFIG_JSON', {\n * validator: createJsonParser<Config>(),\n * });\n * ```\n */\nexport function createJsonParser<T>(): Parser<T>\n{\n return (value: string) => parseJson<T>(value);\n}\n\n// ============================================================================\n// Array Parser\n// ============================================================================\n\n/**\n * Parse comma-separated values into array\n *\n * @param value - Comma-separated string\n * @param options - Parser options\n * @returns Array of strings\n *\n * @example\n * ```typescript\n * const hosts = getEnvVar('ALLOWED_HOSTS', {\n * validator: parseArray,\n * });\n * // \"localhost,example.com,api.example.com\" → ['localhost', 'example.com', 'api.example.com']\n * ```\n */\nexport function parseArray(\n value: string,\n options: {\n separator?: string;\n trim?: boolean;\n filter?: (item: string) => boolean;\n } = {},\n): string[]\n{\n const { separator = ',', trim = true, filter } = options;\n\n if (value.trim() === '')\n {\n return [];\n }\n\n let items = value.split(separator);\n\n if (trim)\n {\n items = items.map((item) => item.trim());\n }\n\n if (filter)\n {\n items = items.filter(filter);\n }\n\n return items;\n}\n\n/**\n * Create an array parser with item parser\n *\n * @param itemParser - Parser to apply to each array item\n * @param options - Array parsing options\n * @returns Parser function\n *\n * @example\n * ```typescript\n * // Parse comma-separated ports\n * const ports = getEnvVar('PORTS', {\n * validator: createArrayParser(\n * createNumberParser({ min: 1, max: 65535, integer: true })\n * ),\n * });\n * // \"3000,4000,5000\" → [3000, 4000, 5000]\n * ```\n */\nexport function createArrayParser<T>(\n itemParser: Parser<T>,\n options: { separator?: string } = {},\n): Parser<T[]>\n{\n return (value: string): T[] =>\n {\n const items = parseArray(value, options);\n\n return items.map((item, index) =>\n {\n try\n {\n return itemParser(item);\n }\n catch (error)\n {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`Invalid item at index ${index}: ${message}`);\n }\n });\n };\n}\n\n// ============================================================================\n// Secure Secret Parser\n// ============================================================================\n\n/**\n * Calculate Shannon entropy of a string\n * Returns entropy in bits per character\n *\n * @param str - String to calculate entropy for\n * @returns Entropy value (0 to ~6.6 bits for printable ASCII)\n *\n * @example\n * ```typescript\n * const entropy = calculateEntropy('my-secret-key');\n * // Higher entropy = more random\n * // - Random lowercase: ~4.7 bits/char\n * // - Random alphanumeric: ~5.2 bits/char\n * // - Random printable ASCII: ~6.6 bits/char\n * // - \"aaaaaaa...\": ~0 bits/char\n * ```\n */\nfunction calculateEntropy(str: string): number\n{\n const len = str.length;\n const frequencies = new Map<string, number>();\n\n // Count character frequencies\n for (const char of str)\n {\n frequencies.set(char, (frequencies.get(char) || 0) + 1);\n }\n\n // Calculate Shannon entropy\n let entropy = 0;\n for (const count of frequencies.values())\n {\n const probability = count / len;\n entropy -= probability * Math.log2(probability);\n }\n\n return entropy;\n}\n\n/**\n * Create a secure secret parser with entropy validation\n *\n * Validates cryptographic secrets for sufficient length, character diversity, and randomness.\n * Uses Shannon entropy to measure randomness quality.\n *\n * @param options - Validation options\n * @returns Parser function\n *\n * @example\n * ```typescript\n * const sessionSecret = getEnvVar('SESSION_SECRET', {\n * validator: createSecureSecretParser({\n * minLength: 32, // Minimum 256-bit\n * minUniqueChars: 16, // Character diversity\n * minEntropy: 3.5, // Shannon entropy (bits/char)\n * }),\n * });\n * ```\n */\nexport function createSecureSecretParser(\n options: {\n minLength?: number;\n minUniqueChars?: number;\n minEntropy?: number;\n } = {},\n): Parser<string>\n{\n const {\n minLength = 32,\n minUniqueChars = 16,\n minEntropy = 3.5,\n } = options;\n\n return (value: string): string =>\n {\n const length = value.length;\n const uniqueChars = new Set(value).size;\n const entropy = calculateEntropy(value);\n\n // Check length (minimum for cryptographic strength)\n if (length < minLength)\n {\n throw new Error(\n `Secret too short: ${length} characters (minimum: ${minLength})`,\n );\n }\n\n // Check unique character diversity\n if (uniqueChars < minUniqueChars)\n {\n throw new Error(\n `Secret has low diversity: ${uniqueChars} unique characters (minimum: ${minUniqueChars})`,\n );\n }\n\n // Check Shannon entropy (randomness quality)\n // Reference values:\n // - Random lowercase: ~4.7 bits/char\n // - Random alphanumeric: ~5.2 bits/char\n // - Random printable ASCII: ~6.6 bits/char\n // - \"aaaaaaa...\": ~0 bits/char\n // - \"abcabcabc...\": ~1.58 bits/char\n if (entropy < minEntropy)\n {\n throw new Error(\n `Secret has low entropy: ${entropy.toFixed(2)} bits/char (minimum: ${minEntropy}). Use a more random secret.`,\n );\n }\n\n return value;\n };\n}\n\n// ============================================================================\n// Password Parser\n// ============================================================================\n\n/**\n * Create a password strength parser\n *\n * Validates password strength based on configurable requirements.\n * Useful for enforcing password policies in environment variables or user input.\n *\n * @param options - Validation options\n * @returns Parser function\n *\n * @example\n * ```typescript\n * const adminPassword = getEnvVar('ADMIN_PASSWORD', {\n * validator: createPasswordParser({\n * minLength: 12,\n * requireUppercase: true,\n * requireLowercase: true,\n * requireNumber: true,\n * requireSpecial: true,\n * }),\n * });\n * ```\n */\nexport function createPasswordParser(\n options: {\n minLength?: number;\n requireUppercase?: boolean;\n requireLowercase?: boolean;\n requireNumber?: boolean;\n requireSpecial?: boolean;\n } = {},\n): Parser<string>\n{\n const {\n minLength = 8,\n requireUppercase = true,\n requireLowercase = true,\n requireNumber = true,\n requireSpecial = true,\n } = options;\n\n return (value: string): string =>\n {\n const errors: string[] = [];\n\n // Length check\n if (value.length < minLength)\n {\n errors.push(`Must be at least ${minLength} characters`);\n }\n\n // Uppercase check\n if (requireUppercase && !/[A-Z]/.test(value))\n {\n errors.push('Must contain at least one uppercase letter');\n }\n\n // Lowercase check\n if (requireLowercase && !/[a-z]/.test(value))\n {\n errors.push('Must contain at least one lowercase letter');\n }\n\n // Number check\n if (requireNumber && !/[0-9]/.test(value))\n {\n errors.push('Must contain at least one number');\n }\n\n // Special character check\n if (requireSpecial && !/[^A-Za-z0-9]/.test(value))\n {\n errors.push('Must contain at least one special character');\n }\n\n if (errors.length > 0)\n {\n throw new Error(`Password validation failed: ${errors.join(', ')}`);\n }\n\n return value;\n };\n}\n\n// ============================================================================\n// Parser Composition\n// ============================================================================\n\n/**\n * Chain multiple parsers sequentially\n *\n * Each parser receives the output of the previous parser.\n * Useful for multi-step validation/transformation.\n *\n * @param parsers - Array of parser functions\n * @returns Combined parser function\n *\n * @example\n * ```typescript\n * const apiKey = getEnvVar('API_KEY', {\n * validator: chain(\n * parseString,\n * createStringParser({ minLength: 32, pattern: /^[A-Za-z0-9_-]+$/ }),\n * ),\n * });\n * ```\n */\nexport function chain<T>(...parsers: Array<Parser<T>>): Parser<T>\n{\n return (value: string): T =>\n {\n let result = value as any;\n\n for (const parser of parsers)\n {\n result = parser(result);\n }\n\n return result;\n };\n}\n\n/**\n * Apply parser with fallback value\n *\n * If parser throws, returns fallback instead.\n * Useful for optional environment variables with complex parsing.\n *\n * @param parser - Parser to attempt\n * @param fallback - Fallback value if parsing fails\n * @returns Parser function\n *\n * @example\n * ```typescript\n * const config = getEnvVar('CONFIG_JSON', {\n * validator: withFallback(parseJson, { host: 'localhost', port: 3000 }),\n * });\n * ```\n */\nexport function withFallback<T>(parser: Parser<T>, fallback: T): Parser<T>\n{\n return (value: string): T =>\n {\n try\n {\n return parser(value);\n }\n catch\n {\n return fallback;\n }\n };\n}\n\n/**\n * Make parser optional\n *\n * Returns undefined for empty strings instead of throwing.\n *\n * @param parser - Parser to make optional\n * @returns Parser function that returns T | undefined\n *\n * @example\n * ```typescript\n * const redisUrl = getEnvVar('REDIS_URL', {\n * validator: optional(parseRedisUrl),\n * });\n * // Empty string → undefined\n * // Valid URL → parsed URL\n * // Invalid URL → throws\n * ```\n */\nexport function optional<T>(parser: Parser<T>): Parser<T | undefined>\n{\n return (value: string): T | undefined =>\n {\n if (value.trim() === '')\n {\n return undefined;\n }\n\n return parser(value);\n };\n}\n","/**\n * Environment Variable Schema Definition System\n *\n * 환경변수에 메타데이터를 정의하여 중앙 관리, 문서화, 검증을 지원합니다.\n *\n * @example\n * ```typescript\n * const schema = defineEnvSchema({\n * DATABASE_URL: envUrl({\n * description: 'Database connection',\n * required: true,\n * validator: parsePostgresUrl,\n * sensitive: true,\n * })\n * });\n * ```\n *\n * @module env/schema\n */\n\nimport { parseBoolean, parseNumber, parseJson } from './validator';\n\n/**\n * 환경변수 스키마 정의\n */\nexport interface EnvVarSchema<T = string>\n{\n /** 환경변수 키 */\n key: string;\n\n /** 설명 (목적, 사용처) */\n description: string;\n\n /** 타입 */\n type: 'string' | 'number' | 'boolean' | 'url' | 'enum' | 'json';\n\n /** 필수 여부 */\n required?: boolean;\n\n /** 기본값 */\n default?: T;\n\n /** 검증/변환 함수 */\n validator?: (value: string) => T;\n\n // === 검증 옵션 ===\n\n /** Fallback 환경변수 키들 (backward compatibility) */\n fallbackKeys?: string[];\n\n /** 최소 길이 (문자열 타입) */\n minLength?: number;\n\n // === 메타데이터 ===\n\n /** 민감정보 여부 (로깅 시 마스킹) */\n sensitive?: boolean;\n\n /**\n * 자동 생성 방식 — 값이 미지정이면 프로비저닝(secrets 관리 CLI)이 이 방식으로 생성한다.\n * 외부에서 받아야 하는 시크릿(API 키 등)은 generate를 비워 사용자 입력으로 둔다.\n */\n generate?: 'hex32' | 'hex64' | 'uuid' | 'base64url32';\n\n /** 예시 값들 (타입과 일치해야 함) */\n examples?: T[];\n\n // === 파일 분리 ===\n\n /**\n * Next.js 프로세스에서 사용 여부\n *\n * - true: .env.local에 존재해야 함 (Next.js 서버 컴포넌트에서 접근 가능)\n * - false: .env.server에만 존재해야 함 (SPFN 서버에서만 접근)\n *\n * @default NEXT_PUBLIC_* 이면 true, 아니면 false\n */\n nextjs?: boolean;\n\n // === Deployment layer ===\n\n /**\n * Which layer of a deployment supplies the value.\n *\n * - `'environment'`: one value shared by every instance of an environment\n * (a third-party API key) — kept in `secrets/<env>.enc.json`.\n * - `'instance'`: a value that differs per instance — kept in\n * `secrets/<env>.<instance>.enc.json` or computed by the deploy script\n * and passed to `spfn secret export --with <file>`.\n *\n * `spfn secret export` fails when a value arrives from the wrong layer.\n *\n * @default 'environment'\n */\n layer?: EnvLayer;\n\n // === Audit ===\n\n /**\n * Files that read this variable under a computed key, as paths relative to\n * the project root — e.g. `['src/server/config/providers.ts']`.\n *\n * `spfn env audit` counts a name as read when it appears as a property\n * access or a string literal; a name only ever reached through\n * `env[name]` needs this instead. The name passes when every listed file\n * exists and is scanned. The runtime ignores `readBy`.\n */\n readBy?: string[];\n}\n\n/**\n * The layer of a deployment an env value comes from — see {@link EnvVarSchema.layer}.\n */\nexport type EnvLayer = 'environment' | 'instance';\n\n/**\n * 스키마 컬렉션 타입\n */\nexport type EnvSchemaCollection = Record<string, EnvVarSchema<any>>;\n\n/**\n * Helper type: Check if field has default value\n */\ntype HasDefault<T> = T extends { default: any } ? true : false;\n\n/**\n * Helper type: Check if field is explicitly required\n */\ntype IsRequired<T> = T extends { required: true } ? true : false;\n\n/**\n * Helper type: Check if field should be required (has default OR required: true)\n */\ntype ShouldBeRequired<T> = HasDefault<T> extends true ? true : IsRequired<T>;\n\n/**\n * 스키마로부터 타입 추출\n *\n * required: true 또는 default가 있는 필드 → 필수\n * required: false 또는 미지정 → optional (| undefined)\n */\nexport type InferEnvType<T extends EnvSchemaCollection> = {\n // Required fields (required: true OR has default)\n [K in keyof T as ShouldBeRequired<T[K]> extends true ? K : never]:\n T[K] extends EnvVarSchema<infer U> ? U : string;\n} & {\n // Optional fields (required: false OR not specified)\n [K in keyof T as ShouldBeRequired<T[K]> extends true ? never : K]?:\n T[K] extends EnvVarSchema<infer U> ? U | undefined : string | undefined;\n};\n\n/**\n * 스키마 정의 헬퍼 (타입 추론 지원)\n *\n * Automatically fills in the `key` property from object keys. Every other\n * field passes through as written — an entry without `layer` is an\n * `'environment'` value.\n *\n * @example\n * ```typescript\n * const schema = defineEnvSchema({\n * DATABASE_URL: envString({ description: 'Database URL', required: true })\n * });\n * // Automatically adds key: 'DATABASE_URL'\n * ```\n */\nexport function defineEnvSchema<T extends Record<string, any>>(\n schema: T,\n): { [K in keyof T]: T[K] & { key: K } }\n{\n const result: any = {};\n\n for (const key in schema)\n {\n result[key] = {\n ...schema[key],\n key,\n };\n }\n\n return result;\n}\n\n/**\n * 문자열 스키마 헬퍼\n *\n * @example\n * ```typescript\n * const schema = {\n * API_KEY: {\n * ...envString({\n * description: 'API authentication key',\n * required: true,\n * sensitive: true,\n * }),\n * key: 'API_KEY',\n * }\n * };\n * ```\n */\nexport function envString<T extends Omit<EnvVarSchema, 'key' | 'type'>>(\n options: T,\n): T & { type: 'string'; validator: (value: string) => string }\n{\n return {\n ...options,\n type: 'string',\n validator: options.validator || ((value: string) => value),\n };\n}\n\n/**\n * 시크릿 스키마 헬퍼 — sensitive:true 자동.\n *\n * generate 지정 시 프로비저닝(secrets CLI)이 값을 자동 생성, 미지정이면 외부 입력(사용자가 붙여넣음).\n * 이로써 env.config.ts가 \"설정(envString) vs 외부 시크릿(envSecret) vs 자동 시크릿(envSecret+generate)\"을\n * 선언적으로 가른다 — CLI·프레임워크·프로비저닝이 한 원천을 읽는다.\n *\n * @example\n * ```typescript\n * SPFN_AUTH_SESSION_SECRET: envSecret({ description: '세션 서명 키', generate: 'base64url32' }),\n * OPENAI_API_KEY: envSecret({ description: 'OpenAI API 키' }), // generate 없음 → 외부 입력\n * ```\n */\nexport function envSecret<T extends Omit<EnvVarSchema, 'key' | 'type' | 'sensitive'>>(\n options: T,\n): T & { type: 'string'; sensitive: true; validator: (value: string) => string }\n{\n return {\n ...options,\n type: 'string',\n sensitive: true,\n validator: options.validator || ((value: string) => value),\n };\n}\n\n/**\n * 숫자 스키마 헬퍼\n *\n * @example\n * ```typescript\n * const schema = {\n * PORT: {\n * ...envNumber({\n * description: 'Server port',\n * default: 3000,\n * validator: createNumberParser({ min: 1, max: 65535 }),\n * }),\n * key: 'PORT',\n * }\n * };\n * ```\n */\nexport function envNumber<T extends Omit<EnvVarSchema<number>, 'key' | 'type'>>(\n options: T,\n): T & { type: 'number'; validator: (value: string) => number }\n{\n return {\n ...options,\n type: 'number',\n validator: options.validator || parseNumber,\n };\n}\n\n/**\n * Boolean 스키마 헬퍼\n *\n * @example\n * ```typescript\n * const schema = {\n * DEBUG: {\n * ...envBoolean({\n * description: 'Enable debug mode',\n * default: false,\n * }),\n * key: 'DEBUG',\n * }\n * };\n * ```\n */\nexport function envBoolean<T extends Omit<EnvVarSchema<boolean>, 'key' | 'type'>>(\n options: T,\n): T & { type: 'boolean'; validator: (value: string) => boolean }\n{\n return {\n ...options,\n type: 'boolean',\n validator: options.validator || parseBoolean,\n };\n}\n\n/**\n * URL 스키마 헬퍼\n *\n * @example\n * ```typescript\n * const schema = {\n * DATABASE_URL: {\n * ...envUrl({\n * description: 'Database connection URL',\n * required: true,\n * validator: parsePostgresUrl,\n * }),\n * key: 'DATABASE_URL',\n * }\n * };\n * ```\n */\nexport function envUrl<T extends Omit<EnvVarSchema, 'key' | 'type'>>(\n options: T,\n): T & { type: 'url' }\n{\n return {\n ...options,\n type: 'url',\n };\n}\n\n/**\n * Enum 스키마 헬퍼\n *\n * @example\n * ```typescript\n * const schema = {\n * LOG_LEVEL: {\n * ...envEnum(['debug', 'info', 'warn', 'error'] as const, {\n * description: 'Logging level',\n * default: 'info',\n * }),\n * key: 'LOG_LEVEL',\n * }\n * };\n * ```\n */\nexport function envEnum<\n T extends string,\n O extends Omit<EnvVarSchema<T>, 'key' | 'type' | 'validator'>,\n>(\n allowed: readonly T[],\n options: O,\n): O & { type: 'enum'; validator: (val: string) => T }\n{\n return {\n ...options,\n type: 'enum',\n validator: (val: string): T =>\n {\n if (!allowed.includes(val as T))\n {\n throw new Error(`Must be one of: ${allowed.join(', ')}`);\n }\n\n return val as T;\n },\n };\n}\n\n/**\n * JSON 스키마 헬퍼\n *\n * @example\n * ```typescript\n * const schema = {\n * CONFIG_JSON: {\n * ...envJson<{ host: string; port: number }>({\n * description: 'JSON configuration object',\n * required: true,\n * }),\n * key: 'CONFIG_JSON',\n * }\n * };\n * ```\n */\nexport function envJson<\n T = any,\n O extends Omit<EnvVarSchema<T>, 'key' | 'type' | 'validator'> = Omit<EnvVarSchema<T>, 'key' | 'type' | 'validator'>,\n>(\n options: O,\n): O & { type: 'json'; validator: (val: string) => T }\n{\n return {\n ...options,\n type: 'json',\n validator: (val: string) => parseJson<T>(val),\n };\n}\n\n/**\n * 환경변수가 클라이언트에서 접근 가능한지 확인\n * (NEXT_PUBLIC_ 접두사로 판단)\n *\n * @param key - 환경변수 키\n * @returns 클라이언트에서 접근 가능하면 true\n *\n * @example\n * ```typescript\n * isClientAccessible('NEXT_PUBLIC_API_URL'); // true\n * isClientAccessible('DATABASE_URL'); // false\n * ```\n */\nexport function isClientAccessible(key: string): boolean\n{\n return key.startsWith('NEXT_PUBLIC_');\n}\n\n/**\n * 환경변수가 서버 전용인지 확인\n * (NEXT_PUBLIC_ 접두사가 없으면 서버 전용)\n *\n * @param key - 환경변수 키\n * @returns 서버 전용이면 true\n *\n * @example\n * ```typescript\n * isServerOnly('DATABASE_URL'); // true\n * isServerOnly('NEXT_PUBLIC_API_URL'); // false\n * ```\n */\nexport function isServerOnly(key: string): boolean\n{\n return !isClientAccessible(key);\n}\n\n/**\n * 스키마의 nextjs 옵션 값 결정\n *\n * 명시적으로 지정되지 않은 경우:\n * - NEXT_PUBLIC_* → true\n * - 그 외 → false\n *\n * @param schema - 환경변수 스키마\n * @returns Next.js 프로세스에서 사용 가능 여부\n */\nexport function isNextjsAccessible(schema: EnvVarSchema): boolean\n{\n if (schema.nextjs !== undefined)\n {\n return schema.nextjs;\n }\n\n return isClientAccessible(schema.key);\n}\n\n/**\n * 스키마가 SPFN 서버 전용인지 확인\n *\n * @param schema - 환경변수 스키마\n * @returns SPFN 서버에서만 사용되면 true\n */\nexport function isSpfnServerOnly(schema: EnvVarSchema): boolean\n{\n return !isNextjsAccessible(schema);\n}\n","/**\n * Environment Variable Registry\n *\n * 환경변수 스키마를 등록하고 타입 안전하게 접근할 수 있는 레지스트리\n *\n * @example\n * ```typescript\n * const schema = defineEnvSchema({\n * DATABASE_URL: envString({ description: 'Database URL', required: true })\n * });\n *\n * const registry = createEnvRegistry(schema);\n * const env = registry.validate(); // 검증 + env 반환\n * console.log(env.DATABASE_URL);\n * ```\n *\n * @module env/registry\n */\nimport type { EnvVarSchema, EnvSchemaCollection, InferEnvType } from './schema';\nimport { isClientAccessible } from './schema';\nimport { logger } from '@spfn/core/logger';\n\nconst envLogger = logger.child('@spfn/core:env-registry');\n\n/**\n * 환경변수 레지스트리\n *\n * 스키마 기반 환경변수 관리 및 검증\n */\nexport class EnvRegistry<T extends EnvSchemaCollection = EnvSchemaCollection>\n{\n private schemas = new Map<string, EnvVarSchema>();\n private hasValidated = false;\n\n constructor(schemas?: T)\n {\n if (schemas)\n {\n this.registerMultiple(schemas);\n }\n }\n\n /**\n * 스키마 등록\n */\n register(schema: EnvVarSchema): void\n {\n this.schemas.set(schema.key, schema);\n }\n\n /**\n * 여러 스키마 등록\n */\n registerMultiple(schemas: EnvSchemaCollection): void\n {\n for (const [key, schema] of Object.entries(schemas))\n {\n this.register({ ...schema, key });\n }\n }\n\n /**\n * 검증 상태 리셋 (테스트용)\n */\n reset(): void\n {\n this.hasValidated = false;\n }\n\n /**\n * 환경변수 원시값 가져오기 (fallback 지원)\n */\n private getRawValue(key: string, fallbackKeys?: string[]): string | undefined\n {\n let value = process.env[key];\n\n if (!value && fallbackKeys)\n {\n for (const fallbackKey of fallbackKeys)\n {\n value = process.env[fallbackKey];\n if (value)\n {\n break;\n }\n }\n }\n\n return value;\n }\n\n /**\n * 값에 validator 적용\n */\n private applyValidator<U>(\n value: string,\n schema: EnvVarSchema<U>,\n ): U\n {\n if (schema.validator)\n {\n return schema.validator(value) as U;\n }\n\n return value as U;\n }\n\n /**\n * 스키마 검증 수행 (값 읽기 없이)\n *\n * @internal\n */\n private validateSchemas(): void\n {\n // Skip if already validated\n if (this.hasValidated)\n {\n return;\n }\n\n const warnings: string[] = [];\n\n // 클라이언트 변수 중 민감정보 경고\n for (const [key, schema] of this.schemas)\n {\n if (isClientAccessible(key) && schema.sensitive)\n {\n warnings.push(\n `${key} is marked as sensitive but accessible from client (NEXT_PUBLIC_*). Remove NEXT_PUBLIC_ prefix or unmark as sensitive.`,\n );\n }\n }\n\n // Log warnings\n if (warnings.length > 0)\n {\n envLogger.warn('Environment validation warnings:');\n warnings.forEach(w => envLogger.warn(` - ${w}`));\n }\n\n this.hasValidated = true;\n }\n\n /**\n * SKIP_ENV_VALIDATION 환경변수 확인\n */\n private shouldSkipValidation(): boolean\n {\n const skip = process.env.SKIP_ENV_VALIDATION;\n\n return skip === 'true' || skip === '1';\n }\n\n /**\n * 실제 접근 시점에 환경변수 값 가져오기 및 검증\n *\n * @internal\n */\n private getAndValidate(key: string): any\n {\n const schema = this.schemas.get(key);\n if (!schema)\n {\n return undefined;\n }\n\n // Get raw value using common helper\n const value = this.getRawValue(key, schema.fallbackKeys);\n\n // Check if required (skip if SKIP_ENV_VALIDATION is set)\n if (schema.required && !value && !this.shouldSkipValidation())\n {\n const fallbackHint = schema.fallbackKeys\n ? ` (or ${schema.fallbackKeys.join(', ')})`\n : '';\n\n const errorMsg = `${key}${fallbackHint} is required but not set. ${schema.description || ''}`;\n envLogger.error(`Environment validation failed:\\n - ${errorMsg}`);\n throw new Error('Environment validation failed');\n }\n\n // If no value and not required, use default\n if (!value)\n {\n return schema.default;\n }\n\n // Check minLength\n if (schema.minLength !== undefined && value.length < schema.minLength)\n {\n const errorMsg = `${key} must be at least ${schema.minLength} characters long (current: ${value.length})`;\n envLogger.error(`Environment validation failed:\\n - ${errorMsg}`);\n throw new Error('Environment validation failed');\n }\n\n // Apply validator\n try\n {\n return this.applyValidator(value, schema);\n }\n catch (error)\n {\n // For sensitive keys, never log the validator's message — it may have\n // interpolated the secret value (e.g. a DB/Redis URL with credentials).\n const detail = schema.sensitive\n ? '(value hidden — sensitive key)'\n : (error instanceof Error ? error.message : String(error));\n const errorMsg = `${key} validation failed: ${detail}`;\n envLogger.error(`Environment validation failed:\\n - ${errorMsg}`);\n throw new Error('Environment validation failed');\n }\n }\n\n /**\n * 모든 환경변수를 명시적으로 검증 (SKIP_ENV_VALIDATION 무시)\n *\n * CLI에서 사용하기 위한 메서드로, 모든 required 환경변수를 강제 검증합니다.\n *\n * @returns 검증 결과 (errors, warnings)\n */\n validateAll(): { errors: Array<{ key: string; message: string }>; warnings: Array<{ key: string; message: string }> }\n {\n const errors: Array<{ key: string; message: string }> = [];\n const warnings: Array<{ key: string; message: string }> = [];\n\n for (const [key, schema] of this.schemas)\n {\n // 클라이언트 변수 중 민감정보 경고\n if (isClientAccessible(key) && schema.sensitive)\n {\n warnings.push({\n key,\n message: `${key} is marked as sensitive but accessible from client (NEXT_PUBLIC_*).`,\n });\n }\n\n const value = this.getRawValue(key, schema.fallbackKeys);\n\n // Check required\n if (schema.required && !value)\n {\n const fallbackHint = schema.fallbackKeys\n ? ` (or ${schema.fallbackKeys.join(', ')})`\n : '';\n errors.push({\n key,\n message: `${key}${fallbackHint} is required but not set. ${schema.description || ''}`,\n });\n continue;\n }\n\n // Check minLength\n if (value && schema.minLength !== undefined && value.length < schema.minLength)\n {\n errors.push({\n key,\n message: `${key} must be at least ${schema.minLength} characters long (current: ${value.length})`,\n });\n continue;\n }\n\n // Check validator\n if (value && schema.validator)\n {\n try\n {\n schema.validator(value);\n }\n catch (error)\n {\n // Same rule as the lazy path: a sensitive key's validator\n // message may have interpolated the value, so it is dropped.\n const detail = schema.sensitive\n ? '(value hidden — sensitive key)'\n : (error instanceof Error ? error.message : String(error));\n\n errors.push({\n key,\n message: `${key} validation failed: ${detail}`,\n });\n }\n }\n }\n\n return { errors, warnings };\n }\n\n /**\n * 환경변수 검증 및 타입 안전한 env 객체 반환\n *\n * Proxy 기반으로 구현되어 실제 환경변수 접근 시점에 값을 읽고 검증합니다.\n * 이를 통해 dotenv 로딩 타이밍과 무관하게 최신 환경변수 값을 가져올 수 있습니다.\n *\n * @returns 검증된 환경변수 객체 (Proxy)\n * @throws {Error} 필수 변수 누락 또는 검증 실패 시\n *\n * @example\n * ```typescript\n * const registry = createEnvRegistry(schema);\n * const env = registry.validate(); // 스키마만 검증\n * // ... dotenv 로딩 ...\n * console.log(env.DATABASE_URL); // 이 시점에 실제 값 읽기\n * ```\n */\n validate(): InferEnvType<T>\n {\n // Perform schema-level validation (without reading values)\n this.validateSchemas();\n\n // Return Proxy that lazily reads and validates on access\n return new Proxy({} as InferEnvType<T>, {\n get: (_target, prop: string) =>\n {\n return this.getAndValidate(prop);\n },\n\n ownKeys: () =>\n {\n return Array.from(this.schemas.keys());\n },\n\n getOwnPropertyDescriptor: (_target, prop: string) =>\n {\n if (this.schemas.has(prop))\n {\n return {\n enumerable: true,\n configurable: true,\n get: () => this.getAndValidate(prop),\n };\n }\n\n return undefined;\n },\n\n has: (_target, prop: string) =>\n {\n return this.schemas.has(prop);\n },\n });\n }\n}\n\n/**\n * 환경변수 검증 결과\n */\nexport interface EnvValidationResult\n{\n valid: boolean;\n errors: Array<{ key: string; message: string }>;\n warnings: Array<{ key: string; message: string }>;\n}\n\n/**\n * 레지스트리 생성 헬퍼\n *\n * @example\n * ```typescript\n * const schema = defineEnvSchema({\n * DATABASE_URL: envString({ description: 'Database URL', required: true })\n * });\n *\n * const registry = createEnvRegistry(schema);\n * const env = registry.validate();\n * ```\n */\nexport function createEnvRegistry<T extends EnvSchemaCollection>(\n schemas: T,\n): EnvRegistry<T>\n{\n return new EnvRegistry(schemas);\n}\n\n/**\n * 모든 환경변수를 명시적으로 검증 (SKIP_ENV_VALIDATION 무시)\n *\n * CLI `spfn env validate` 명령어에서 사용\n *\n * @param registries - 검증할 레지스트리 배열\n * @returns 검증 결과\n *\n * @example\n * ```typescript\n * const result = validateAllEnv([coreRegistry, authRegistry]);\n * if (!result.valid) {\n * console.error('Missing env vars:', result.errors);\n * process.exit(1);\n * }\n * ```\n */\nexport function validateAllEnv(\n registries: EnvRegistry<any>[],\n): EnvValidationResult\n{\n const errors: Array<{ key: string; message: string }> = [];\n const warnings: Array<{ key: string; message: string }> = [];\n\n for (const registry of registries)\n {\n const result = registry.validateAll();\n errors.push(...result.errors);\n warnings.push(...result.warnings);\n }\n\n return {\n valid: errors.length === 0,\n errors,\n warnings,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/env/validator.ts","../../src/env/schema.ts","../../src/env/registry.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Environment Variable Management - Parsers\n *\n * Parser functions that transform and validate environment variable strings.\n * All parsers follow the pattern: (value: string) => T or throw Error\n *\n * An error message never contains the input. Messages are logged — by the\n * registry and by the server's boot check — and the input may be a secret or a\n * URL carrying credentials. Describe the expected shape instead.\n */\n\n// ============================================================================\n// Core Types\n// ============================================================================\n\n/**\n * Parser function that transforms and validates a string value\n * @throws Error if validation fails\n */\nexport type Parser<T> = (value: string) => T;\n\n// ============================================================================\n// String Parsers\n// ============================================================================\n\n/**\n * Parse a non-empty string\n *\n * @param value - Value to parse\n * @returns Trimmed string\n * @throws Error if string is empty after trimming\n *\n * @example\n * ```typescript\n * const name = getEnvVar('APP_NAME', {\n * validator: parseString,\n * });\n * ```\n */\nexport function parseString(value: string): string\n{\n const trimmed = value.trim();\n\n if (trimmed.length === 0)\n {\n throw new Error('Value cannot be empty');\n }\n\n return trimmed;\n}\n\n/**\n * Create a string parser with validation rules\n *\n * @param options - Validation options\n * @returns Parser function\n *\n * @example\n * ```typescript\n * const apiKey = getEnvVar('API_KEY', {\n * validator: createStringParser({\n * minLength: 32,\n * pattern: /^[A-Za-z0-9_-]+$/,\n * }),\n * });\n * ```\n */\nexport function createStringParser(\n options: {\n minLength?: number;\n maxLength?: number;\n pattern?: RegExp;\n trim?: boolean;\n } = {},\n): Parser<string>\n{\n return (value: string): string =>\n {\n const { minLength, maxLength, pattern, trim = true } = options;\n\n let result = trim ? value.trim() : value;\n\n // Empty check\n if (result.length === 0)\n {\n throw new Error('Value cannot be empty');\n }\n\n // Length validation\n if (minLength !== undefined && result.length < minLength)\n {\n throw new Error(`Must be at least ${minLength} characters long (current: ${result.length})`);\n }\n\n if (maxLength !== undefined && result.length > maxLength)\n {\n throw new Error(`Must be at most ${maxLength} characters long (current: ${result.length})`);\n }\n\n // Pattern validation\n if (pattern && !pattern.test(result))\n {\n throw new Error(`Must match pattern ${pattern}`);\n }\n\n return result;\n };\n}\n\n// ============================================================================\n// Boolean Parser\n// ============================================================================\n\n/**\n * Parse a boolean environment variable\n *\n * Accepts: 'true', '1', 'yes' (case-insensitive) → true\n * 'false', '0', 'no' (case-insensitive) → false\n *\n * @param value - Value to parse\n * @returns Boolean value\n * @throws Error if value is not a valid boolean string\n *\n * @example\n * ```typescript\n * const debug = getEnvVar('DEBUG', {\n * default: 'false',\n * validator: parseBoolean,\n * });\n * ```\n */\nexport function parseBoolean(value: string): boolean\n{\n const normalized = value.toLowerCase().trim();\n\n if (['true', '1', 'yes'].includes(normalized))\n {\n return true;\n }\n\n if (['false', '0', 'no'].includes(normalized))\n {\n return false;\n }\n\n throw new Error('Must be a boolean value (true/false, 1/0, yes/no)');\n}\n\n// ============================================================================\n// Number Parsers\n// ============================================================================\n\n/**\n * Parse and validate number\n *\n * @param value - Value to parse\n * @param options - Validation options\n * @returns Parsed number\n * @throws Error if invalid number or constraint violation\n *\n * @example\n * ```typescript\n * const port = getEnvVar('PORT', {\n * default: '3000',\n * validator: (val) => parseNumber(val, { min: 1, max: 65535, integer: true }),\n * });\n * ```\n */\nexport function parseNumber(\n value: string,\n options: { min?: number; max?: number; integer?: boolean } = {},\n): number\n{\n const { min, max, integer = false } = options;\n\n // Reject empty strings\n if (value.trim() === '')\n {\n throw new Error('Value cannot be empty');\n }\n\n const num = Number(value);\n\n if (isNaN(num))\n {\n throw new Error('Must be a valid number');\n }\n\n if (integer && !Number.isInteger(num))\n {\n throw new Error('Must be an integer');\n }\n\n if (min !== undefined && num < min)\n {\n throw new Error(`Must be at least ${min}`);\n }\n\n if (max !== undefined && num > max)\n {\n throw new Error(`Must be at most ${max}`);\n }\n\n return num;\n}\n\n/**\n * Create a number parser with specific constraints\n *\n * @param options - Validation constraints\n * @returns Parser function\n *\n * @example\n * ```typescript\n * const port = getEnvVar('PORT', {\n * default: '3000',\n * validator: createNumberParser({ min: 1, max: 65535, integer: true }),\n * });\n * ```\n */\nexport function createNumberParser(\n options: { min?: number; max?: number; integer?: boolean } = {},\n): Parser<number>\n{\n return (value: string) => parseNumber(value, options);\n}\n\n/**\n * Parse integer with optional constraints\n *\n * @param value - Value to parse\n * @param options - Min/max constraints\n * @returns Parsed integer\n * @throws Error if invalid or out of range\n *\n * @example\n * ```typescript\n * const retries = getEnvVar('MAX_RETRIES', {\n * default: '3',\n * validator: (val) => parseInteger(val, { min: 1, max: 10 }),\n * });\n * ```\n */\nexport function parseInteger(\n value: string,\n options: { min?: number; max?: number } = {},\n): number\n{\n return parseNumber(value, { ...options, integer: true });\n}\n\n/**\n * Parse float/decimal number with optional constraints\n *\n * @param value - Value to parse\n * @param options - Min/max constraints\n * @returns Parsed decimal number\n * @throws Error if invalid or out of range\n *\n * @example\n * ```typescript\n * const ratio = getEnvVar('CACHE_RATIO', {\n * default: '0.75',\n * validator: (val) => parseDecimal(val, { min: 0, max: 1 }),\n * });\n * ```\n */\nexport function parseDecimal(\n value: string,\n options: { min?: number; max?: number } = {},\n): number\n{\n return parseNumber(value, { ...options, integer: false });\n}\n\n// ============================================================================\n// URL Parsers\n// ============================================================================\n\n/**\n * Parse and validate URL\n *\n * @param value - Value to parse\n * @param options - Validation options\n * @returns Validated URL string\n * @throws Error if invalid URL or protocol mismatch\n *\n * @example\n * ```typescript\n * const apiUrl = getEnvVar('API_URL', {\n * validator: (val) => parseUrl(val, { protocol: 'https' }),\n * });\n * ```\n */\nexport function parseUrl(\n value: string,\n options: { protocol?: 'http' | 'https' | 'any' } = {},\n): string\n{\n const { protocol = 'any' } = options;\n\n // Parse URL (may throw TypeError)\n let url: URL;\n try\n {\n url = new URL(value);\n }\n catch (error)\n {\n if (error instanceof TypeError)\n {\n throw new Error('Invalid URL');\n }\n throw error;\n }\n\n // Validate protocol\n if (protocol === 'http' && url.protocol !== 'http:')\n {\n throw new Error(`URL must use HTTP protocol, got ${url.protocol}`);\n }\n\n if (protocol === 'https' && url.protocol !== 'https:')\n {\n throw new Error(`URL must use HTTPS protocol, got ${url.protocol}`);\n }\n\n return value;\n}\n\n/**\n * Create a URL parser with specific protocol requirement\n *\n * @param protocol - Required protocol ('http', 'https', or 'any')\n * @returns Parser function\n *\n * @example\n * ```typescript\n * const apiUrl = getEnvVar('API_URL', {\n * validator: createUrlParser('https'),\n * });\n * ```\n */\nexport function createUrlParser(protocol: 'http' | 'https' | 'any' = 'any'): Parser<string>\n{\n return (value: string) => parseUrl(value, { protocol });\n}\n\n/**\n * Parse PostgreSQL connection string\n *\n * @param value - Value to parse\n * @returns Validated PostgreSQL URL string\n * @throws Error if invalid PostgreSQL URL\n *\n * @example\n * ```typescript\n * const dbUrl = getEnvVar('DATABASE_URL', {\n * required: true,\n * validator: parsePostgresUrl,\n * });\n * ```\n */\nexport function parsePostgresUrl(value: string): string\n{\n // Parse URL (may throw TypeError)\n let url: URL;\n try\n {\n url = new URL(value);\n }\n catch (error)\n {\n if (error instanceof TypeError)\n {\n // Never echo the value — these URLs carry embedded credentials\n throw new Error('Invalid PostgreSQL URL');\n }\n throw error;\n }\n\n // Validate protocol\n if (url.protocol !== 'postgres:' && url.protocol !== 'postgresql:')\n {\n throw new Error(\n `Must be a PostgreSQL URL (postgres:// or postgresql://), got ${url.protocol}`,\n );\n }\n\n return value;\n}\n\n/**\n * Parse Redis connection string\n *\n * @param value - Value to parse\n * @returns Validated Redis URL string\n * @throws Error if invalid Redis URL\n *\n * @example\n * ```typescript\n * const redisUrl = getEnvVar('REDIS_URL', {\n * required: true,\n * validator: parseRedisUrl,\n * });\n * ```\n */\nexport function parseRedisUrl(value: string): string\n{\n // Parse URL (may throw TypeError)\n let url: URL;\n try\n {\n url = new URL(value);\n }\n catch (error)\n {\n if (error instanceof TypeError)\n {\n // Never echo the value — these URLs carry embedded credentials\n throw new Error('Invalid Redis URL');\n }\n throw error;\n }\n\n // Validate protocol\n if (url.protocol !== 'redis:' && url.protocol !== 'rediss:')\n {\n throw new Error(\n `Must be a Redis URL (redis:// or rediss://), got ${url.protocol}`,\n );\n }\n\n return value;\n}\n\n// ============================================================================\n// Enum Parser\n// ============================================================================\n\n/**\n * Parse and validate enum value\n *\n * @param value - Value to parse\n * @param allowed - Array of allowed values\n * @param caseInsensitive - Whether to perform case-insensitive comparison\n * @returns Validated enum value\n * @throws Error if value not in allowed list\n *\n * @example\n * ```typescript\n * const env = getEnvVar('NODE_ENV', {\n * validator: (val) => parseEnum(val, ['development', 'production', 'test']),\n * });\n * ```\n */\nexport function parseEnum(\n value: string,\n allowed: string[],\n caseInsensitive = false,\n): string\n{\n if (caseInsensitive)\n {\n const normalizedValue = value.toLowerCase();\n const normalizedAllowed = allowed.map((v) => v.toLowerCase());\n const index = normalizedAllowed.indexOf(normalizedValue);\n\n if (index === -1)\n {\n throw new Error(\n `Must be one of [${allowed.join(', ')}]`,\n );\n }\n\n return allowed[index]; // Return original case from allowed list\n }\n\n if (!allowed.includes(value))\n {\n throw new Error(\n `Must be one of [${allowed.join(', ')}]`,\n );\n }\n\n return value;\n}\n\n/**\n * Create an enum parser with specific allowed values\n *\n * @param allowed - Array of allowed values\n * @param caseInsensitive - Whether to perform case-insensitive comparison\n * @returns Parser function\n *\n * @example\n * ```typescript\n * const logLevel = getEnvVar('LOG_LEVEL', {\n * default: 'info',\n * validator: createEnumParser(['debug', 'info', 'warn', 'error']),\n * });\n * ```\n */\nexport function createEnumParser(allowed: string[], caseInsensitive = false): Parser<string>\n{\n return (value: string) => parseEnum(value, allowed, caseInsensitive);\n}\n\n// ============================================================================\n// JSON Parser\n// ============================================================================\n\n/**\n * Parse JSON string\n *\n * @param value - JSON string to parse\n * @returns Parsed JSON value\n * @throws Error if invalid JSON\n *\n * @example\n * ```typescript\n * const config = getEnvVar('CONFIG_JSON', {\n * validator: parseJson,\n * });\n * ```\n */\nexport function parseJson<T = any>(value: string): T\n{\n try\n {\n return JSON.parse(value) as T;\n }\n catch\n {\n // JSON.parse quotes the input in its message; the input may be a secret.\n throw new Error('Invalid JSON');\n }\n}\n\n/**\n * Create a typed JSON parser\n *\n * @returns Parser function\n *\n * @example\n * ```typescript\n * interface Config {\n * host: string;\n * port: number;\n * }\n *\n * const config = getEnvVar('CONFIG_JSON', {\n * validator: createJsonParser<Config>(),\n * });\n * ```\n */\nexport function createJsonParser<T>(): Parser<T>\n{\n return (value: string) => parseJson<T>(value);\n}\n\n// ============================================================================\n// Array Parser\n// ============================================================================\n\n/**\n * Parse comma-separated values into array\n *\n * @param value - Comma-separated string\n * @param options - Parser options\n * @returns Array of strings\n *\n * @example\n * ```typescript\n * const hosts = getEnvVar('ALLOWED_HOSTS', {\n * validator: parseArray,\n * });\n * // \"localhost,example.com,api.example.com\" → ['localhost', 'example.com', 'api.example.com']\n * ```\n */\nexport function parseArray(\n value: string,\n options: {\n separator?: string;\n trim?: boolean;\n filter?: (item: string) => boolean;\n } = {},\n): string[]\n{\n const { separator = ',', trim = true, filter } = options;\n\n if (value.trim() === '')\n {\n return [];\n }\n\n let items = value.split(separator);\n\n if (trim)\n {\n items = items.map((item) => item.trim());\n }\n\n if (filter)\n {\n items = items.filter(filter);\n }\n\n return items;\n}\n\n/**\n * Create an array parser with item parser\n *\n * @param itemParser - Parser to apply to each array item\n * @param options - Array parsing options\n * @returns Parser function\n *\n * @example\n * ```typescript\n * // Parse comma-separated ports\n * const ports = getEnvVar('PORTS', {\n * validator: createArrayParser(\n * createNumberParser({ min: 1, max: 65535, integer: true })\n * ),\n * });\n * // \"3000,4000,5000\" → [3000, 4000, 5000]\n * ```\n */\nexport function createArrayParser<T>(\n itemParser: Parser<T>,\n options: { separator?: string } = {},\n): Parser<T[]>\n{\n return (value: string): T[] =>\n {\n const items = parseArray(value, options);\n\n return items.map((item, index) =>\n {\n try\n {\n return itemParser(item);\n }\n catch (error)\n {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`Invalid item at index ${index}: ${message}`);\n }\n });\n };\n}\n\n// ============================================================================\n// Secure Secret Parser\n// ============================================================================\n\n/**\n * Calculate Shannon entropy of a string\n * Returns entropy in bits per character\n *\n * @param str - String to calculate entropy for\n * @returns Entropy value (0 to ~6.6 bits for printable ASCII)\n *\n * @example\n * ```typescript\n * const entropy = calculateEntropy('my-secret-key');\n * // Higher entropy = more random\n * // - Random lowercase: ~4.7 bits/char\n * // - Random alphanumeric: ~5.2 bits/char\n * // - Random printable ASCII: ~6.6 bits/char\n * // - \"aaaaaaa...\": ~0 bits/char\n * ```\n */\nfunction calculateEntropy(str: string): number\n{\n const len = str.length;\n const frequencies = new Map<string, number>();\n\n // Count character frequencies\n for (const char of str)\n {\n frequencies.set(char, (frequencies.get(char) || 0) + 1);\n }\n\n // Calculate Shannon entropy\n let entropy = 0;\n for (const count of frequencies.values())\n {\n const probability = count / len;\n entropy -= probability * Math.log2(probability);\n }\n\n return entropy;\n}\n\n/**\n * Create a secure secret parser with entropy validation\n *\n * Validates cryptographic secrets for sufficient length, character diversity, and randomness.\n * Uses Shannon entropy to measure randomness quality.\n *\n * @param options - Validation options\n * @returns Parser function\n *\n * @example\n * ```typescript\n * const sessionSecret = getEnvVar('SESSION_SECRET', {\n * validator: createSecureSecretParser({\n * minLength: 32, // Minimum 256-bit\n * minUniqueChars: 16, // Character diversity\n * minEntropy: 3.5, // Shannon entropy (bits/char)\n * }),\n * });\n * ```\n */\nexport function createSecureSecretParser(\n options: {\n minLength?: number;\n minUniqueChars?: number;\n minEntropy?: number;\n } = {},\n): Parser<string>\n{\n const {\n minLength = 32,\n minUniqueChars = 16,\n minEntropy = 3.5,\n } = options;\n\n return (value: string): string =>\n {\n const length = value.length;\n const uniqueChars = new Set(value).size;\n const entropy = calculateEntropy(value);\n\n // Check length (minimum for cryptographic strength)\n if (length < minLength)\n {\n throw new Error(\n `Secret too short: ${length} characters (minimum: ${minLength})`,\n );\n }\n\n // Check unique character diversity\n if (uniqueChars < minUniqueChars)\n {\n throw new Error(\n `Secret has low diversity: ${uniqueChars} unique characters (minimum: ${minUniqueChars})`,\n );\n }\n\n // Check Shannon entropy (randomness quality)\n // Reference values:\n // - Random lowercase: ~4.7 bits/char\n // - Random alphanumeric: ~5.2 bits/char\n // - Random printable ASCII: ~6.6 bits/char\n // - \"aaaaaaa...\": ~0 bits/char\n // - \"abcabcabc...\": ~1.58 bits/char\n if (entropy < minEntropy)\n {\n throw new Error(\n `Secret has low entropy: ${entropy.toFixed(2)} bits/char (minimum: ${minEntropy}). Use a more random secret.`,\n );\n }\n\n return value;\n };\n}\n\n// ============================================================================\n// Password Parser\n// ============================================================================\n\n/**\n * Create a password strength parser\n *\n * Validates password strength based on configurable requirements.\n * Useful for enforcing password policies in environment variables or user input.\n *\n * @param options - Validation options\n * @returns Parser function\n *\n * @example\n * ```typescript\n * const adminPassword = getEnvVar('ADMIN_PASSWORD', {\n * validator: createPasswordParser({\n * minLength: 12,\n * requireUppercase: true,\n * requireLowercase: true,\n * requireNumber: true,\n * requireSpecial: true,\n * }),\n * });\n * ```\n */\nexport function createPasswordParser(\n options: {\n minLength?: number;\n requireUppercase?: boolean;\n requireLowercase?: boolean;\n requireNumber?: boolean;\n requireSpecial?: boolean;\n } = {},\n): Parser<string>\n{\n const {\n minLength = 8,\n requireUppercase = true,\n requireLowercase = true,\n requireNumber = true,\n requireSpecial = true,\n } = options;\n\n return (value: string): string =>\n {\n const errors: string[] = [];\n\n // Length check\n if (value.length < minLength)\n {\n errors.push(`Must be at least ${minLength} characters`);\n }\n\n // Uppercase check\n if (requireUppercase && !/[A-Z]/.test(value))\n {\n errors.push('Must contain at least one uppercase letter');\n }\n\n // Lowercase check\n if (requireLowercase && !/[a-z]/.test(value))\n {\n errors.push('Must contain at least one lowercase letter');\n }\n\n // Number check\n if (requireNumber && !/[0-9]/.test(value))\n {\n errors.push('Must contain at least one number');\n }\n\n // Special character check\n if (requireSpecial && !/[^A-Za-z0-9]/.test(value))\n {\n errors.push('Must contain at least one special character');\n }\n\n if (errors.length > 0)\n {\n throw new Error(`Password validation failed: ${errors.join(', ')}`);\n }\n\n return value;\n };\n}\n\n// ============================================================================\n// Parser Composition\n// ============================================================================\n\n/**\n * Chain multiple parsers sequentially\n *\n * Each parser receives the output of the previous parser.\n * Useful for multi-step validation/transformation.\n *\n * @param parsers - Array of parser functions\n * @returns Combined parser function\n *\n * @example\n * ```typescript\n * const apiKey = getEnvVar('API_KEY', {\n * validator: chain(\n * parseString,\n * createStringParser({ minLength: 32, pattern: /^[A-Za-z0-9_-]+$/ }),\n * ),\n * });\n * ```\n */\nexport function chain<T>(...parsers: Array<Parser<T>>): Parser<T>\n{\n return (value: string): T =>\n {\n let result = value as any;\n\n for (const parser of parsers)\n {\n result = parser(result);\n }\n\n return result;\n };\n}\n\n/**\n * Apply parser with fallback value\n *\n * If parser throws, returns fallback instead.\n * Useful for optional environment variables with complex parsing.\n *\n * @param parser - Parser to attempt\n * @param fallback - Fallback value if parsing fails\n * @returns Parser function\n *\n * @example\n * ```typescript\n * const config = getEnvVar('CONFIG_JSON', {\n * validator: withFallback(parseJson, { host: 'localhost', port: 3000 }),\n * });\n * ```\n */\nexport function withFallback<T>(parser: Parser<T>, fallback: T): Parser<T>\n{\n return (value: string): T =>\n {\n try\n {\n return parser(value);\n }\n catch\n {\n return fallback;\n }\n };\n}\n\n/**\n * Make parser optional\n *\n * Returns undefined for empty strings instead of throwing.\n *\n * @param parser - Parser to make optional\n * @returns Parser function that returns T | undefined\n *\n * @example\n * ```typescript\n * const redisUrl = getEnvVar('REDIS_URL', {\n * validator: optional(parseRedisUrl),\n * });\n * // Empty string → undefined\n * // Valid URL → parsed URL\n * // Invalid URL → throws\n * ```\n */\nexport function optional<T>(parser: Parser<T>): Parser<T | undefined>\n{\n return (value: string): T | undefined =>\n {\n if (value.trim() === '')\n {\n return undefined;\n }\n\n return parser(value);\n };\n}\n","/**\n * Environment Variable Schema Definition System\n *\n * 환경변수에 메타데이터를 정의하여 중앙 관리, 문서화, 검증을 지원합니다.\n *\n * @example\n * ```typescript\n * const schema = defineEnvSchema({\n * DATABASE_URL: envUrl({\n * description: 'Database connection',\n * required: true,\n * validator: parsePostgresUrl,\n * sensitive: true,\n * })\n * });\n * ```\n *\n * @module env/schema\n */\n\nimport { parseBoolean, parseNumber, parseJson } from './validator';\n\n/**\n * 환경변수 스키마 정의\n */\nexport interface EnvVarSchema<T = string>\n{\n /** 환경변수 키 */\n key: string;\n\n /** 설명 (목적, 사용처) */\n description: string;\n\n /** 타입 */\n type: 'string' | 'number' | 'boolean' | 'url' | 'enum' | 'json';\n\n /** 필수 여부 */\n required?: boolean;\n\n /** 기본값 */\n default?: T;\n\n /** 검증/변환 함수 */\n validator?: (value: string) => T;\n\n // === 검증 옵션 ===\n\n /** Fallback 환경변수 키들 (backward compatibility) */\n fallbackKeys?: string[];\n\n /** 최소 길이 (문자열 타입) */\n minLength?: number;\n\n // === 메타데이터 ===\n\n /** 민감정보 여부 (로깅 시 마스킹) */\n sensitive?: boolean;\n\n /**\n * 자동 생성 방식 — 값이 미지정이면 프로비저닝(secrets 관리 CLI)이 이 방식으로 생성한다.\n * 외부에서 받아야 하는 시크릿(API 키 등)은 generate를 비워 사용자 입력으로 둔다.\n */\n generate?: 'hex32' | 'hex64' | 'uuid' | 'base64url32';\n\n /** 예시 값들 (타입과 일치해야 함) */\n examples?: T[];\n\n // === 파일 분리 ===\n\n /**\n * Next.js 프로세스에서 사용 여부\n *\n * - true: .env.local에 존재해야 함 (Next.js 서버 컴포넌트에서 접근 가능)\n * - false: .env.server에만 존재해야 함 (SPFN 서버에서만 접근)\n *\n * @default NEXT_PUBLIC_* 이면 true, 아니면 false\n */\n nextjs?: boolean;\n\n // === Deployment layer ===\n\n /**\n * Which layer of a deployment must supply the value.\n *\n * - `'environment'`: one value shared by every instance of an environment\n * (a third-party API key) — kept in `secrets/<env>.enc.json`.\n * - `'instance'`: a value that differs per instance — kept in\n * `secrets/<env>.<instance>.enc.json` or computed by the deploy script\n * and passed to `spfn secret export --with <file>`.\n *\n * Unset: the value may come from any layer. Set it to pin where a value\n * must live — `spfn secret export` then fails when the value arrives from\n * another layer. `--all` on `spfn secret set`/`generate`/`rotate` gives an\n * unset entry to the environment target only.\n */\n layer?: EnvLayer;\n\n // === Audit ===\n\n /**\n * Files that read this variable under a computed key, as paths relative to\n * the project root — e.g. `['src/server/config/providers.ts']`.\n *\n * `spfn env audit` counts a name as read when it appears as a property\n * access or a string literal; a name only ever reached through\n * `env[name]` needs this instead. The name passes when every listed file\n * exists and is scanned. The runtime ignores `readBy`.\n */\n readBy?: string[];\n}\n\n/**\n * The layer of a deployment an env value comes from — see {@link EnvVarSchema.layer}.\n */\nexport type EnvLayer = 'environment' | 'instance';\n\n/**\n * 스키마 컬렉션 타입\n */\nexport type EnvSchemaCollection = Record<string, EnvVarSchema<any>>;\n\n/**\n * Helper type: Check if field has default value\n */\ntype HasDefault<T> = T extends { default: any } ? true : false;\n\n/**\n * Helper type: Check if field is explicitly required\n */\ntype IsRequired<T> = T extends { required: true } ? true : false;\n\n/**\n * Helper type: Check if field should be required (has default OR required: true)\n */\ntype ShouldBeRequired<T> = HasDefault<T> extends true ? true : IsRequired<T>;\n\n/**\n * 스키마로부터 타입 추출\n *\n * required: true 또는 default가 있는 필드 → 필수\n * required: false 또는 미지정 → optional (| undefined)\n */\nexport type InferEnvType<T extends EnvSchemaCollection> = {\n // Required fields (required: true OR has default)\n [K in keyof T as ShouldBeRequired<T[K]> extends true ? K : never]:\n T[K] extends EnvVarSchema<infer U> ? U : string;\n} & {\n // Optional fields (required: false OR not specified)\n [K in keyof T as ShouldBeRequired<T[K]> extends true ? never : K]?:\n T[K] extends EnvVarSchema<infer U> ? U | undefined : string | undefined;\n};\n\n/**\n * 스키마 정의 헬퍼 (타입 추론 지원)\n *\n * Automatically fills in the `key` property from object keys. Every other\n * field passes through as written — an entry without `layer` may come from\n * any deployment layer.\n *\n * @example\n * ```typescript\n * const schema = defineEnvSchema({\n * DATABASE_URL: envString({ description: 'Database URL', required: true })\n * });\n * // Automatically adds key: 'DATABASE_URL'\n * ```\n */\nexport function defineEnvSchema<T extends Record<string, any>>(\n schema: T,\n): { [K in keyof T]: T[K] & { key: K } }\n{\n const result: any = {};\n\n for (const key in schema)\n {\n result[key] = {\n ...schema[key],\n key,\n };\n }\n\n return result;\n}\n\n/**\n * 문자열 스키마 헬퍼\n *\n * @example\n * ```typescript\n * const schema = {\n * API_KEY: {\n * ...envString({\n * description: 'API authentication key',\n * required: true,\n * sensitive: true,\n * }),\n * key: 'API_KEY',\n * }\n * };\n * ```\n */\nexport function envString<T extends Omit<EnvVarSchema, 'key' | 'type'>>(\n options: T,\n): T & { type: 'string'; validator: (value: string) => string }\n{\n return {\n ...options,\n type: 'string',\n validator: options.validator || ((value: string) => value),\n };\n}\n\n/**\n * 시크릿 스키마 헬퍼 — sensitive:true 자동.\n *\n * generate 지정 시 프로비저닝(secrets CLI)이 값을 자동 생성, 미지정이면 외부 입력(사용자가 붙여넣음).\n * 이로써 env.config.ts가 \"설정(envString) vs 외부 시크릿(envSecret) vs 자동 시크릿(envSecret+generate)\"을\n * 선언적으로 가른다 — CLI·프레임워크·프로비저닝이 한 원천을 읽는다.\n *\n * @example\n * ```typescript\n * SPFN_AUTH_SESSION_SECRET: envSecret({ description: '세션 서명 키', generate: 'base64url32' }),\n * OPENAI_API_KEY: envSecret({ description: 'OpenAI API 키' }), // generate 없음 → 외부 입력\n * ```\n */\nexport function envSecret<T extends Omit<EnvVarSchema, 'key' | 'type' | 'sensitive'>>(\n options: T,\n): T & { type: 'string'; sensitive: true; validator: (value: string) => string }\n{\n return {\n ...options,\n type: 'string',\n sensitive: true,\n validator: options.validator || ((value: string) => value),\n };\n}\n\n/**\n * 숫자 스키마 헬퍼\n *\n * @example\n * ```typescript\n * const schema = {\n * PORT: {\n * ...envNumber({\n * description: 'Server port',\n * default: 3000,\n * validator: createNumberParser({ min: 1, max: 65535 }),\n * }),\n * key: 'PORT',\n * }\n * };\n * ```\n */\nexport function envNumber<T extends Omit<EnvVarSchema<number>, 'key' | 'type'>>(\n options: T,\n): T & { type: 'number'; validator: (value: string) => number }\n{\n return {\n ...options,\n type: 'number',\n validator: options.validator || parseNumber,\n };\n}\n\n/**\n * Boolean 스키마 헬퍼\n *\n * @example\n * ```typescript\n * const schema = {\n * DEBUG: {\n * ...envBoolean({\n * description: 'Enable debug mode',\n * default: false,\n * }),\n * key: 'DEBUG',\n * }\n * };\n * ```\n */\nexport function envBoolean<T extends Omit<EnvVarSchema<boolean>, 'key' | 'type'>>(\n options: T,\n): T & { type: 'boolean'; validator: (value: string) => boolean }\n{\n return {\n ...options,\n type: 'boolean',\n validator: options.validator || parseBoolean,\n };\n}\n\n/**\n * URL 스키마 헬퍼\n *\n * @example\n * ```typescript\n * const schema = {\n * DATABASE_URL: {\n * ...envUrl({\n * description: 'Database connection URL',\n * required: true,\n * validator: parsePostgresUrl,\n * }),\n * key: 'DATABASE_URL',\n * }\n * };\n * ```\n */\nexport function envUrl<T extends Omit<EnvVarSchema, 'key' | 'type'>>(\n options: T,\n): T & { type: 'url' }\n{\n return {\n ...options,\n type: 'url',\n };\n}\n\n/**\n * Enum 스키마 헬퍼\n *\n * @example\n * ```typescript\n * const schema = {\n * LOG_LEVEL: {\n * ...envEnum(['debug', 'info', 'warn', 'error'] as const, {\n * description: 'Logging level',\n * default: 'info',\n * }),\n * key: 'LOG_LEVEL',\n * }\n * };\n * ```\n */\nexport function envEnum<\n T extends string,\n O extends Omit<EnvVarSchema<T>, 'key' | 'type' | 'validator'>,\n>(\n allowed: readonly T[],\n options: O,\n): O & { type: 'enum'; validator: (val: string) => T }\n{\n return {\n ...options,\n type: 'enum',\n validator: (val: string): T =>\n {\n if (!allowed.includes(val as T))\n {\n throw new Error(`Must be one of: ${allowed.join(', ')}`);\n }\n\n return val as T;\n },\n };\n}\n\n/**\n * JSON 스키마 헬퍼\n *\n * @example\n * ```typescript\n * const schema = {\n * CONFIG_JSON: {\n * ...envJson<{ host: string; port: number }>({\n * description: 'JSON configuration object',\n * required: true,\n * }),\n * key: 'CONFIG_JSON',\n * }\n * };\n * ```\n */\nexport function envJson<\n T = any,\n O extends Omit<EnvVarSchema<T>, 'key' | 'type' | 'validator'> = Omit<EnvVarSchema<T>, 'key' | 'type' | 'validator'>,\n>(\n options: O,\n): O & { type: 'json'; validator: (val: string) => T }\n{\n return {\n ...options,\n type: 'json',\n validator: (val: string) => parseJson<T>(val),\n };\n}\n\n/**\n * 환경변수가 클라이언트에서 접근 가능한지 확인\n * (NEXT_PUBLIC_ 접두사로 판단)\n *\n * @param key - 환경변수 키\n * @returns 클라이언트에서 접근 가능하면 true\n *\n * @example\n * ```typescript\n * isClientAccessible('NEXT_PUBLIC_API_URL'); // true\n * isClientAccessible('DATABASE_URL'); // false\n * ```\n */\nexport function isClientAccessible(key: string): boolean\n{\n return key.startsWith('NEXT_PUBLIC_');\n}\n\n/**\n * 환경변수가 서버 전용인지 확인\n * (NEXT_PUBLIC_ 접두사가 없으면 서버 전용)\n *\n * @param key - 환경변수 키\n * @returns 서버 전용이면 true\n *\n * @example\n * ```typescript\n * isServerOnly('DATABASE_URL'); // true\n * isServerOnly('NEXT_PUBLIC_API_URL'); // false\n * ```\n */\nexport function isServerOnly(key: string): boolean\n{\n return !isClientAccessible(key);\n}\n\n/**\n * 스키마의 nextjs 옵션 값 결정\n *\n * 명시적으로 지정되지 않은 경우:\n * - NEXT_PUBLIC_* → true\n * - 그 외 → false\n *\n * @param schema - 환경변수 스키마\n * @returns Next.js 프로세스에서 사용 가능 여부\n */\nexport function isNextjsAccessible(schema: EnvVarSchema): boolean\n{\n if (schema.nextjs !== undefined)\n {\n return schema.nextjs;\n }\n\n return isClientAccessible(schema.key);\n}\n\n/**\n * 스키마가 SPFN 서버 전용인지 확인\n *\n * @param schema - 환경변수 스키마\n * @returns SPFN 서버에서만 사용되면 true\n */\nexport function isSpfnServerOnly(schema: EnvVarSchema): boolean\n{\n return !isNextjsAccessible(schema);\n}\n","/**\n * Environment Variable Registry\n *\n * 환경변수 스키마를 등록하고 타입 안전하게 접근할 수 있는 레지스트리\n *\n * @example\n * ```typescript\n * const schema = defineEnvSchema({\n * DATABASE_URL: envString({ description: 'Database URL', required: true })\n * });\n *\n * const registry = createEnvRegistry(schema);\n * const env = registry.validate(); // 검증 + env 반환\n * console.log(env.DATABASE_URL);\n * ```\n *\n * @module env/registry\n */\nimport type { EnvVarSchema, EnvSchemaCollection, InferEnvType } from './schema';\nimport { isClientAccessible } from './schema';\nimport { logger } from '@spfn/core/logger';\n\nconst envLogger = logger.child('@spfn/core:env-registry');\n\n/**\n * 환경변수 레지스트리\n *\n * 스키마 기반 환경변수 관리 및 검증\n */\nexport class EnvRegistry<T extends EnvSchemaCollection = EnvSchemaCollection>\n{\n private schemas = new Map<string, EnvVarSchema>();\n private hasValidated = false;\n\n constructor(schemas?: T)\n {\n if (schemas)\n {\n this.registerMultiple(schemas);\n }\n }\n\n /**\n * 스키마 등록\n */\n register(schema: EnvVarSchema): void\n {\n this.schemas.set(schema.key, schema);\n }\n\n /**\n * 여러 스키마 등록\n */\n registerMultiple(schemas: EnvSchemaCollection): void\n {\n for (const [key, schema] of Object.entries(schemas))\n {\n this.register({ ...schema, key });\n }\n }\n\n /**\n * 검증 상태 리셋 (테스트용)\n */\n reset(): void\n {\n this.hasValidated = false;\n }\n\n /**\n * 환경변수 원시값 가져오기 (fallback 지원)\n */\n private getRawValue(key: string, fallbackKeys?: string[]): string | undefined\n {\n let value = process.env[key];\n\n if (!value && fallbackKeys)\n {\n for (const fallbackKey of fallbackKeys)\n {\n value = process.env[fallbackKey];\n if (value)\n {\n break;\n }\n }\n }\n\n return value;\n }\n\n /**\n * 값에 validator 적용\n */\n private applyValidator<U>(\n value: string,\n schema: EnvVarSchema<U>,\n ): U\n {\n if (schema.validator)\n {\n return schema.validator(value) as U;\n }\n\n return value as U;\n }\n\n /**\n * 스키마 검증 수행 (값 읽기 없이)\n *\n * @internal\n */\n private validateSchemas(): void\n {\n // Skip if already validated\n if (this.hasValidated)\n {\n return;\n }\n\n const warnings: string[] = [];\n\n // 클라이언트 변수 중 민감정보 경고\n for (const [key, schema] of this.schemas)\n {\n if (isClientAccessible(key) && schema.sensitive)\n {\n warnings.push(\n `${key} is marked as sensitive but accessible from client (NEXT_PUBLIC_*). Remove NEXT_PUBLIC_ prefix or unmark as sensitive.`,\n );\n }\n }\n\n // Log warnings\n if (warnings.length > 0)\n {\n envLogger.warn('Environment validation warnings:');\n warnings.forEach(w => envLogger.warn(` - ${w}`));\n }\n\n this.hasValidated = true;\n }\n\n /**\n * SKIP_ENV_VALIDATION 환경변수 확인\n */\n private shouldSkipValidation(): boolean\n {\n const skip = process.env.SKIP_ENV_VALIDATION;\n\n return skip === 'true' || skip === '1';\n }\n\n /**\n * 실제 접근 시점에 환경변수 값 가져오기 및 검증\n *\n * @internal\n */\n private getAndValidate(key: string): any\n {\n const schema = this.schemas.get(key);\n if (!schema)\n {\n return undefined;\n }\n\n // Get raw value using common helper\n const value = this.getRawValue(key, schema.fallbackKeys);\n\n // Check if required (skip if SKIP_ENV_VALIDATION is set)\n if (schema.required && !value && !this.shouldSkipValidation())\n {\n const fallbackHint = schema.fallbackKeys\n ? ` (or ${schema.fallbackKeys.join(', ')})`\n : '';\n\n const errorMsg = `${key}${fallbackHint} is required but not set. ${schema.description || ''}`;\n envLogger.error(`Environment validation failed:\\n - ${errorMsg}`);\n throw new Error('Environment validation failed');\n }\n\n // If no value and not required, use default\n if (!value)\n {\n return schema.default;\n }\n\n // Check minLength\n if (schema.minLength !== undefined && value.length < schema.minLength)\n {\n const errorMsg = `${key} must be at least ${schema.minLength} characters long (current: ${value.length})`;\n envLogger.error(`Environment validation failed:\\n - ${errorMsg}`);\n throw new Error('Environment validation failed');\n }\n\n // Apply validator\n try\n {\n return this.applyValidator(value, schema);\n }\n catch (error)\n {\n // For sensitive keys, never log the validator's message — it may have\n // interpolated the secret value (e.g. a DB/Redis URL with credentials).\n const detail = schema.sensitive\n ? '(value hidden — sensitive key)'\n : (error instanceof Error ? error.message : String(error));\n const errorMsg = `${key} validation failed: ${detail}`;\n envLogger.error(`Environment validation failed:\\n - ${errorMsg}`);\n throw new Error('Environment validation failed');\n }\n }\n\n /**\n * 모든 환경변수를 명시적으로 검증 (SKIP_ENV_VALIDATION 무시)\n *\n * CLI에서 사용하기 위한 메서드로, 모든 required 환경변수를 강제 검증합니다.\n *\n * @returns 검증 결과 (errors, warnings)\n */\n validateAll(): { errors: Array<{ key: string; message: string }>; warnings: Array<{ key: string; message: string }> }\n {\n const errors: Array<{ key: string; message: string }> = [];\n const warnings: Array<{ key: string; message: string }> = [];\n\n for (const [key, schema] of this.schemas)\n {\n // 클라이언트 변수 중 민감정보 경고\n if (isClientAccessible(key) && schema.sensitive)\n {\n warnings.push({\n key,\n message: `${key} is marked as sensitive but accessible from client (NEXT_PUBLIC_*).`,\n });\n }\n\n const value = this.getRawValue(key, schema.fallbackKeys);\n\n // Check required\n if (schema.required && !value)\n {\n const fallbackHint = schema.fallbackKeys\n ? ` (or ${schema.fallbackKeys.join(', ')})`\n : '';\n errors.push({\n key,\n message: `${key}${fallbackHint} is required but not set. ${schema.description || ''}`,\n });\n continue;\n }\n\n // Check minLength\n if (value && schema.minLength !== undefined && value.length < schema.minLength)\n {\n errors.push({\n key,\n message: `${key} must be at least ${schema.minLength} characters long (current: ${value.length})`,\n });\n continue;\n }\n\n // Check validator\n if (value && schema.validator)\n {\n try\n {\n schema.validator(value);\n }\n catch (error)\n {\n // Same rule as the lazy path: a sensitive key's validator\n // message may have interpolated the value, so it is dropped.\n const detail = schema.sensitive\n ? '(value hidden — sensitive key)'\n : (error instanceof Error ? error.message : String(error));\n\n errors.push({\n key,\n message: `${key} validation failed: ${detail}`,\n });\n }\n }\n }\n\n return { errors, warnings };\n }\n\n /**\n * 환경변수 검증 및 타입 안전한 env 객체 반환\n *\n * Proxy 기반으로 구현되어 실제 환경변수 접근 시점에 값을 읽고 검증합니다.\n * 이를 통해 dotenv 로딩 타이밍과 무관하게 최신 환경변수 값을 가져올 수 있습니다.\n *\n * @returns 검증된 환경변수 객체 (Proxy)\n * @throws {Error} 필수 변수 누락 또는 검증 실패 시\n *\n * @example\n * ```typescript\n * const registry = createEnvRegistry(schema);\n * const env = registry.validate(); // 스키마만 검증\n * // ... dotenv 로딩 ...\n * console.log(env.DATABASE_URL); // 이 시점에 실제 값 읽기\n * ```\n */\n validate(): InferEnvType<T>\n {\n // Perform schema-level validation (without reading values)\n this.validateSchemas();\n\n // Return Proxy that lazily reads and validates on access\n return new Proxy({} as InferEnvType<T>, {\n get: (_target, prop: string) =>\n {\n return this.getAndValidate(prop);\n },\n\n ownKeys: () =>\n {\n return Array.from(this.schemas.keys());\n },\n\n getOwnPropertyDescriptor: (_target, prop: string) =>\n {\n if (this.schemas.has(prop))\n {\n return {\n enumerable: true,\n configurable: true,\n get: () => this.getAndValidate(prop),\n };\n }\n\n return undefined;\n },\n\n has: (_target, prop: string) =>\n {\n return this.schemas.has(prop);\n },\n });\n }\n}\n\n/**\n * 환경변수 검증 결과\n */\nexport interface EnvValidationResult\n{\n valid: boolean;\n errors: Array<{ key: string; message: string }>;\n warnings: Array<{ key: string; message: string }>;\n}\n\n/**\n * 레지스트리 생성 헬퍼\n *\n * @example\n * ```typescript\n * const schema = defineEnvSchema({\n * DATABASE_URL: envString({ description: 'Database URL', required: true })\n * });\n *\n * const registry = createEnvRegistry(schema);\n * const env = registry.validate();\n * ```\n */\nexport function createEnvRegistry<T extends EnvSchemaCollection>(\n schemas: T,\n): EnvRegistry<T>\n{\n return new EnvRegistry(schemas);\n}\n\n/**\n * 모든 환경변수를 명시적으로 검증 (SKIP_ENV_VALIDATION 무시)\n *\n * CLI `spfn env validate` 명령어에서 사용\n *\n * @param registries - 검증할 레지스트리 배열\n * @returns 검증 결과\n *\n * @example\n * ```typescript\n * const result = validateAllEnv([coreRegistry, authRegistry]);\n * if (!result.valid) {\n * console.error('Missing env vars:', result.errors);\n * process.exit(1);\n * }\n * ```\n */\nexport function validateAllEnv(\n registries: EnvRegistry<any>[],\n): EnvValidationResult\n{\n const errors: Array<{ key: string; message: string }> = [];\n const warnings: Array<{ key: string; message: string }> = [];\n\n for (const registry of registries)\n {\n const result = registry.validateAll();\n errors.push(...result.errors);\n warnings.push(...result.warnings);\n }\n\n return {\n valid: errors.length === 0,\n errors,\n warnings,\n };\n}\n"]}
|
package/dist/nextjs/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Router, RouteDef } from '@spfn/core/route';
|
|
2
|
-
import { R as RequestInterceptor, a as ResponseInterceptor, I as InferRouteInput, b as InferRouteOutput, E as ExtractRoutes, A as ApiConfig } from '../types-
|
|
3
|
-
export { C as CallOptions, c as CookieOptions, d as RouterInput, e as RouterOutput, S as SetCookie, f as StructuredInput } from '../types-
|
|
2
|
+
import { R as RequestInterceptor, a as ResponseInterceptor, I as InferRouteInput, b as InferRouteOutput, E as ExtractRoutes, A as ApiConfig } from '../types-CxhPH4s_.js';
|
|
3
|
+
export { C as CallOptions, c as CookieOptions, d as RouterInput, e as RouterOutput, S as SetCookie, f as StructuredInput } from '../types-CxhPH4s_.js';
|
|
4
4
|
import '@sinclair/typebox';
|
|
5
5
|
import '@spfn/core/errors';
|
|
6
6
|
|
|
@@ -57,6 +57,11 @@ type RouteClient<TRoute extends RouteDef<any, any>> = {
|
|
|
57
57
|
tags?: string[];
|
|
58
58
|
};
|
|
59
59
|
}): RouteClient<TRoute>;
|
|
60
|
+
/**
|
|
61
|
+
* Set this call's timeout in milliseconds, overriding the client-wide one.
|
|
62
|
+
* Throws a `TypeError` unless `ms` is finite, `> 0` and `<= 2147483647`.
|
|
63
|
+
*/
|
|
64
|
+
timeout(ms: number): RouteClient<TRoute>;
|
|
60
65
|
/**
|
|
61
66
|
* Set request interceptor
|
|
62
67
|
*/
|
|
@@ -175,13 +180,21 @@ declare function documentCookieEntries(): Array<[string, string]>;
|
|
|
175
180
|
|
|
176
181
|
/**
|
|
177
182
|
* Typed client error
|
|
183
|
+
*
|
|
184
|
+
* `errorType` tells apart why a call failed:
|
|
185
|
+
* - `'http'`: the server answered with a non-2xx status
|
|
186
|
+
* - `'network'`: the fetch or the body read failed (status 0)
|
|
187
|
+
* - `'timeout'`: the call's timeout elapsed before response headers arrived (status 408)
|
|
188
|
+
* - `'aborted'`: the caller's own signal aborted the call (status 0); `cause` is the signal's reason
|
|
178
189
|
*/
|
|
179
190
|
declare class ApiError extends Error {
|
|
180
191
|
readonly status: number;
|
|
181
192
|
readonly url: string;
|
|
182
193
|
readonly response?: unknown | undefined;
|
|
183
|
-
readonly errorType?: "http" | "network" | "timeout" | undefined;
|
|
184
|
-
constructor(message: string, status: number, url: string, response?: unknown | undefined, errorType?: "http" | "network" | "timeout" | undefined
|
|
194
|
+
readonly errorType?: "http" | "network" | "timeout" | "aborted" | undefined;
|
|
195
|
+
constructor(message: string, status: number, url: string, response?: unknown | undefined, errorType?: "http" | "network" | "timeout" | "aborted" | undefined, options?: {
|
|
196
|
+
cause?: unknown;
|
|
197
|
+
});
|
|
185
198
|
}
|
|
186
199
|
|
|
187
200
|
export { ApiConfig, ApiError, CSRF_HEADER, type Client, InferRouteInput, InferRouteOutput, RequestInterceptor, ResponseInterceptor, type RouteClient, createApi, csrfHeaderValue, documentCookieEntries };
|
package/dist/nextjs/index.js
CHANGED
|
@@ -69,8 +69,8 @@ function logThrowingDeserializedError(logger3, error) {
|
|
|
69
69
|
|
|
70
70
|
// src/nextjs/client/errors.ts
|
|
71
71
|
var ApiError = class extends Error {
|
|
72
|
-
constructor(message, status, url, response, errorType) {
|
|
73
|
-
super(message);
|
|
72
|
+
constructor(message, status, url, response, errorType, options) {
|
|
73
|
+
super(message, options);
|
|
74
74
|
this.status = status;
|
|
75
75
|
this.url = url;
|
|
76
76
|
this.response = response;
|
|
@@ -123,19 +123,45 @@ async function autoDetectServerCookies() {
|
|
|
123
123
|
return {};
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
|
-
|
|
126
|
+
function openAbortScope(callerSignal, timeout) {
|
|
127
127
|
const controller = new AbortController();
|
|
128
|
-
|
|
128
|
+
let cause;
|
|
129
|
+
const abort = (source, reason) => {
|
|
130
|
+
cause ??= source;
|
|
131
|
+
controller.abort(reason);
|
|
132
|
+
};
|
|
133
|
+
const onCallerAbort = () => abort("caller", callerSignal?.reason);
|
|
134
|
+
const timeoutId = setTimeout(() => abort("timeout"), timeout);
|
|
135
|
+
callerSignal?.addEventListener("abort", onCallerAbort, { once: true });
|
|
136
|
+
return {
|
|
137
|
+
signal: controller.signal,
|
|
138
|
+
cause: () => cause,
|
|
139
|
+
stopTimer: () => clearTimeout(timeoutId),
|
|
140
|
+
close: () => {
|
|
141
|
+
clearTimeout(timeoutId);
|
|
142
|
+
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
async function fetchInAbortScope(url, init, timeout, customFetch = fetch) {
|
|
147
|
+
const callerSignal = init.signal ?? void 0;
|
|
148
|
+
if (callerSignal?.aborted) {
|
|
149
|
+
return { ok: false, error: callerSignal.reason, cause: "caller" };
|
|
150
|
+
}
|
|
151
|
+
const scope = openAbortScope(callerSignal, timeout);
|
|
129
152
|
try {
|
|
130
|
-
const response = await customFetch(url, {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
153
|
+
const response = await customFetch(url, { ...init, signal: scope.signal });
|
|
154
|
+
scope.stopTimer();
|
|
155
|
+
const body = await parseResponseBody(response);
|
|
156
|
+
if (scope.cause() === "caller") {
|
|
157
|
+
return { ok: false, error: callerSignal?.reason, cause: "caller" };
|
|
158
|
+
}
|
|
159
|
+
return { ok: true, response, body };
|
|
136
160
|
} catch (error) {
|
|
137
|
-
|
|
138
|
-
|
|
161
|
+
const cause = scope.cause();
|
|
162
|
+
return { ok: false, cause, error: cause === "caller" ? callerSignal?.reason : error };
|
|
163
|
+
} finally {
|
|
164
|
+
scope.close();
|
|
139
165
|
}
|
|
140
166
|
}
|
|
141
167
|
async function handleErrorResponse(response, body, fullUrl, errorRegistry, debug, logger3) {
|
|
@@ -213,6 +239,7 @@ function documentCookieEntries() {
|
|
|
213
239
|
}
|
|
214
240
|
|
|
215
241
|
// src/nextjs/client/builder.ts
|
|
242
|
+
var MAX_TIMEOUT_MS = 2147483647;
|
|
216
243
|
var RouteCallBuilder = class _RouteCallBuilder {
|
|
217
244
|
constructor(executor, routeName) {
|
|
218
245
|
this.executor = executor;
|
|
@@ -221,6 +248,7 @@ var RouteCallBuilder = class _RouteCallBuilder {
|
|
|
221
248
|
_headers;
|
|
222
249
|
_cookies;
|
|
223
250
|
_fetchOptions;
|
|
251
|
+
_timeout;
|
|
224
252
|
_onRequest;
|
|
225
253
|
_onResponse;
|
|
226
254
|
/**
|
|
@@ -234,6 +262,7 @@ var RouteCallBuilder = class _RouteCallBuilder {
|
|
|
234
262
|
builder._headers = this._headers;
|
|
235
263
|
builder._cookies = this._cookies;
|
|
236
264
|
builder._fetchOptions = this._fetchOptions;
|
|
265
|
+
builder._timeout = this._timeout;
|
|
237
266
|
builder._onRequest = this._onRequest;
|
|
238
267
|
builder._onResponse = this._onResponse;
|
|
239
268
|
return builder;
|
|
@@ -262,6 +291,20 @@ var RouteCallBuilder = class _RouteCallBuilder {
|
|
|
262
291
|
builder._fetchOptions = { ...this._fetchOptions, ...options };
|
|
263
292
|
return builder;
|
|
264
293
|
}
|
|
294
|
+
/**
|
|
295
|
+
* Set this call's timeout in milliseconds, overriding the client-wide one
|
|
296
|
+
*
|
|
297
|
+
* Throws a `TypeError`, before any request is made, unless `ms` is a finite number
|
|
298
|
+
* greater than 0 and at most 2147483647 (the largest delay `setTimeout` honours).
|
|
299
|
+
*/
|
|
300
|
+
timeout(ms) {
|
|
301
|
+
if (!Number.isFinite(ms) || ms <= 0 || ms > MAX_TIMEOUT_MS) {
|
|
302
|
+
throw new TypeError(`timeout must be a finite number of milliseconds in (0, ${MAX_TIMEOUT_MS}], got ${String(ms)}`);
|
|
303
|
+
}
|
|
304
|
+
const builder = this.clone();
|
|
305
|
+
builder._timeout = ms;
|
|
306
|
+
return builder;
|
|
307
|
+
}
|
|
265
308
|
/**
|
|
266
309
|
* Set request interceptor
|
|
267
310
|
*/
|
|
@@ -297,6 +340,9 @@ var RouteCallBuilder = class _RouteCallBuilder {
|
|
|
297
340
|
if (this._fetchOptions) {
|
|
298
341
|
options.fetchOptions = this._fetchOptions;
|
|
299
342
|
}
|
|
343
|
+
if (this._timeout !== void 0) {
|
|
344
|
+
options.timeout = this._timeout;
|
|
345
|
+
}
|
|
300
346
|
if (this._onRequest) {
|
|
301
347
|
options.onRequest = this._onRequest;
|
|
302
348
|
}
|
|
@@ -309,6 +355,28 @@ var RouteCallBuilder = class _RouteCallBuilder {
|
|
|
309
355
|
|
|
310
356
|
// src/nextjs/client/core.ts
|
|
311
357
|
var apiLogger = logger.child("@spfn/core:api-client");
|
|
358
|
+
function transportError(failure, site, timeout) {
|
|
359
|
+
if (failure.cause === "timeout") {
|
|
360
|
+
apiLogger.error("Request timeout", { ...site, timeout });
|
|
361
|
+
return new ApiError(`Request timeout after ${timeout}ms`, 408, site.url, void 0, "timeout");
|
|
362
|
+
}
|
|
363
|
+
if (failure.cause === "caller") {
|
|
364
|
+
apiLogger.debug("Request aborted by caller", site);
|
|
365
|
+
return new ApiError("Request aborted", 0, site.url, void 0, "aborted", { cause: failure.error });
|
|
366
|
+
}
|
|
367
|
+
const { error } = failure;
|
|
368
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
369
|
+
apiLogger.error("Request timeout", { ...site, timeout });
|
|
370
|
+
return new ApiError(`Request timeout after ${timeout}ms`, 408, site.url, void 0, "timeout");
|
|
371
|
+
}
|
|
372
|
+
const errorMessage = error instanceof Error ? error.message : "Network error";
|
|
373
|
+
apiLogger.error("Network error", {
|
|
374
|
+
...site,
|
|
375
|
+
error: errorMessage,
|
|
376
|
+
errorName: error instanceof Error ? error.name : "unknown"
|
|
377
|
+
});
|
|
378
|
+
return new ApiError(errorMessage, 0, site.url, void 0, "network");
|
|
379
|
+
}
|
|
312
380
|
function createApi(config = {}) {
|
|
313
381
|
const {
|
|
314
382
|
baseUrl = "/api/rpc",
|
|
@@ -424,43 +492,12 @@ function createApi(config = {}) {
|
|
|
424
492
|
if (debug) {
|
|
425
493
|
logRequest(apiLogger, routeName, method, fullUrl, !!init.body);
|
|
426
494
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
body = await parseResponseBody(response);
|
|
432
|
-
} catch (error) {
|
|
433
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
434
|
-
apiLogger.error("Request timeout", {
|
|
435
|
-
route: routeName,
|
|
436
|
-
method,
|
|
437
|
-
url: fullUrl,
|
|
438
|
-
timeout
|
|
439
|
-
});
|
|
440
|
-
throw new ApiError(
|
|
441
|
-
`Request timeout after ${timeout}ms`,
|
|
442
|
-
408,
|
|
443
|
-
fullUrl,
|
|
444
|
-
void 0,
|
|
445
|
-
"timeout"
|
|
446
|
-
);
|
|
447
|
-
}
|
|
448
|
-
const errorMessage = error instanceof Error ? error.message : "Network error";
|
|
449
|
-
apiLogger.error("Network error", {
|
|
450
|
-
route: routeName,
|
|
451
|
-
method,
|
|
452
|
-
url: fullUrl,
|
|
453
|
-
error: errorMessage,
|
|
454
|
-
errorName: error instanceof Error ? error.name : "unknown"
|
|
455
|
-
});
|
|
456
|
-
throw new ApiError(
|
|
457
|
-
errorMessage,
|
|
458
|
-
0,
|
|
459
|
-
fullUrl,
|
|
460
|
-
void 0,
|
|
461
|
-
"network"
|
|
462
|
-
);
|
|
495
|
+
const appliedTimeout = options.timeout ?? timeout;
|
|
496
|
+
const fetched = await fetchInAbortScope(fullUrl, init, appliedTimeout, customFetch);
|
|
497
|
+
if (!fetched.ok) {
|
|
498
|
+
throw transportError(fetched, { route: routeName, method, url: fullUrl }, appliedTimeout);
|
|
463
499
|
}
|
|
500
|
+
let { response, body } = fetched;
|
|
464
501
|
if (globalOnResponse) {
|
|
465
502
|
const result = await globalOnResponse(response, body);
|
|
466
503
|
response = result.response;
|
package/dist/nextjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/nextjs/client/debug-logs.ts","../../src/nextjs/client/errors.ts","../../src/nextjs/shared.ts","../../src/nextjs/client/helpers.ts","../../src/nextjs/client/csrf.ts","../../src/nextjs/client/builder.ts","../../src/nextjs/client/core.ts"],"names":["logger","errorRegistry","coreErrorRegistry","headers"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Debug logging utilities for API client\n * Separates debug logging logic from main client code for better maintainability\n */\nimport type { Logger } from '@spfn/core/logger';\n\nexport function logCookieAutoDetection(\n logger: Logger,\n cookies: Array<{ name: string; value: string }>,\n): void\n{\n logger.debug('Auto-detected server environment, forwarding cookies', {\n cookieCount: cookies.length,\n cookieNames: cookies.map(c => c.name),\n });\n}\n\nexport function logRequest(\n logger: Logger,\n routeName: string,\n method: string,\n url: string,\n hasBody: boolean,\n): void\n{\n logger.debug('→ Request', {\n route: routeName,\n method,\n url,\n hasBody,\n });\n}\n\nexport function logResponse(\n logger: Logger,\n routeName: string,\n status: number,\n hasBody: boolean,\n): void\n{\n logger.debug('← Response', {\n route: routeName,\n status,\n hasBody,\n });\n}\n\nexport function logErrorResponse(\n logger: Logger,\n status: number,\n body: any,\n): void\n{\n logger.debug('Error response received', {\n status,\n hasBody: !!body,\n bodyType: typeof body,\n hasTypeField: body && typeof body === 'object' && '__type' in body,\n typeValue: body?.__type,\n });\n}\n\nexport function logErrorDeserializationAttempt(\n logger: Logger,\n errorType: string,\n registeredTypes: string[],\n): void\n{\n logger.debug('Attempting error deserialization', {\n errorType,\n hasRegistry: true,\n registeredTypes,\n });\n}\n\nexport function logErrorDeserializationSuccess(\n logger: Logger,\n error: Error | null,\n): void\n{\n logger.debug('Error deserialized successfully', {\n errorName: error?.name,\n errorConstructor: error?.constructor.name,\n message: error?.message,\n });\n}\n\nexport function logErrorDeserializationFailure(\n logger: Logger,\n error: unknown,\n): void\n{\n logger.debug('Deserialization failed', {\n errorName: error instanceof Error ? error.name : 'unknown',\n errorMessage: error instanceof Error ? error.message : String(error),\n });\n}\n\nexport function logErrorDeserializationSkipped(\n logger: Logger,\n errorRegistry: any,\n body: any,\n): void\n{\n const reason = !errorRegistry\n ? 'no registry'\n : !body\n ? 'no body'\n : typeof body !== 'object'\n ? 'body not object'\n : !('__type' in body)\n ? 'no __type field'\n : 'unknown';\n\n logger.debug('Skipping error deserialization', { reason });\n}\n\nexport function logThrowingDeserializedError(\n logger: Logger,\n error: Error,\n): void\n{\n logger.debug('Throwing deserialized error', {\n errorName: error.name,\n errorConstructorName: error.constructor.name,\n prototype: Object.getPrototypeOf(error).constructor.name,\n });\n}\n","// ============================================================================\n// Client Error\n// ============================================================================\n\n/**\n * Typed client error\n */\nexport class ApiError extends Error\n{\n constructor(\n message: string,\n public readonly status: number,\n public readonly url: string,\n public readonly response?: unknown,\n public readonly errorType?: 'http' | 'network' | 'timeout',\n )\n {\n super(message);\n this.name = 'ApiError';\n }\n}\n","/**\n * Shared utilities for Next.js client and proxy modules\n *\n * Contains common functions used by both client and proxy to avoid code duplication.\n */\n\n/**\n * Build URL with path parameters replaced\n *\n * @example\n * buildUrlWithParams('/users/:id/posts/:postId', { id: '123', postId: '456' })\n * // Returns: '/users/123/posts/456'\n */\nexport function buildUrlWithParams(path: string, params: Record<string, any>): string\n{\n let url = path;\n for (const [key, value] of Object.entries(params))\n {\n url = url.replace(`:${key}`, encodeURIComponent(String(value)));\n }\n\n return url;\n}\n\n/**\n * Build query string from object\n *\n * @example\n * buildQueryString({ page: '1', limit: '10', tags: ['foo', 'bar'] })\n * // Returns: '?page=1&limit=10&tags=foo&tags=bar'\n */\nexport function buildQueryString(query: Record<string, any>): string\n{\n if (Object.keys(query).length === 0)\n {\n return '';\n }\n\n const searchParams = new URLSearchParams();\n for (const [key, value] of Object.entries(query))\n {\n if (Array.isArray(value))\n {\n value.forEach((v) => searchParams.append(key, String(v)));\n }\n else\n {\n searchParams.append(key, String(value));\n }\n }\n\n return `?${searchParams.toString()}`;\n}\n\n/**\n * Build Cookie header string from cookies object\n *\n * @example\n * buildCookieHeader({ session: 'abc123', theme: 'dark' })\n * // Returns: 'session=abc123; theme=dark'\n */\nexport function buildCookieHeader(cookies: Record<string, string>): string\n{\n return Object.entries(cookies)\n .map(([key, value]) => `${key}=${value}`)\n .join('; ');\n}\n\n/**\n * Parse response body based on content type\n *\n * Handles:\n * - 204 No Content: returns null (no body expected)\n * - application/json: parses JSON body\n * - Other content types: returns raw text\n */\nexport async function parseResponseBody(response: Response): Promise<any>\n{\n // 204 No Content has no body\n if (response.status === 204)\n {\n return null;\n }\n\n const contentType = response.headers.get('content-type');\n\n if (contentType?.includes('application/json'))\n {\n const text = await response.text();\n\n return text ? JSON.parse(text) : null;\n }\n else\n {\n return await response.text();\n }\n}\n","import type { Logger } from '@spfn/core/logger';\nimport type { ErrorRegistry } from '@spfn/core/errors';\nimport { logger } from '@spfn/core/logger';\nimport { ApiError } from './errors';\nimport * as debugLogs from './debug-logs';\n\n// Re-export shared utilities\nexport { buildCookieHeader, parseResponseBody } from '../shared';\n\nconst cookieLogger = logger.child('@spfn/core:auto-cookies');\n\n/**\n * Auto-detect cookies from Next.js server environment\n * Returns empty object if not in server environment or if cookies are not accessible\n */\nexport async function autoDetectServerCookies(): Promise<Record<string, string>>\n{\n // Client environment — browser sends cookies automatically\n if (typeof window !== 'undefined')\n {\n return {};\n }\n\n try\n {\n // Next.js cookies() API is only available in server environment\n const { cookies } = await import('next/headers');\n const cookieStore = await cookies();\n const allCookies = cookieStore.getAll();\n\n const result = Object.fromEntries(\n allCookies.map(cookie => [cookie.name, cookie.value]),\n );\n\n cookieLogger.debug('Server cookies detected', {\n count: allCookies.length,\n names: allCookies.map(c => c.name),\n });\n\n return result;\n }\n catch (error)\n {\n // Server environment but cookies() not accessible\n // (e.g. static generation, build time, or outside request context)\n const err = error as Error;\n cookieLogger.warn('Failed to read server cookies', {\n message: err.message,\n name: err.name,\n });\n\n return {};\n }\n}\n\n/**\n * Execute fetch with timeout and abort controller\n */\nexport async function executeFetchWithTimeout(\n url: string,\n init: RequestInit,\n timeout: number,\n customFetch: typeof fetch = fetch,\n): Promise<Response>\n{\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try\n {\n const response = await customFetch(url, {\n ...init,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n return response;\n }\n catch (error)\n {\n clearTimeout(timeoutId);\n throw error;\n }\n}\n\n/**\n * Handle error response with deserialization support\n * Attempts to deserialize custom errors if errorRegistry is provided\n * Falls back to ApiError if deserialization fails or is not available\n */\nexport async function handleErrorResponse(\n response: Response,\n body: any,\n fullUrl: string,\n errorRegistry: ErrorRegistry | undefined,\n debug: boolean,\n logger: Logger,\n): Promise<never>\n{\n if (debug)\n {\n debugLogs.logErrorResponse(logger, response.status, body);\n }\n\n // Try to deserialize error if registry is provided\n let deserializedError: Error | null = null;\n\n if (errorRegistry && body && typeof body === 'object' && '__type' in body)\n {\n if (debug)\n {\n debugLogs.logErrorDeserializationAttempt(logger, body.__type, errorRegistry.getRegisteredTypes());\n }\n\n try\n {\n deserializedError = errorRegistry.deserialize(body as any);\n\n if (debug)\n {\n debugLogs.logErrorDeserializationSuccess(logger, deserializedError);\n }\n }\n catch (deserializeError)\n {\n // Deserialization itself failed (type not found, invalid data, etc.)\n if (debug)\n {\n debugLogs.logErrorDeserializationFailure(logger, deserializeError);\n }\n // Fall through to ApiError below\n }\n }\n else if (debug)\n {\n debugLogs.logErrorDeserializationSkipped(logger, errorRegistry, body);\n }\n\n // If deserialization succeeded, throw the deserialized error\n if (deserializedError)\n {\n if (debug)\n {\n debugLogs.logThrowingDeserializedError(logger, deserializedError);\n }\n\n throw deserializedError;\n }\n\n // Fallback to generic ApiError\n if (response.status === 404 && process.env.NODE_ENV !== 'production')\n {\n logger.warn(\n '\\n⚠️ 404 Not Found\\n\\n' +\n 'Check the following:\\n' +\n ' 1. Routes are registered in server.config.ts:\\n' +\n ' → defineServerConfig().routes(appRouter)\\n' +\n ' 2. Delete .spfn cache if you recently added new routes:\\n' +\n ' → rm -rf .spfn\\n',\n );\n }\n\n throw new ApiError(\n body?.message || `HTTP ${response.status}: ${response.statusText}`,\n response.status,\n fullUrl,\n body,\n 'http',\n );\n}\n","/**\n * CSRF token transport for the RPC client\n *\n * The Next.js proxy sets a readable (non-HttpOnly) cookie carrying an HMAC of\n * the session's key id; this module mirrors it into a request header. The proxy\n * never compares cookie against header — it recomputes the value from the\n * session — so the cookie is transport only and reading it here is not a trust\n * decision. See the CSRF section of the @spfn/auth README.\n */\n\n/**\n * Header the readable CSRF cookie is mirrored into.\n *\n * Must match CSRF_HEADER in @spfn/auth. It is duplicated rather than imported\n * because @spfn/core cannot depend on @spfn/auth.\n */\nexport const CSRF_HEADER = 'x-spfn-csrf';\n\n/**\n * Cookie names carrying a CSRF token.\n *\n * @spfn/auth suffixes its cookie names with the server port so several local dev\n * instances on the same host do not overwrite each other's session, and the\n * browser cannot know that port — so match the family, not one name. Every\n * candidate is sent and the proxy accepts the request if any of them recomputes\n * to the expected value; a candidate an attacker tossed in never will.\n */\nconst CSRF_COOKIE_PATTERN = /^spfn_csrf(_\\d+)?$/;\n\n/**\n * Values that can be put in a header at all: printable ASCII, no space.\n *\n * A cookie jar is attacker-writable in the very case this feature exists for, and\n * `new Headers()` rejects a value outside Latin-1 — so a sibling subdomain that\n * tosses `spfn_csrf=한` would make every RPC call throw before it was sent, not\n * merely fail the check. A genuine token is 64 hex characters; anything that\n * cannot be a header value cannot be one.\n */\nconst HEADER_SAFE_VALUE = /^[!-~]+$/;\n\n/**\n * Upper bound on candidates sent, so a cookie-flooded jar stays a bounded header.\n *\n * Deliberately generous. The server recomputes the expected value once and then\n * only compares fixed-length strings, so extra candidates cost it almost nothing,\n * while every candidate dropped here is a chance to lock the user out of every\n * mutation — the far worse failure. Must not exceed the matching bound in\n * @spfn/auth's `matchesCsrfToken`, or values sent would never be looked at.\n */\nconst MAX_CANDIDATES = 32;\n\n/**\n * Choose which candidates to send when the jar holds more than the cap.\n *\n * `document.cookie` exposes neither Domain nor Path, so a tossed cookie is\n * indistinguishable from the genuine one here — the proxy is what tells them\n * apart, by recomputing. What this can do is refuse to drop the genuine value on\n * the floor. Browsers order the jar by path length descending, then by age\n * ascending, so a flood pushes the genuine cookie towards the end (attacker\n * cookies on a longer path) or towards the start (attacker cookies written later\n * on the same path). Keeping both ends survives either, where a plain prefix\n * survives only one.\n *\n * A jar flooded far past the cap is a denial of service that no client-side\n * choice fully prevents; the structural fix is a `__Host-` prefixed cookie name,\n * which a sibling subdomain cannot write at all.\n */\nfunction selectCandidates(values: string[]): string[]\n{\n // Identical duplicates carry no extra information, so collapsing them buys\n // back slots that a flood of one repeated value would otherwise burn.\n const unique = Array.from(new Set(values));\n\n if (unique.length <= MAX_CANDIDATES)\n {\n return unique;\n }\n\n const half = MAX_CANDIDATES / 2;\n\n return [...unique.slice(0, half), ...unique.slice(-half)];\n}\n\n/**\n * Collect the CSRF token candidates from cookie entries.\n *\n * Takes entries rather than a map because a name can legitimately appear twice:\n * a sibling subdomain that tosses `spfn_csrf` for the parent domain leaves the\n * browser holding two cookies of that name, and folding them into a map would\n * throw away whichever the tossed one displaced — locking the user out of every\n * mutation. Sending both keeps the real one in play, and the tossed one is inert\n * because the proxy recomputes what it expects.\n *\n * @returns Header value (comma-separated), or undefined when no cookie exists\n */\nexport function csrfHeaderValue(cookies: Iterable<[string, string]>): string | undefined\n{\n // A comma separates candidates on the wire, so a value containing one would\n // arrive as several. That cannot be a genuine token — they are hex — but it\n // would let a single tossed cookie spend the receiver's whole candidate\n // budget and bury the real value behind it. Drop those, and anything that\n // could not be a header value at all.\n const values = Array.from(cookies)\n .filter(([name, value]) => CSRF_COOKIE_PATTERN.test(name)\n && HEADER_SAFE_VALUE.test(value)\n && !value.includes(','))\n .map(([, value]) => value);\n\n const candidates = selectCandidates(values);\n\n return candidates.length > 0 ? candidates.join(',') : undefined;\n}\n\n/**\n * Read the browser cookie jar as entries, duplicates preserved.\n *\n * Empty outside a browser (SSR reads the jar it forwards instead).\n */\nexport function documentCookieEntries(): Array<[string, string]>\n{\n if (typeof document === 'undefined' || !document.cookie)\n {\n return [];\n }\n\n const entries: Array<[string, string]> = [];\n\n for (const pair of document.cookie.split(';'))\n {\n const separator = pair.indexOf('=');\n\n if (separator > 0)\n {\n entries.push([pair.slice(0, separator).trim(), pair.slice(separator + 1).trim()]);\n }\n }\n\n return entries;\n}\n","// ============================================================================\n// Route Call Builder (Structured Input API)\n// ============================================================================\n\nimport type { RouteDef, Router } from '@spfn/core/route';\nimport type {\n CallOptions,\n ExtractRoutes,\n InferRouteInput,\n InferRouteOutput,\n RequestInterceptor,\n ResponseInterceptor,\n} from './types';\n\n/**\n * Pick only non-empty fields from StructuredInput\n *\n * This removes fields that are empty objects `{}` from the input type,\n * so users only need to provide fields that are actually defined in the route.\n */\ntype PickNonEmpty<T> = {\n [K in keyof T as T[K] extends Record<string, never> ? never : K]: T[K];\n};\n\n/**\n * Make fields that can be undefined into optional fields\n *\n * When a field is defined as `Type.Optional(Type.Object({...}))`,\n * the resulting type is `T | undefined`. This utility converts such fields\n * into proper optional fields (`field?: T`) so users don't need to pass them.\n */\ntype MakeOptionalIfUndefinable<T> =\n // Required fields (undefined is not assignable)\n { [K in keyof T as undefined extends T[K] ? never : K]: T[K] }\n // Optional fields (undefined is assignable)\n & { [K in keyof T as undefined extends T[K] ? K : never]?: Exclude<T[K], undefined> };\n\n/**\n * Clean structured input - only include fields that have actual schema,\n * and make fields optional if they accept undefined\n */\ntype CleanStructuredInput<TInput> = MakeOptionalIfUndefinable<PickNonEmpty<TInput>>;\n\n/**\n * Check if input has any required fields\n *\n * Returns false if all fields are optional (i.e., {} is assignable to the input type)\n */\ntype HasAnyRequiredFields<TInput> = {} extends CleanStructuredInput<TInput> ? false : true;\n\n/**\n * Route call builder with structured input API\n *\n * Input is structured with explicit params, query, body fields\n * that match the server-side route definition.\n *\n * @example\n * ```typescript\n * // GET /users/:id - params only\n * const user = await api.getUser.call({ params: { id: '1' } });\n *\n * // GET /users/:id?include=posts - params + query\n * const user = await api.getUser.call({\n * params: { id: '1' },\n * query: { include: 'posts' }\n * });\n *\n * // POST /users - body only\n * const user = await api.createUser.call({\n * body: { name: 'John', email: 'john@example.com' }\n * });\n *\n * // PUT /users/:id - params + body\n * const user = await api.updateUser.call({\n * params: { id: '1' },\n * body: { name: 'Jane' }\n * });\n *\n * // With options (headers, cookies, Next.js caching)\n * const user = await api.getUser\n * .headers({ 'X-Custom': 'value' })\n * .fetchOptions({ next: { revalidate: 60 } })\n * .call({ params: { id: '1' } });\n * ```\n */\nexport class RouteCallBuilder<\n TInput,\n TOutput,\n>\n{\n private _headers?: Record<string, string>;\n private _cookies?: Record<string, string>;\n private _fetchOptions?: RequestInit;\n private _onRequest?: RequestInterceptor;\n private _onResponse?: ResponseInterceptor;\n\n constructor(\n private readonly executor: (input: any, options: CallOptions) => Promise<TOutput>,\n private readonly routeName: string,\n ) \n {}\n\n /**\n * Clone builder\n */\n private clone(): RouteCallBuilder<TInput, TOutput>\n {\n const builder = new RouteCallBuilder<TInput, TOutput>(\n this.executor,\n this.routeName,\n );\n builder._headers = this._headers;\n builder._cookies = this._cookies;\n builder._fetchOptions = this._fetchOptions;\n builder._onRequest = this._onRequest;\n builder._onResponse = this._onResponse;\n\n return builder;\n }\n\n /**\n * Set request headers\n */\n headers(headers: Record<string, string>): RouteCallBuilder<TInput, TOutput>\n {\n const builder = this.clone();\n builder._headers = { ...this._headers, ...headers };\n\n return builder;\n }\n\n /**\n * Set cookies\n */\n cookies(cookies: Record<string, string>): RouteCallBuilder<TInput, TOutput>\n {\n const builder = this.clone();\n builder._cookies = { ...this._cookies, ...cookies };\n\n return builder;\n }\n\n /**\n * Set Next.js fetch options\n */\n fetchOptions(options: RequestInit & { next?: { revalidate?: number | false; tags?: string[] } }): RouteCallBuilder<TInput, TOutput>\n {\n const builder = this.clone();\n builder._fetchOptions = { ...this._fetchOptions, ...options };\n\n return builder;\n }\n\n /**\n * Set request interceptor\n */\n onRequest(interceptor: RequestInterceptor): RouteCallBuilder<TInput, TOutput>\n {\n const builder = this.clone();\n builder._onRequest = interceptor;\n\n return builder;\n }\n\n /**\n * Set response interceptor\n */\n onResponse(interceptor: ResponseInterceptor): RouteCallBuilder<TInput, TOutput>\n {\n const builder = this.clone();\n builder._onResponse = interceptor;\n\n return builder;\n }\n\n /**\n * Execute the API call with structured input\n *\n * Input structure matches the server-side route definition:\n * - params: Path parameters (e.g., { id: '123' } for /users/:id)\n * - query: Query string parameters\n * - body: Request body (for POST, PUT, PATCH)\n */\n call(input?: CleanStructuredInput<TInput>): Promise<TOutput>\n {\n const options: CallOptions = {};\n\n if (this._headers)\n {\n options.headers = this._headers;\n }\n\n if (this._cookies)\n {\n options.cookies = this._cookies;\n }\n\n if (this._fetchOptions)\n {\n options.fetchOptions = this._fetchOptions;\n }\n\n if (this._onRequest)\n {\n options.onRequest = this._onRequest;\n }\n\n if (this._onResponse)\n {\n options.onResponse = this._onResponse;\n }\n\n return this.executor(input || {}, options);\n }\n}\n\n/**\n * Individual route client with structured input API\n */\nexport type RouteClient<TRoute extends RouteDef<any, any>> = {\n /**\n * Set request headers\n */\n headers(headers: Record<string, string>): RouteClient<TRoute>;\n\n /**\n * Set cookies\n */\n cookies(cookies: Record<string, string>): RouteClient<TRoute>;\n\n /**\n * Set Next.js fetch options\n */\n fetchOptions(options: RequestInit & { next?: { revalidate?: number | false; tags?: string[] } }): RouteClient<TRoute>;\n\n /**\n * Set request interceptor\n */\n onRequest(interceptor: RequestInterceptor): RouteClient<TRoute>;\n\n /**\n * Set response interceptor\n */\n onResponse(interceptor: ResponseInterceptor): RouteClient<TRoute>;\n\n /**\n * Execute the API call with structured input\n *\n * @example\n * ```typescript\n * // GET /users/:id\n * api.getUser.call({ params: { id: '123' } });\n *\n * // PUT /users/:id\n * api.updateUser.call({ params: { id: '123' }, body: { name: 'Jane' } });\n * ```\n */\n call: HasAnyRequiredFields<InferRouteInput<TRoute>> extends true\n ? (input: CleanStructuredInput<InferRouteInput<TRoute>>) => Promise<InferRouteOutput<TRoute>>\n : (input?: CleanStructuredInput<InferRouteInput<TRoute>>) => Promise<InferRouteOutput<TRoute>>;\n};\n\n/**\n * One property per route in a flattened route record.\n *\n * A slot the flattening could not resolve to a route definition is `never`, as it was\n * before nesting was flattened — a record whose values are neither routes nor routers\n * names nothing callable.\n */\ntype RouteClients<TRoutes> = {\n [K in keyof TRoutes]: TRoutes[K] extends RouteDef<any, any, any>\n ? RouteClient<TRoutes[K]>\n : never;\n};\n\n/**\n * Typed client for entire router\n *\n * One flat namespace, holding the same route names the server registers. A route\n * declared inside a nested `defineRouter` is reached by its own key — `api.getThing`,\n * never `api.group.getThing` — because that is the name `registerRoutes` mounts it\n * under, the name the RPC proxy resolves, and the name `RouterOutput` takes. The group\n * key is not a route and naming it is a compile error.\n *\n * @example\n * ```typescript\n * const appRouter = defineRouter({\n * getRoot,\n * users: defineRouter({ getUser }),\n * });\n *\n * const api = createApi<typeof appRouter>();\n *\n * await api.getUser.call({ params: { id: '1' } }); // not api.users.getUser\n * ```\n *\n * A name two branches both declare is left out of the namespace rather than resolved to\n * one of them — the same collision `RouterOutput` refuses. Routes mounted with\n * `.packages()` are deliberately absent: a package publishes its own route map and its\n * own client (`authApi`, `cmsApi`), and that is what calls them.\n */\nexport type Client<TRouter extends Router<any>> = RouteClients<ExtractRoutes<TRouter>>;\n","/**\n * Type-Safe RPC-Style Client with Structured Input API\n *\n * Provides full end-to-end type safety from server routes to client calls.\n * No metadata codegen required - method/path resolution happens at the proxy layer.\n *\n * @example\n * ```typescript\n * // Server\n * export const appRouter = defineRouter({\n * getUser: route.get('/users/:id')\n * .input({ params: Type.Object({ id: Type.String() }) })\n * .handler(async (c) => { ... }),\n * createUser: route.post('/users')\n * .input({ body: Type.Object({ name: Type.String() }) })\n * .handler(async (c) => { ... }),\n * });\n *\n * export type AppRouter = typeof appRouter;\n *\n * // Client - no metadata needed!\n * const api = createApi<AppRouter>();\n *\n * // ✅ GET (no body) - becomes GET /api/rpc/getUser?input={...}\n * const user = await api.getUser.call({ params: { id: '1' } });\n *\n * // ✅ POST (has body) - becomes POST /api/rpc/createUser\n * const newUser = await api.createUser.call({ body: { name: 'John' } });\n *\n * // ✅ With options (headers, cookies, interceptors)\n * const user = await api.getUser\n * .headers({ 'X-Custom': 'value' })\n * .cookies({ session: 'xxx' })\n * .fetchOptions({ next: { revalidate: 60 } })\n * .call({ params: { id: '1' } });\n * ```\n */\nimport { env } from '@spfn/core/config';\nimport { ErrorRegistry, errorRegistry as coreErrorRegistry } from '@spfn/core/errors';\nimport { logger } from '@spfn/core/logger';\nimport type { Router } from '@spfn/core/route';\nimport * as debugLogs from './debug-logs';\nimport { ApiError } from './errors';\nimport {\n parseResponseBody,\n executeFetchWithTimeout,\n handleErrorResponse,\n buildCookieHeader,\n autoDetectServerCookies,\n} from './helpers';\nimport { CSRF_HEADER, csrfHeaderValue, documentCookieEntries } from './csrf';\nimport { RouteCallBuilder } from './builder';\nimport type { ApiConfig, CallOptions } from './types';\nimport type { Client } from './builder';\n\nconst apiLogger = logger.child('@spfn/core:api-client');\n\n// ============================================================================\n// Client Implementation\n// ============================================================================\n\n/**\n * Create type-safe RPC client\n *\n * No metadata required - the client sends routeName + input to the proxy,\n * and the proxy resolves the actual HTTP method and path from the router.\n *\n * @example\n * ```typescript\n * // Client - no metadata needed!\n * const api = createApi<AppRouter>();\n *\n * // GET request (no body) - browser cacheable\n * const user = await api.getUser.call({ params: { id: '1' } });\n *\n * // POST request (has body)\n * const newUser = await api.createUser.call({ body: { name: 'John' } });\n * ```\n */\nexport function createApi<TRouter extends Router<any>>(\n config: ApiConfig = {},\n): Client<TRouter>\n{\n const {\n baseUrl = '/api/rpc',\n headers: defaultHeaders = {},\n timeout = env.SERVER_TIMEOUT,\n fetch: customFetch = fetch,\n onRequest: globalOnRequest,\n onResponse: globalOnResponse,\n errorRegistry: errorRegistryConfig,\n debug = false,\n } = config;\n\n // Normalize errorRegistry: always include coreErrorRegistry\n const errorRegistry = Array.isArray(errorRegistryConfig)\n ? new ErrorRegistry([coreErrorRegistry, ...errorRegistryConfig])\n : errorRegistryConfig ?? coreErrorRegistry;\n\n if (debug)\n {\n apiLogger.debug('API client initialized', { baseUrl });\n }\n\n /**\n * Execute API call\n *\n * Determines GET vs POST based on body/formData presence:\n * - No body/formData → GET /api/rpc/{routeName}?input={...}\n * - Has body → POST /api/rpc/{routeName} with JSON body\n * - Has formData → POST /api/rpc/{routeName} with multipart/form-data\n */\n async function executeCall(\n routeName: string,\n input: any = {},\n options: CallOptions = {},\n ): Promise<any>\n {\n const hasBody = input.body !== undefined;\n const hasFormData = input.formData !== undefined && Object.keys(input.formData).length > 0;\n const method = (hasBody || hasFormData) ? 'POST' : 'GET';\n\n // Build full URL - handle SSR case where SPFN_APP_URL might not be set\n let appUrl = env.SPFN_APP_URL || '';\n\n // In SSR environment, if SPFN_APP_URL is not set, try to get host from request headers\n if (!appUrl && typeof window === 'undefined')\n {\n try\n {\n const { headers } = await import('next/headers');\n const headersList = await headers();\n const host = headersList.get('host');\n const protocol = headersList.get('x-forwarded-proto') || 'http';\n if (host)\n {\n appUrl = `${protocol}://${host}`;\n if (debug)\n {\n apiLogger.debug(`Auto-detected app URL from headers: ${appUrl}`);\n }\n }\n }\n catch\n {\n // Fallback: use relative URL and let fetch handle it\n if (debug)\n {\n apiLogger.warn('Could not determine app URL in SSR environment, using relative URL');\n }\n }\n }\n\n // Build URL based on method\n let fullUrl: string;\n if (method === 'GET')\n {\n // GET: encode input in query string\n const inputParam = encodeURIComponent(JSON.stringify(input));\n fullUrl = `${appUrl}${baseUrl}/${routeName}?input=${inputParam}`;\n }\n else\n {\n // POST: input goes in body\n fullUrl = `${appUrl}${baseUrl}/${routeName}`;\n }\n\n // Prepare headers\n // Note: Don't set Content-Type for formData - browser sets it with boundary\n const headers: Record<string, string> = {\n ...(hasFormData ? {} : { 'Content-Type': 'application/json' }),\n ...defaultHeaders,\n ...options.headers,\n };\n\n // Auto-detect server cookies and merge with user-provided cookies\n const autoDetectedCookies = await autoDetectServerCookies();\n const cookiesToSend = {\n ...autoDetectedCookies,\n ...(options.cookies || {}),\n };\n\n // Add Cookie header if we have cookies to send\n if (Object.keys(cookiesToSend).length > 0)\n {\n headers['Cookie'] = buildCookieHeader(cookiesToSend);\n }\n\n // Mirror the readable CSRF cookie into a header, from the browser jar or\n // — server-side — from the jar being forwarded.\n //\n // Carried on GET calls too, deliberately: the wire method here is chosen\n // by whether the input has a body, not by what the route does. A bodyless\n // mutation (`api.logout.call({})`) travels as GET and the proxy still\n // forwards it as the route's POST, so a GET-shaped call can be exactly\n // the request that needs the header. The proxy decides what to check from\n // the resolved route method, and ignores the header on safe routes.\n const csrfToken = csrfHeaderValue(\n typeof window === 'undefined' ? Object.entries(cookiesToSend) : documentCookieEntries(),\n );\n\n const csrfSetByCaller = Object.keys(headers).some(name => name.toLowerCase() === CSRF_HEADER);\n\n if (csrfToken && !csrfSetByCaller)\n {\n headers[CSRF_HEADER] = csrfToken;\n }\n\n // Log cookie auto-detection if debug enabled\n if (debug && Object.keys(autoDetectedCookies).length > 0)\n {\n const cookieArray = Object.entries(autoDetectedCookies).map(([name, value]) => ({ name, value }));\n debugLogs.logCookieAutoDetection(apiLogger, cookieArray);\n }\n\n // Build request init\n const requestInit: RequestInit = {\n method,\n headers,\n ...options.fetchOptions,\n };\n\n // Add body for POST\n if (method === 'POST')\n {\n if (hasFormData)\n {\n // Build FormData for file uploads\n const formData = new FormData();\n\n // Add non-formData fields (params, query, headers, cookies) as JSON metadata\n const metadata: Record<string, any> = {};\n if (input.params) metadata.params = input.params;\n if (input.query) metadata.query = input.query;\n if (input.headers) metadata.headers = input.headers;\n if (input.cookies) metadata.cookies = input.cookies;\n\n if (Object.keys(metadata).length > 0)\n {\n formData.append('__metadata', JSON.stringify(metadata));\n }\n\n // Add formData fields\n for (const [key, value] of Object.entries(input.formData))\n {\n if (value instanceof File)\n {\n formData.append(key, value);\n }\n else if (Array.isArray(value))\n {\n // Handle array of files or values\n for (const item of value)\n {\n if (item instanceof File)\n {\n formData.append(key, item);\n }\n else\n {\n formData.append(key, String(item));\n }\n }\n }\n else if (value !== undefined && value !== null)\n {\n formData.append(key, String(value));\n }\n }\n\n requestInit.body = formData;\n }\n else\n {\n requestInit.body = JSON.stringify(input);\n }\n }\n\n // Execute request interceptors\n let init = requestInit;\n if (globalOnRequest)\n {\n init = await globalOnRequest(fullUrl, init);\n }\n if (options.onRequest)\n {\n init = await options.onRequest(fullUrl, init);\n }\n\n if (debug)\n {\n debugLogs.logRequest(apiLogger, routeName, method, fullUrl, !!init.body);\n }\n\n // Execute fetch with timeout\n let response: Response;\n let body: any;\n\n try\n {\n response = await executeFetchWithTimeout(fullUrl, init, timeout, customFetch);\n\n // Parse response\n body = await parseResponseBody(response);\n }\n catch (error)\n {\n // Handle timeout specifically\n if (error instanceof Error && error.name === 'AbortError')\n {\n apiLogger.error('Request timeout', {\n route: routeName,\n method,\n url: fullUrl,\n timeout,\n });\n\n throw new ApiError(\n `Request timeout after ${timeout}ms`,\n 408,\n fullUrl,\n undefined,\n 'timeout',\n );\n }\n\n // Network error\n const errorMessage = error instanceof Error ? error.message : 'Network error';\n apiLogger.error('Network error', {\n route: routeName,\n method,\n url: fullUrl,\n error: errorMessage,\n errorName: error instanceof Error ? error.name : 'unknown',\n });\n\n throw new ApiError(\n errorMessage,\n 0,\n fullUrl,\n undefined,\n 'network',\n );\n }\n\n // Execute global + local response interceptors\n //\n // These run outside the try/catch on purpose. That catch classifies a transport\n // failure, and an interceptor throwing is not one: Next.js implements `redirect()`\n // and `notFound()` by throwing an error carrying a digest, and an interceptor\n // raising a domain error of its own is not a transport failure either. Run inside\n // the try, every one of those became `ApiError(..., 0, ..., 'network')` — the\n // navigation never happened and the caller was handed a network failure that never\n // occurred, which retry and offline handling keyed on `errorType === 'network'`\n // act on.\n if (globalOnResponse)\n {\n const result = await globalOnResponse(response, body);\n response = result.response;\n body = result.body;\n }\n if (options.onResponse)\n {\n const result = await options.onResponse(response, body);\n response = result.response;\n body = result.body;\n }\n\n if (debug)\n {\n debugLogs.logResponse(apiLogger, routeName, response.status, !!body);\n }\n\n // Handle error responses\n if (!response.ok)\n {\n await handleErrorResponse(response, body, fullUrl, errorRegistry, debug, apiLogger);\n }\n\n return body;\n }\n\n /**\n * Build client proxy\n *\n * Every property access returns a `RouteCallBuilder` for that one property read as a\n * whole route name, and that name goes on the wire as `/api/rpc/{routeName}`. The\n * proxy route resolves it against the generated route map, whose names are flat.\n *\n * Nesting needs nothing here: `registerRoutes` mounts a route declared inside a\n * nested `defineRouter` under its own key, so `api.getThing` is the call for a nested\n * `getThing` exactly as for a top-level one — which is the namespace `Client` types.\n * There is no dot-notation traversal: a property access never returns another proxy,\n * so `api.group.getThing` reads a property of a builder and is `undefined`.\n *\n * `prefix` is vestigial — nothing passes one, and adding a caller that did would\n * build a dotted name the route map cannot resolve.\n */\n function buildProxy(prefix = ''): any\n {\n return new Proxy(\n {},\n {\n get(_target, prop: string)\n {\n const currentPath = prefix ? `${prefix}.${prop}` : prop;\n\n // Return RouteCallBuilder that can either be called or chained\n return new RouteCallBuilder(\n (input: any, options: CallOptions) => executeCall(currentPath, input, options),\n currentPath,\n );\n },\n },\n );\n }\n\n return buildProxy() as Client<TRouter>;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/nextjs/client/debug-logs.ts","../../src/nextjs/client/errors.ts","../../src/nextjs/shared.ts","../../src/nextjs/client/helpers.ts","../../src/nextjs/client/csrf.ts","../../src/nextjs/client/builder.ts","../../src/nextjs/client/core.ts"],"names":["logger","errorRegistry","coreErrorRegistry","headers"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Debug logging utilities for API client\n * Separates debug logging logic from main client code for better maintainability\n */\nimport type { Logger } from '@spfn/core/logger';\n\nexport function logCookieAutoDetection(\n logger: Logger,\n cookies: Array<{ name: string; value: string }>,\n): void\n{\n logger.debug('Auto-detected server environment, forwarding cookies', {\n cookieCount: cookies.length,\n cookieNames: cookies.map(c => c.name),\n });\n}\n\nexport function logRequest(\n logger: Logger,\n routeName: string,\n method: string,\n url: string,\n hasBody: boolean,\n): void\n{\n logger.debug('→ Request', {\n route: routeName,\n method,\n url,\n hasBody,\n });\n}\n\nexport function logResponse(\n logger: Logger,\n routeName: string,\n status: number,\n hasBody: boolean,\n): void\n{\n logger.debug('← Response', {\n route: routeName,\n status,\n hasBody,\n });\n}\n\nexport function logErrorResponse(\n logger: Logger,\n status: number,\n body: any,\n): void\n{\n logger.debug('Error response received', {\n status,\n hasBody: !!body,\n bodyType: typeof body,\n hasTypeField: body && typeof body === 'object' && '__type' in body,\n typeValue: body?.__type,\n });\n}\n\nexport function logErrorDeserializationAttempt(\n logger: Logger,\n errorType: string,\n registeredTypes: string[],\n): void\n{\n logger.debug('Attempting error deserialization', {\n errorType,\n hasRegistry: true,\n registeredTypes,\n });\n}\n\nexport function logErrorDeserializationSuccess(\n logger: Logger,\n error: Error | null,\n): void\n{\n logger.debug('Error deserialized successfully', {\n errorName: error?.name,\n errorConstructor: error?.constructor.name,\n message: error?.message,\n });\n}\n\nexport function logErrorDeserializationFailure(\n logger: Logger,\n error: unknown,\n): void\n{\n logger.debug('Deserialization failed', {\n errorName: error instanceof Error ? error.name : 'unknown',\n errorMessage: error instanceof Error ? error.message : String(error),\n });\n}\n\nexport function logErrorDeserializationSkipped(\n logger: Logger,\n errorRegistry: any,\n body: any,\n): void\n{\n const reason = !errorRegistry\n ? 'no registry'\n : !body\n ? 'no body'\n : typeof body !== 'object'\n ? 'body not object'\n : !('__type' in body)\n ? 'no __type field'\n : 'unknown';\n\n logger.debug('Skipping error deserialization', { reason });\n}\n\nexport function logThrowingDeserializedError(\n logger: Logger,\n error: Error,\n): void\n{\n logger.debug('Throwing deserialized error', {\n errorName: error.name,\n errorConstructorName: error.constructor.name,\n prototype: Object.getPrototypeOf(error).constructor.name,\n });\n}\n","// ============================================================================\n// Client Error\n// ============================================================================\n\n/**\n * Typed client error\n *\n * `errorType` tells apart why a call failed:\n * - `'http'`: the server answered with a non-2xx status\n * - `'network'`: the fetch or the body read failed (status 0)\n * - `'timeout'`: the call's timeout elapsed before response headers arrived (status 408)\n * - `'aborted'`: the caller's own signal aborted the call (status 0); `cause` is the signal's reason\n */\nexport class ApiError extends Error\n{\n constructor(\n message: string,\n public readonly status: number,\n public readonly url: string,\n public readonly response?: unknown,\n public readonly errorType?: 'http' | 'network' | 'timeout' | 'aborted',\n options?: { cause?: unknown },\n )\n {\n super(message, options);\n this.name = 'ApiError';\n }\n}\n","/**\n * Shared utilities for Next.js client and proxy modules\n *\n * Contains common functions used by both client and proxy to avoid code duplication.\n */\n\n/**\n * Build URL with path parameters replaced\n *\n * @example\n * buildUrlWithParams('/users/:id/posts/:postId', { id: '123', postId: '456' })\n * // Returns: '/users/123/posts/456'\n */\nexport function buildUrlWithParams(path: string, params: Record<string, any>): string\n{\n let url = path;\n for (const [key, value] of Object.entries(params))\n {\n url = url.replace(`:${key}`, encodeURIComponent(String(value)));\n }\n\n return url;\n}\n\n/**\n * Build query string from object\n *\n * @example\n * buildQueryString({ page: '1', limit: '10', tags: ['foo', 'bar'] })\n * // Returns: '?page=1&limit=10&tags=foo&tags=bar'\n */\nexport function buildQueryString(query: Record<string, any>): string\n{\n if (Object.keys(query).length === 0)\n {\n return '';\n }\n\n const searchParams = new URLSearchParams();\n for (const [key, value] of Object.entries(query))\n {\n if (Array.isArray(value))\n {\n value.forEach((v) => searchParams.append(key, String(v)));\n }\n else\n {\n searchParams.append(key, String(value));\n }\n }\n\n return `?${searchParams.toString()}`;\n}\n\n/**\n * Build Cookie header string from cookies object\n *\n * @example\n * buildCookieHeader({ session: 'abc123', theme: 'dark' })\n * // Returns: 'session=abc123; theme=dark'\n */\nexport function buildCookieHeader(cookies: Record<string, string>): string\n{\n return Object.entries(cookies)\n .map(([key, value]) => `${key}=${value}`)\n .join('; ');\n}\n\n/**\n * Parse response body based on content type\n *\n * Handles:\n * - 204 No Content: returns null (no body expected)\n * - application/json: parses JSON body\n * - Other content types: returns raw text\n */\nexport async function parseResponseBody(response: Response): Promise<any>\n{\n // 204 No Content has no body\n if (response.status === 204)\n {\n return null;\n }\n\n const contentType = response.headers.get('content-type');\n\n if (contentType?.includes('application/json'))\n {\n const text = await response.text();\n\n return text ? JSON.parse(text) : null;\n }\n else\n {\n return await response.text();\n }\n}\n","import type { Logger } from '@spfn/core/logger';\nimport type { ErrorRegistry } from '@spfn/core/errors';\nimport { logger } from '@spfn/core/logger';\nimport { ApiError } from './errors';\nimport * as debugLogs from './debug-logs';\nimport { parseResponseBody } from '../shared';\n\n// Re-export shared utilities\nexport { buildCookieHeader, parseResponseBody } from '../shared';\n\nconst cookieLogger = logger.child('@spfn/core:auto-cookies');\n\n/**\n * Auto-detect cookies from Next.js server environment\n * Returns empty object if not in server environment or if cookies are not accessible\n */\nexport async function autoDetectServerCookies(): Promise<Record<string, string>>\n{\n // Client environment — browser sends cookies automatically\n if (typeof window !== 'undefined')\n {\n return {};\n }\n\n try\n {\n // Next.js cookies() API is only available in server environment\n const { cookies } = await import('next/headers');\n const cookieStore = await cookies();\n const allCookies = cookieStore.getAll();\n\n const result = Object.fromEntries(\n allCookies.map(cookie => [cookie.name, cookie.value]),\n );\n\n cookieLogger.debug('Server cookies detected', {\n count: allCookies.length,\n names: allCookies.map(c => c.name),\n });\n\n return result;\n }\n catch (error)\n {\n // Server environment but cookies() not accessible\n // (e.g. static generation, build time, or outside request context)\n const err = error as Error;\n cookieLogger.warn('Failed to read server cookies', {\n message: err.message,\n name: err.name,\n });\n\n return {};\n }\n}\n\n/**\n * Why a call was aborted, as recorded by the abort scope that aborted it\n */\nexport type AbortCause = 'timeout' | 'caller';\n\n/**\n * What a fetch run in an abort scope produced: the response and its parsed body,\n * or the failure together with the abort cause the scope recorded, if any. For a\n * caller abort, `error` is the caller signal's reason.\n */\nexport type ScopedFetchResult =\n | { ok: true; response: Response; body: any }\n | { ok: false; error: unknown; cause?: AbortCause };\n\n/**\n * One controller that two sources may abort: the call's timer and the caller's signal\n *\n * The first source to fire is recorded as the cause and later ones are ignored, so a\n * caller aborting after the timer fired still reads as a timeout. Composed by hand\n * rather than with `AbortSignal.any` / `AbortSignal.timeout`, which browsers below\n * Safari 17.4 lack.\n */\nfunction openAbortScope(callerSignal: AbortSignal | undefined, timeout: number)\n{\n const controller = new AbortController();\n let cause: AbortCause | undefined;\n\n const abort = (source: AbortCause, reason?: unknown) =>\n {\n cause ??= source;\n controller.abort(reason);\n };\n const onCallerAbort = () => abort('caller', callerSignal?.reason);\n const timeoutId = setTimeout(() => abort('timeout'), timeout);\n\n callerSignal?.addEventListener('abort', onCallerAbort, { once: true });\n\n return {\n signal: controller.signal,\n cause: () => cause,\n stopTimer: () => clearTimeout(timeoutId),\n close: () =>\n {\n clearTimeout(timeoutId);\n callerSignal?.removeEventListener('abort', onCallerAbort);\n },\n };\n}\n\n/**\n * Fetch and parse a response inside one abort scope\n *\n * The caller's signal is the one on the final `init`, after interceptors ran. The timer\n * stops when response headers arrive; the caller's abort stays effective until the body\n * is parsed. A failure is returned, not thrown, with the cause the scope recorded —\n * classification reads that record, never the error's name.\n *\n * A custom `fetch`'s body read may not honour the signal at all, so a caller abort during\n * that read can otherwise go unnoticed and the call resolves as if nothing happened. After\n * the body is parsed, the scope is checked again: a recorded `'caller'` cause still wins.\n */\nexport async function fetchInAbortScope(\n url: string,\n init: RequestInit,\n timeout: number,\n customFetch: typeof fetch = fetch,\n): Promise<ScopedFetchResult>\n{\n const callerSignal = init.signal ?? undefined;\n\n if (callerSignal?.aborted)\n {\n return { ok: false, error: callerSignal.reason, cause: 'caller' };\n }\n\n const scope = openAbortScope(callerSignal, timeout);\n\n try\n {\n const response = await customFetch(url, { ...init, signal: scope.signal });\n\n scope.stopTimer();\n\n const body = await parseResponseBody(response);\n\n if (scope.cause() === 'caller')\n {\n return { ok: false, error: callerSignal?.reason, cause: 'caller' };\n }\n\n return { ok: true, response, body };\n }\n catch (error)\n {\n const cause = scope.cause();\n\n return { ok: false, cause, error: cause === 'caller' ? callerSignal?.reason : error };\n }\n finally\n {\n scope.close();\n }\n}\n\n/**\n * Handle error response with deserialization support\n * Attempts to deserialize custom errors if errorRegistry is provided\n * Falls back to ApiError if deserialization fails or is not available\n */\nexport async function handleErrorResponse(\n response: Response,\n body: any,\n fullUrl: string,\n errorRegistry: ErrorRegistry | undefined,\n debug: boolean,\n logger: Logger,\n): Promise<never>\n{\n if (debug)\n {\n debugLogs.logErrorResponse(logger, response.status, body);\n }\n\n // Try to deserialize error if registry is provided\n let deserializedError: Error | null = null;\n\n if (errorRegistry && body && typeof body === 'object' && '__type' in body)\n {\n if (debug)\n {\n debugLogs.logErrorDeserializationAttempt(logger, body.__type, errorRegistry.getRegisteredTypes());\n }\n\n try\n {\n deserializedError = errorRegistry.deserialize(body as any);\n\n if (debug)\n {\n debugLogs.logErrorDeserializationSuccess(logger, deserializedError);\n }\n }\n catch (deserializeError)\n {\n // Deserialization itself failed (type not found, invalid data, etc.)\n if (debug)\n {\n debugLogs.logErrorDeserializationFailure(logger, deserializeError);\n }\n // Fall through to ApiError below\n }\n }\n else if (debug)\n {\n debugLogs.logErrorDeserializationSkipped(logger, errorRegistry, body);\n }\n\n // If deserialization succeeded, throw the deserialized error\n if (deserializedError)\n {\n if (debug)\n {\n debugLogs.logThrowingDeserializedError(logger, deserializedError);\n }\n\n throw deserializedError;\n }\n\n // Fallback to generic ApiError\n if (response.status === 404 && process.env.NODE_ENV !== 'production')\n {\n logger.warn(\n '\\n⚠️ 404 Not Found\\n\\n' +\n 'Check the following:\\n' +\n ' 1. Routes are registered in server.config.ts:\\n' +\n ' → defineServerConfig().routes(appRouter)\\n' +\n ' 2. Delete .spfn cache if you recently added new routes:\\n' +\n ' → rm -rf .spfn\\n',\n );\n }\n\n throw new ApiError(\n body?.message || `HTTP ${response.status}: ${response.statusText}`,\n response.status,\n fullUrl,\n body,\n 'http',\n );\n}\n","/**\n * CSRF token transport for the RPC client\n *\n * The Next.js proxy sets a readable (non-HttpOnly) cookie carrying an HMAC of\n * the session's key id; this module mirrors it into a request header. The proxy\n * never compares cookie against header — it recomputes the value from the\n * session — so the cookie is transport only and reading it here is not a trust\n * decision. See the CSRF section of the @spfn/auth README.\n */\n\n/**\n * Header the readable CSRF cookie is mirrored into.\n *\n * Must match CSRF_HEADER in @spfn/auth. It is duplicated rather than imported\n * because @spfn/core cannot depend on @spfn/auth.\n */\nexport const CSRF_HEADER = 'x-spfn-csrf';\n\n/**\n * Cookie names carrying a CSRF token.\n *\n * @spfn/auth suffixes its cookie names with the server port so several local dev\n * instances on the same host do not overwrite each other's session, and the\n * browser cannot know that port — so match the family, not one name. Every\n * candidate is sent and the proxy accepts the request if any of them recomputes\n * to the expected value; a candidate an attacker tossed in never will.\n */\nconst CSRF_COOKIE_PATTERN = /^spfn_csrf(_\\d+)?$/;\n\n/**\n * Values that can be put in a header at all: printable ASCII, no space.\n *\n * A cookie jar is attacker-writable in the very case this feature exists for, and\n * `new Headers()` rejects a value outside Latin-1 — so a sibling subdomain that\n * tosses `spfn_csrf=한` would make every RPC call throw before it was sent, not\n * merely fail the check. A genuine token is 64 hex characters; anything that\n * cannot be a header value cannot be one.\n */\nconst HEADER_SAFE_VALUE = /^[!-~]+$/;\n\n/**\n * Upper bound on candidates sent, so a cookie-flooded jar stays a bounded header.\n *\n * Deliberately generous. The server recomputes the expected value once and then\n * only compares fixed-length strings, so extra candidates cost it almost nothing,\n * while every candidate dropped here is a chance to lock the user out of every\n * mutation — the far worse failure. Must not exceed the matching bound in\n * @spfn/auth's `matchesCsrfToken`, or values sent would never be looked at.\n */\nconst MAX_CANDIDATES = 32;\n\n/**\n * Choose which candidates to send when the jar holds more than the cap.\n *\n * `document.cookie` exposes neither Domain nor Path, so a tossed cookie is\n * indistinguishable from the genuine one here — the proxy is what tells them\n * apart, by recomputing. What this can do is refuse to drop the genuine value on\n * the floor. Browsers order the jar by path length descending, then by age\n * ascending, so a flood pushes the genuine cookie towards the end (attacker\n * cookies on a longer path) or towards the start (attacker cookies written later\n * on the same path). Keeping both ends survives either, where a plain prefix\n * survives only one.\n *\n * A jar flooded far past the cap is a denial of service that no client-side\n * choice fully prevents; the structural fix is a `__Host-` prefixed cookie name,\n * which a sibling subdomain cannot write at all.\n */\nfunction selectCandidates(values: string[]): string[]\n{\n // Identical duplicates carry no extra information, so collapsing them buys\n // back slots that a flood of one repeated value would otherwise burn.\n const unique = Array.from(new Set(values));\n\n if (unique.length <= MAX_CANDIDATES)\n {\n return unique;\n }\n\n const half = MAX_CANDIDATES / 2;\n\n return [...unique.slice(0, half), ...unique.slice(-half)];\n}\n\n/**\n * Collect the CSRF token candidates from cookie entries.\n *\n * Takes entries rather than a map because a name can legitimately appear twice:\n * a sibling subdomain that tosses `spfn_csrf` for the parent domain leaves the\n * browser holding two cookies of that name, and folding them into a map would\n * throw away whichever the tossed one displaced — locking the user out of every\n * mutation. Sending both keeps the real one in play, and the tossed one is inert\n * because the proxy recomputes what it expects.\n *\n * @returns Header value (comma-separated), or undefined when no cookie exists\n */\nexport function csrfHeaderValue(cookies: Iterable<[string, string]>): string | undefined\n{\n // A comma separates candidates on the wire, so a value containing one would\n // arrive as several. That cannot be a genuine token — they are hex — but it\n // would let a single tossed cookie spend the receiver's whole candidate\n // budget and bury the real value behind it. Drop those, and anything that\n // could not be a header value at all.\n const values = Array.from(cookies)\n .filter(([name, value]) => CSRF_COOKIE_PATTERN.test(name)\n && HEADER_SAFE_VALUE.test(value)\n && !value.includes(','))\n .map(([, value]) => value);\n\n const candidates = selectCandidates(values);\n\n return candidates.length > 0 ? candidates.join(',') : undefined;\n}\n\n/**\n * Read the browser cookie jar as entries, duplicates preserved.\n *\n * Empty outside a browser (SSR reads the jar it forwards instead).\n */\nexport function documentCookieEntries(): Array<[string, string]>\n{\n if (typeof document === 'undefined' || !document.cookie)\n {\n return [];\n }\n\n const entries: Array<[string, string]> = [];\n\n for (const pair of document.cookie.split(';'))\n {\n const separator = pair.indexOf('=');\n\n if (separator > 0)\n {\n entries.push([pair.slice(0, separator).trim(), pair.slice(separator + 1).trim()]);\n }\n }\n\n return entries;\n}\n","// ============================================================================\n// Route Call Builder (Structured Input API)\n// ============================================================================\n\nimport type { RouteDef, Router } from '@spfn/core/route';\nimport type {\n CallOptions,\n ExtractRoutes,\n InferRouteInput,\n InferRouteOutput,\n RequestInterceptor,\n ResponseInterceptor,\n} from './types';\n\n/**\n * Pick only non-empty fields from StructuredInput\n *\n * This removes fields that are empty objects `{}` from the input type,\n * so users only need to provide fields that are actually defined in the route.\n */\ntype PickNonEmpty<T> = {\n [K in keyof T as T[K] extends Record<string, never> ? never : K]: T[K];\n};\n\n/**\n * Make fields that can be undefined into optional fields\n *\n * When a field is defined as `Type.Optional(Type.Object({...}))`,\n * the resulting type is `T | undefined`. This utility converts such fields\n * into proper optional fields (`field?: T`) so users don't need to pass them.\n */\ntype MakeOptionalIfUndefinable<T> =\n // Required fields (undefined is not assignable)\n { [K in keyof T as undefined extends T[K] ? never : K]: T[K] }\n // Optional fields (undefined is assignable)\n & { [K in keyof T as undefined extends T[K] ? K : never]?: Exclude<T[K], undefined> };\n\n/**\n * Clean structured input - only include fields that have actual schema,\n * and make fields optional if they accept undefined\n */\ntype CleanStructuredInput<TInput> = MakeOptionalIfUndefinable<PickNonEmpty<TInput>>;\n\n/**\n * Check if input has any required fields\n *\n * Returns false if all fields are optional (i.e., {} is assignable to the input type)\n */\ntype HasAnyRequiredFields<TInput> = {} extends CleanStructuredInput<TInput> ? false : true;\n\n/**\n * Largest delay `setTimeout` honours; a longer one fires immediately\n */\nconst MAX_TIMEOUT_MS = 2147483647;\n\n/**\n * Route call builder with structured input API\n *\n * Input is structured with explicit params, query, body fields\n * that match the server-side route definition.\n *\n * @example\n * ```typescript\n * // GET /users/:id - params only\n * const user = await api.getUser.call({ params: { id: '1' } });\n *\n * // GET /users/:id?include=posts - params + query\n * const user = await api.getUser.call({\n * params: { id: '1' },\n * query: { include: 'posts' }\n * });\n *\n * // POST /users - body only\n * const user = await api.createUser.call({\n * body: { name: 'John', email: 'john@example.com' }\n * });\n *\n * // PUT /users/:id - params + body\n * const user = await api.updateUser.call({\n * params: { id: '1' },\n * body: { name: 'Jane' }\n * });\n *\n * // With options (headers, cookies, Next.js caching)\n * const user = await api.getUser\n * .headers({ 'X-Custom': 'value' })\n * .fetchOptions({ next: { revalidate: 60 } })\n * .call({ params: { id: '1' } });\n * ```\n */\nexport class RouteCallBuilder<\n TInput,\n TOutput,\n>\n{\n private _headers?: Record<string, string>;\n private _cookies?: Record<string, string>;\n private _fetchOptions?: RequestInit;\n private _timeout?: number;\n private _onRequest?: RequestInterceptor;\n private _onResponse?: ResponseInterceptor;\n\n constructor(\n private readonly executor: (input: any, options: CallOptions) => Promise<TOutput>,\n private readonly routeName: string,\n ) \n {}\n\n /**\n * Clone builder\n */\n private clone(): RouteCallBuilder<TInput, TOutput>\n {\n const builder = new RouteCallBuilder<TInput, TOutput>(\n this.executor,\n this.routeName,\n );\n builder._headers = this._headers;\n builder._cookies = this._cookies;\n builder._fetchOptions = this._fetchOptions;\n builder._timeout = this._timeout;\n builder._onRequest = this._onRequest;\n builder._onResponse = this._onResponse;\n\n return builder;\n }\n\n /**\n * Set request headers\n */\n headers(headers: Record<string, string>): RouteCallBuilder<TInput, TOutput>\n {\n const builder = this.clone();\n builder._headers = { ...this._headers, ...headers };\n\n return builder;\n }\n\n /**\n * Set cookies\n */\n cookies(cookies: Record<string, string>): RouteCallBuilder<TInput, TOutput>\n {\n const builder = this.clone();\n builder._cookies = { ...this._cookies, ...cookies };\n\n return builder;\n }\n\n /**\n * Set Next.js fetch options\n */\n fetchOptions(options: RequestInit & { next?: { revalidate?: number | false; tags?: string[] } }): RouteCallBuilder<TInput, TOutput>\n {\n const builder = this.clone();\n builder._fetchOptions = { ...this._fetchOptions, ...options };\n\n return builder;\n }\n\n /**\n * Set this call's timeout in milliseconds, overriding the client-wide one\n *\n * Throws a `TypeError`, before any request is made, unless `ms` is a finite number\n * greater than 0 and at most 2147483647 (the largest delay `setTimeout` honours).\n */\n timeout(ms: number): RouteCallBuilder<TInput, TOutput>\n {\n if (!Number.isFinite(ms) || ms <= 0 || ms > MAX_TIMEOUT_MS)\n {\n throw new TypeError(`timeout must be a finite number of milliseconds in (0, ${MAX_TIMEOUT_MS}], got ${String(ms)}`);\n }\n\n const builder = this.clone();\n builder._timeout = ms;\n\n return builder;\n }\n\n /**\n * Set request interceptor\n */\n onRequest(interceptor: RequestInterceptor): RouteCallBuilder<TInput, TOutput>\n {\n const builder = this.clone();\n builder._onRequest = interceptor;\n\n return builder;\n }\n\n /**\n * Set response interceptor\n */\n onResponse(interceptor: ResponseInterceptor): RouteCallBuilder<TInput, TOutput>\n {\n const builder = this.clone();\n builder._onResponse = interceptor;\n\n return builder;\n }\n\n /**\n * Execute the API call with structured input\n *\n * Input structure matches the server-side route definition:\n * - params: Path parameters (e.g., { id: '123' } for /users/:id)\n * - query: Query string parameters\n * - body: Request body (for POST, PUT, PATCH)\n */\n call(input?: CleanStructuredInput<TInput>): Promise<TOutput>\n {\n const options: CallOptions = {};\n\n if (this._headers)\n {\n options.headers = this._headers;\n }\n\n if (this._cookies)\n {\n options.cookies = this._cookies;\n }\n\n if (this._fetchOptions)\n {\n options.fetchOptions = this._fetchOptions;\n }\n\n if (this._timeout !== undefined)\n {\n options.timeout = this._timeout;\n }\n\n if (this._onRequest)\n {\n options.onRequest = this._onRequest;\n }\n\n if (this._onResponse)\n {\n options.onResponse = this._onResponse;\n }\n\n return this.executor(input || {}, options);\n }\n}\n\n/**\n * Individual route client with structured input API\n */\nexport type RouteClient<TRoute extends RouteDef<any, any>> = {\n /**\n * Set request headers\n */\n headers(headers: Record<string, string>): RouteClient<TRoute>;\n\n /**\n * Set cookies\n */\n cookies(cookies: Record<string, string>): RouteClient<TRoute>;\n\n /**\n * Set Next.js fetch options\n */\n fetchOptions(options: RequestInit & { next?: { revalidate?: number | false; tags?: string[] } }): RouteClient<TRoute>;\n\n /**\n * Set this call's timeout in milliseconds, overriding the client-wide one.\n * Throws a `TypeError` unless `ms` is finite, `> 0` and `<= 2147483647`.\n */\n timeout(ms: number): RouteClient<TRoute>;\n\n /**\n * Set request interceptor\n */\n onRequest(interceptor: RequestInterceptor): RouteClient<TRoute>;\n\n /**\n * Set response interceptor\n */\n onResponse(interceptor: ResponseInterceptor): RouteClient<TRoute>;\n\n /**\n * Execute the API call with structured input\n *\n * @example\n * ```typescript\n * // GET /users/:id\n * api.getUser.call({ params: { id: '123' } });\n *\n * // PUT /users/:id\n * api.updateUser.call({ params: { id: '123' }, body: { name: 'Jane' } });\n * ```\n */\n call: HasAnyRequiredFields<InferRouteInput<TRoute>> extends true\n ? (input: CleanStructuredInput<InferRouteInput<TRoute>>) => Promise<InferRouteOutput<TRoute>>\n : (input?: CleanStructuredInput<InferRouteInput<TRoute>>) => Promise<InferRouteOutput<TRoute>>;\n};\n\n/**\n * One property per route in a flattened route record.\n *\n * A slot the flattening could not resolve to a route definition is `never`, as it was\n * before nesting was flattened — a record whose values are neither routes nor routers\n * names nothing callable.\n */\ntype RouteClients<TRoutes> = {\n [K in keyof TRoutes]: TRoutes[K] extends RouteDef<any, any, any>\n ? RouteClient<TRoutes[K]>\n : never;\n};\n\n/**\n * Typed client for entire router\n *\n * One flat namespace, holding the same route names the server registers. A route\n * declared inside a nested `defineRouter` is reached by its own key — `api.getThing`,\n * never `api.group.getThing` — because that is the name `registerRoutes` mounts it\n * under, the name the RPC proxy resolves, and the name `RouterOutput` takes. The group\n * key is not a route and naming it is a compile error.\n *\n * @example\n * ```typescript\n * const appRouter = defineRouter({\n * getRoot,\n * users: defineRouter({ getUser }),\n * });\n *\n * const api = createApi<typeof appRouter>();\n *\n * await api.getUser.call({ params: { id: '1' } }); // not api.users.getUser\n * ```\n *\n * A name two branches both declare is left out of the namespace rather than resolved to\n * one of them — the same collision `RouterOutput` refuses. Routes mounted with\n * `.packages()` are deliberately absent: a package publishes its own route map and its\n * own client (`authApi`, `cmsApi`), and that is what calls them.\n */\nexport type Client<TRouter extends Router<any>> = RouteClients<ExtractRoutes<TRouter>>;\n","/**\n * Type-Safe RPC-Style Client with Structured Input API\n *\n * Provides full end-to-end type safety from server routes to client calls.\n * No metadata codegen required - method/path resolution happens at the proxy layer.\n *\n * @example\n * ```typescript\n * // Server\n * export const appRouter = defineRouter({\n * getUser: route.get('/users/:id')\n * .input({ params: Type.Object({ id: Type.String() }) })\n * .handler(async (c) => { ... }),\n * createUser: route.post('/users')\n * .input({ body: Type.Object({ name: Type.String() }) })\n * .handler(async (c) => { ... }),\n * });\n *\n * export type AppRouter = typeof appRouter;\n *\n * // Client - no metadata needed!\n * const api = createApi<AppRouter>();\n *\n * // ✅ GET (no body) - becomes GET /api/rpc/getUser?input={...}\n * const user = await api.getUser.call({ params: { id: '1' } });\n *\n * // ✅ POST (has body) - becomes POST /api/rpc/createUser\n * const newUser = await api.createUser.call({ body: { name: 'John' } });\n *\n * // ✅ With options (headers, cookies, interceptors)\n * const user = await api.getUser\n * .headers({ 'X-Custom': 'value' })\n * .cookies({ session: 'xxx' })\n * .fetchOptions({ next: { revalidate: 60 } })\n * .call({ params: { id: '1' } });\n * ```\n */\nimport { env } from '@spfn/core/config';\nimport { ErrorRegistry, errorRegistry as coreErrorRegistry } from '@spfn/core/errors';\nimport { logger } from '@spfn/core/logger';\nimport type { Router } from '@spfn/core/route';\nimport * as debugLogs from './debug-logs';\nimport { ApiError } from './errors';\nimport {\n fetchInAbortScope,\n handleErrorResponse,\n buildCookieHeader,\n autoDetectServerCookies,\n} from './helpers';\nimport { CSRF_HEADER, csrfHeaderValue, documentCookieEntries } from './csrf';\nimport { RouteCallBuilder } from './builder';\nimport type { ScopedFetchResult } from './helpers';\nimport type { ApiConfig, CallOptions } from './types';\nimport type { Client } from './builder';\n\nconst apiLogger = logger.child('@spfn/core:api-client');\n\n/**\n * Where a failed call went, for the log line its transport error writes\n */\ninterface CallSite\n{\n route: string;\n method: string;\n url: string;\n}\n\n/**\n * Classify a failed fetch by the abort cause its scope recorded\n *\n * - timeout → 408 `'timeout'`, reporting the applied timeout\n * - caller abort → status 0 `'aborted'`, the signal's reason as `cause`; logged at debug\n * level, since it is the caller's own intent\n * - no cause recorded but the error is a plain `AbortError` (a custom `fetch` aborting its\n * own controller, with neither our timer nor the caller's signal involved) → 408\n * `'timeout'`, as it always was before the scope recorded a cause\n * - anything else → status 0 `'network'`\n */\nfunction transportError(\n failure: Extract<ScopedFetchResult, { ok: false }>,\n site: CallSite,\n timeout: number,\n): ApiError\n{\n if (failure.cause === 'timeout')\n {\n apiLogger.error('Request timeout', { ...site, timeout });\n\n return new ApiError(`Request timeout after ${timeout}ms`, 408, site.url, undefined, 'timeout');\n }\n\n if (failure.cause === 'caller')\n {\n apiLogger.debug('Request aborted by caller', site);\n\n return new ApiError('Request aborted', 0, site.url, undefined, 'aborted', { cause: failure.error });\n }\n\n const { error } = failure;\n\n if (error instanceof Error && error.name === 'AbortError')\n {\n apiLogger.error('Request timeout', { ...site, timeout });\n\n return new ApiError(`Request timeout after ${timeout}ms`, 408, site.url, undefined, 'timeout');\n }\n\n const errorMessage = error instanceof Error ? error.message : 'Network error';\n apiLogger.error('Network error', {\n ...site,\n error: errorMessage,\n errorName: error instanceof Error ? error.name : 'unknown',\n });\n\n return new ApiError(errorMessage, 0, site.url, undefined, 'network');\n}\n\n// ============================================================================\n// Client Implementation\n// ============================================================================\n\n/**\n * Create type-safe RPC client\n *\n * No metadata required - the client sends routeName + input to the proxy,\n * and the proxy resolves the actual HTTP method and path from the router.\n *\n * @example\n * ```typescript\n * // Client - no metadata needed!\n * const api = createApi<AppRouter>();\n *\n * // GET request (no body) - browser cacheable\n * const user = await api.getUser.call({ params: { id: '1' } });\n *\n * // POST request (has body)\n * const newUser = await api.createUser.call({ body: { name: 'John' } });\n * ```\n */\nexport function createApi<TRouter extends Router<any>>(\n config: ApiConfig = {},\n): Client<TRouter>\n{\n const {\n baseUrl = '/api/rpc',\n headers: defaultHeaders = {},\n timeout = env.SERVER_TIMEOUT,\n fetch: customFetch = fetch,\n onRequest: globalOnRequest,\n onResponse: globalOnResponse,\n errorRegistry: errorRegistryConfig,\n debug = false,\n } = config;\n\n // Normalize errorRegistry: always include coreErrorRegistry\n const errorRegistry = Array.isArray(errorRegistryConfig)\n ? new ErrorRegistry([coreErrorRegistry, ...errorRegistryConfig])\n : errorRegistryConfig ?? coreErrorRegistry;\n\n if (debug)\n {\n apiLogger.debug('API client initialized', { baseUrl });\n }\n\n /**\n * Execute API call\n *\n * Determines GET vs POST based on body/formData presence:\n * - No body/formData → GET /api/rpc/{routeName}?input={...}\n * - Has body → POST /api/rpc/{routeName} with JSON body\n * - Has formData → POST /api/rpc/{routeName} with multipart/form-data\n */\n async function executeCall(\n routeName: string,\n input: any = {},\n options: CallOptions = {},\n ): Promise<any>\n {\n const hasBody = input.body !== undefined;\n const hasFormData = input.formData !== undefined && Object.keys(input.formData).length > 0;\n const method = (hasBody || hasFormData) ? 'POST' : 'GET';\n\n // Build full URL - handle SSR case where SPFN_APP_URL might not be set\n let appUrl = env.SPFN_APP_URL || '';\n\n // In SSR environment, if SPFN_APP_URL is not set, try to get host from request headers\n if (!appUrl && typeof window === 'undefined')\n {\n try\n {\n const { headers } = await import('next/headers');\n const headersList = await headers();\n const host = headersList.get('host');\n const protocol = headersList.get('x-forwarded-proto') || 'http';\n if (host)\n {\n appUrl = `${protocol}://${host}`;\n if (debug)\n {\n apiLogger.debug(`Auto-detected app URL from headers: ${appUrl}`);\n }\n }\n }\n catch\n {\n // Fallback: use relative URL and let fetch handle it\n if (debug)\n {\n apiLogger.warn('Could not determine app URL in SSR environment, using relative URL');\n }\n }\n }\n\n // Build URL based on method\n let fullUrl: string;\n if (method === 'GET')\n {\n // GET: encode input in query string\n const inputParam = encodeURIComponent(JSON.stringify(input));\n fullUrl = `${appUrl}${baseUrl}/${routeName}?input=${inputParam}`;\n }\n else\n {\n // POST: input goes in body\n fullUrl = `${appUrl}${baseUrl}/${routeName}`;\n }\n\n // Prepare headers\n // Note: Don't set Content-Type for formData - browser sets it with boundary\n const headers: Record<string, string> = {\n ...(hasFormData ? {} : { 'Content-Type': 'application/json' }),\n ...defaultHeaders,\n ...options.headers,\n };\n\n // Auto-detect server cookies and merge with user-provided cookies\n const autoDetectedCookies = await autoDetectServerCookies();\n const cookiesToSend = {\n ...autoDetectedCookies,\n ...(options.cookies || {}),\n };\n\n // Add Cookie header if we have cookies to send\n if (Object.keys(cookiesToSend).length > 0)\n {\n headers['Cookie'] = buildCookieHeader(cookiesToSend);\n }\n\n // Mirror the readable CSRF cookie into a header, from the browser jar or\n // — server-side — from the jar being forwarded.\n //\n // Carried on GET calls too, deliberately: the wire method here is chosen\n // by whether the input has a body, not by what the route does. A bodyless\n // mutation (`api.logout.call({})`) travels as GET and the proxy still\n // forwards it as the route's POST, so a GET-shaped call can be exactly\n // the request that needs the header. The proxy decides what to check from\n // the resolved route method, and ignores the header on safe routes.\n const csrfToken = csrfHeaderValue(\n typeof window === 'undefined' ? Object.entries(cookiesToSend) : documentCookieEntries(),\n );\n\n const csrfSetByCaller = Object.keys(headers).some(name => name.toLowerCase() === CSRF_HEADER);\n\n if (csrfToken && !csrfSetByCaller)\n {\n headers[CSRF_HEADER] = csrfToken;\n }\n\n // Log cookie auto-detection if debug enabled\n if (debug && Object.keys(autoDetectedCookies).length > 0)\n {\n const cookieArray = Object.entries(autoDetectedCookies).map(([name, value]) => ({ name, value }));\n debugLogs.logCookieAutoDetection(apiLogger, cookieArray);\n }\n\n // Build request init\n const requestInit: RequestInit = {\n method,\n headers,\n ...options.fetchOptions,\n };\n\n // Add body for POST\n if (method === 'POST')\n {\n if (hasFormData)\n {\n // Build FormData for file uploads\n const formData = new FormData();\n\n // Add non-formData fields (params, query, headers, cookies) as JSON metadata\n const metadata: Record<string, any> = {};\n if (input.params) metadata.params = input.params;\n if (input.query) metadata.query = input.query;\n if (input.headers) metadata.headers = input.headers;\n if (input.cookies) metadata.cookies = input.cookies;\n\n if (Object.keys(metadata).length > 0)\n {\n formData.append('__metadata', JSON.stringify(metadata));\n }\n\n // Add formData fields\n for (const [key, value] of Object.entries(input.formData))\n {\n if (value instanceof File)\n {\n formData.append(key, value);\n }\n else if (Array.isArray(value))\n {\n // Handle array of files or values\n for (const item of value)\n {\n if (item instanceof File)\n {\n formData.append(key, item);\n }\n else\n {\n formData.append(key, String(item));\n }\n }\n }\n else if (value !== undefined && value !== null)\n {\n formData.append(key, String(value));\n }\n }\n\n requestInit.body = formData;\n }\n else\n {\n requestInit.body = JSON.stringify(input);\n }\n }\n\n // Execute request interceptors\n let init = requestInit;\n if (globalOnRequest)\n {\n init = await globalOnRequest(fullUrl, init);\n }\n if (options.onRequest)\n {\n init = await options.onRequest(fullUrl, init);\n }\n\n if (debug)\n {\n debugLogs.logRequest(apiLogger, routeName, method, fullUrl, !!init.body);\n }\n\n // Execute fetch and parse the body inside one abort scope\n const appliedTimeout = options.timeout ?? timeout;\n const fetched = await fetchInAbortScope(fullUrl, init, appliedTimeout, customFetch);\n\n if (!fetched.ok)\n {\n throw transportError(fetched, { route: routeName, method, url: fullUrl }, appliedTimeout);\n }\n\n let { response, body } = fetched;\n\n // Execute global + local response interceptors\n //\n // These run outside the abort scope on purpose. That scope classifies a transport\n // failure, and an interceptor throwing is not one: Next.js implements `redirect()`\n // and `notFound()` by throwing an error carrying a digest, and an interceptor\n // raising a domain error of its own is not a transport failure either. Run inside\n // it, every one of those became `ApiError(..., 0, ..., 'network')` — the\n // navigation never happened and the caller was handed a network failure that never\n // occurred, which retry and offline handling keyed on `errorType === 'network'`\n // act on.\n if (globalOnResponse)\n {\n const result = await globalOnResponse(response, body);\n response = result.response;\n body = result.body;\n }\n if (options.onResponse)\n {\n const result = await options.onResponse(response, body);\n response = result.response;\n body = result.body;\n }\n\n if (debug)\n {\n debugLogs.logResponse(apiLogger, routeName, response.status, !!body);\n }\n\n // Handle error responses\n if (!response.ok)\n {\n await handleErrorResponse(response, body, fullUrl, errorRegistry, debug, apiLogger);\n }\n\n return body;\n }\n\n /**\n * Build client proxy\n *\n * Every property access returns a `RouteCallBuilder` for that one property read as a\n * whole route name, and that name goes on the wire as `/api/rpc/{routeName}`. The\n * proxy route resolves it against the generated route map, whose names are flat.\n *\n * Nesting needs nothing here: `registerRoutes` mounts a route declared inside a\n * nested `defineRouter` under its own key, so `api.getThing` is the call for a nested\n * `getThing` exactly as for a top-level one — which is the namespace `Client` types.\n * There is no dot-notation traversal: a property access never returns another proxy,\n * so `api.group.getThing` reads a property of a builder and is `undefined`.\n *\n * `prefix` is vestigial — nothing passes one, and adding a caller that did would\n * build a dotted name the route map cannot resolve.\n */\n function buildProxy(prefix = ''): any\n {\n return new Proxy(\n {},\n {\n get(_target, prop: string)\n {\n const currentPath = prefix ? `${prefix}.${prop}` : prop;\n\n // Return RouteCallBuilder that can either be called or chained\n return new RouteCallBuilder(\n (input: any, options: CallOptions) => executeCall(currentPath, input, options),\n currentPath,\n );\n },\n },\n );\n }\n\n return buildProxy() as Client<TRouter>;\n}\n"]}
|
package/dist/nextjs/server.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
2
|
import { HttpMethod } from '@spfn/core/route';
|
|
3
3
|
import { InterceptorRule as InterceptorRule$1 } from '@spfn/core/nextjs/server';
|
|
4
|
-
import { S as SetCookie } from '../types-
|
|
4
|
+
import { S as SetCookie } from '../types-CxhPH4s_.js';
|
|
5
5
|
import '@sinclair/typebox';
|
|
6
6
|
import '@spfn/core/errors';
|
|
7
7
|
|
|
@@ -56,7 +56,7 @@ interface EnvVarSchema<T = string> {
|
|
|
56
56
|
*/
|
|
57
57
|
nextjs?: boolean;
|
|
58
58
|
/**
|
|
59
|
-
* Which layer of a deployment
|
|
59
|
+
* Which layer of a deployment must supply the value.
|
|
60
60
|
*
|
|
61
61
|
* - `'environment'`: one value shared by every instance of an environment
|
|
62
62
|
* (a third-party API key) — kept in `secrets/<env>.enc.json`.
|
|
@@ -64,9 +64,10 @@ interface EnvVarSchema<T = string> {
|
|
|
64
64
|
* `secrets/<env>.<instance>.enc.json` or computed by the deploy script
|
|
65
65
|
* and passed to `spfn secret export --with <file>`.
|
|
66
66
|
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
67
|
+
* Unset: the value may come from any layer. Set it to pin where a value
|
|
68
|
+
* must live — `spfn secret export` then fails when the value arrives from
|
|
69
|
+
* another layer. `--all` on `spfn secret set`/`generate`/`rotate` gives an
|
|
70
|
+
* unset entry to the environment target only.
|
|
70
71
|
*/
|
|
71
72
|
layer?: EnvLayer;
|
|
72
73
|
/**
|
|
@@ -119,8 +120,8 @@ type InferEnvType<T extends EnvSchemaCollection> = {
|
|
|
119
120
|
* 스키마 정의 헬퍼 (타입 추론 지원)
|
|
120
121
|
*
|
|
121
122
|
* Automatically fills in the `key` property from object keys. Every other
|
|
122
|
-
* field passes through as written — an entry without `layer`
|
|
123
|
-
*
|
|
123
|
+
* field passes through as written — an entry without `layer` may come from
|
|
124
|
+
* any deployment layer.
|
|
124
125
|
*
|
|
125
126
|
* @example
|
|
126
127
|
* ```typescript
|
package/dist/server/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { DatabaseProvider, MigrationStatus, MigrationStatusDb } from '@spfn/core
|
|
|
13
13
|
import * as _sinclair_typebox from '@sinclair/typebox';
|
|
14
14
|
import { Static } from '@sinclair/typebox';
|
|
15
15
|
import { R as RouteDef } from '../route-builder-2ani2jEI.js';
|
|
16
|
-
import { a as EnvRegistry } from '../registry-
|
|
16
|
+
import { a as EnvRegistry } from '../registry-C2KQCh0m.js';
|
|
17
17
|
import 'pg-boss';
|
|
18
18
|
import '../define-middleware-CVKgqo8S.js';
|
|
19
19
|
import 'hono/utils/http-status';
|
|
@@ -328,8 +328,8 @@ interface ApiConfig {
|
|
|
328
328
|
*/
|
|
329
329
|
interface CallOptions {
|
|
330
330
|
/**
|
|
331
|
-
* Request timeout in milliseconds
|
|
332
|
-
* Overrides the global timeout set in ApiConfig
|
|
331
|
+
* Request timeout in milliseconds, covering the wait for response headers
|
|
332
|
+
* Overrides the global timeout set in ApiConfig; set it with `.timeout(ms)`
|
|
333
333
|
*/
|
|
334
334
|
timeout?: number;
|
|
335
335
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spfn/core",
|
|
3
|
-
"version": "0.3.0-beta.
|
|
3
|
+
"version": "0.3.0-beta.18",
|
|
4
4
|
"description": "Full-stack TypeScript backend for Next.js: file-based typed routes, Drizzle entities and repositories, PostgreSQL transactions and a generated end-to-end client, in one fixed vertical slice per feature",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -209,7 +209,7 @@
|
|
|
209
209
|
"pg-boss": "^11.1.2",
|
|
210
210
|
"postgres": "^3.4.0",
|
|
211
211
|
"typescript": "^5.3.3",
|
|
212
|
-
"undici": "^7.29.
|
|
212
|
+
"undici": "^7.29.1",
|
|
213
213
|
"zod": "^4.4.3"
|
|
214
214
|
},
|
|
215
215
|
"optionalDependencies": {
|
package/src/env/README.md
CHANGED
|
@@ -160,7 +160,7 @@ CONFIG: envJson<{ host: string; port: number }>({
|
|
|
160
160
|
| `sensitive` | `boolean` | Marks a secret; triggers a warning if `NEXT_PUBLIC_*` |
|
|
161
161
|
| `examples` | `T[]` | Example values (metadata only) |
|
|
162
162
|
| `nextjs` | `boolean` | File-separation hint (see below). Defaults to `true` for `NEXT_PUBLIC_*`, else `false` |
|
|
163
|
-
| `layer` | `'environment' \| 'instance'` | Which deployment layer
|
|
163
|
+
| `layer` | `'environment' \| 'instance'` | Which deployment layer must supply the value (see below). Unset: may come from any layer |
|
|
164
164
|
| `readBy` | `string[]` | Files that read the name under a computed key, relative to the project root — for `spfn env audit` (see below) |
|
|
165
165
|
|
|
166
166
|
> There is **no** `category` option. Docs that show `category: '...'` are stale — passing
|
|
@@ -360,24 +360,26 @@ cannot tell a message from a lookup key.
|
|
|
360
360
|
### Deployment layers (`layer`)
|
|
361
361
|
|
|
362
362
|
A deployed environment can run more than one instance — `staging` as `blue` and `green`,
|
|
363
|
-
each with its own port, URLs and database. `layer`
|
|
363
|
+
each with its own port, URLs and database. `layer` pins where a variable's value must come
|
|
364
364
|
from, so `spfn secret export` can assemble one instance's env file and refuse a value that
|
|
365
|
-
arrives from the wrong place
|
|
365
|
+
arrives from the wrong place. Unset, the value may come from any layer — a package schema
|
|
366
|
+
leaves it unset, so an app can keep a value such as a session secret shared or give each
|
|
367
|
+
instance its own:
|
|
366
368
|
|
|
367
369
|
| Layer | Holds | Source |
|
|
368
370
|
|-------|-------|--------|
|
|
369
|
-
| `environment`
|
|
371
|
+
| `environment` | Values every instance of the environment shares — third-party API keys | `secrets/<env>.enc.json` |
|
|
370
372
|
| `instance` | Values of one instance: its own secrets, or plain values the deploy script computes (port, URLs, database name) | `secrets/<env>.<instance>.enc.json`, or a `--with <file>` dotenv file |
|
|
371
373
|
|
|
372
374
|
```typescript
|
|
373
375
|
const schema = defineEnvSchema({
|
|
374
|
-
PAYMENT_API_KEY: envSecret({ description: 'Payment provider key', required: true }),
|
|
376
|
+
PAYMENT_API_KEY: envSecret({ description: 'Payment provider key', required: true, layer: 'environment' }),
|
|
375
377
|
DB_NAME: envString({ description: 'Database of this instance', required: true, layer: 'instance' }),
|
|
376
378
|
});
|
|
377
379
|
```
|
|
378
380
|
|
|
379
|
-
|
|
380
|
-
environment file, or
|
|
381
|
+
A name declared `environment` found in an instance layer, a name declared `instance` found
|
|
382
|
+
in the environment file, or any name found in two layers fails the export. The runtime ignores
|
|
381
383
|
`layer`; it only steers the CLI.
|
|
382
384
|
|
|
383
385
|
### Fallback keys
|
package/src/nextjs/README.md
CHANGED
|
@@ -194,18 +194,42 @@ const user = await api.getUser
|
|
|
194
194
|
.headers({ 'X-Custom': 'value' })
|
|
195
195
|
.cookies({ session: 'xxx' })
|
|
196
196
|
.fetchOptions({ next: { revalidate: 60 } })
|
|
197
|
+
.timeout(5000) // this call only; overrides ApiConfig.timeout
|
|
197
198
|
.onRequest((url, init) => init) // client RequestInterceptor
|
|
198
199
|
.onResponse((res, body) => ({ response: res, body })) // client ResponseInterceptor
|
|
199
200
|
.call({ params: { id: '123' } });
|
|
200
201
|
```
|
|
201
202
|
|
|
203
|
+
`.timeout(ms)` throws a `TypeError` — before any request is made — unless `ms` is a finite
|
|
204
|
+
number greater than 0 and at most 2147483647 (`setTimeout`'s ceiling). The timer covers the
|
|
205
|
+
wait for response headers, not the body read: once headers arrive it stops, however long the
|
|
206
|
+
body takes.
|
|
207
|
+
|
|
208
|
+
#### Cancellation
|
|
209
|
+
|
|
210
|
+
Pass your own `AbortSignal` through `.fetchOptions({ signal })` (or set `init.signal` in an
|
|
211
|
+
`onRequest` interceptor — the signal on the final `init` is the one honoured). Aborting it
|
|
212
|
+
rejects the call with an `ApiError` of type `'aborted'`, from before the request is sent until
|
|
213
|
+
the response body has been read; the timeout keeps running alongside it.
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
const controller = new AbortController();
|
|
217
|
+
|
|
218
|
+
const pending = api.searchUsers
|
|
219
|
+
.timeout(10_000)
|
|
220
|
+
.fetchOptions({ signal: controller.signal })
|
|
221
|
+
.call({ query: { q: 'ada' } });
|
|
222
|
+
|
|
223
|
+
controller.abort('superseded'); // pending rejects: errorType 'aborted', status 0, cause 'superseded'
|
|
224
|
+
```
|
|
225
|
+
|
|
202
226
|
### `ApiConfig`
|
|
203
227
|
|
|
204
228
|
```typescript
|
|
205
229
|
interface ApiConfig {
|
|
206
230
|
baseUrl?: string; // default '/api/rpc'
|
|
207
231
|
headers?: Record<string, string>; // default headers on every request
|
|
208
|
-
timeout?: number; // default env.SERVER_TIMEOUT (120000 ms)
|
|
232
|
+
timeout?: number; // default env.SERVER_TIMEOUT (120000 ms); per call: .timeout(ms)
|
|
209
233
|
fetch?: typeof fetch; // custom fetch impl
|
|
210
234
|
onRequest?: RequestInterceptor; // global, (url, init) => init
|
|
211
235
|
onResponse?: ResponseInterceptor; // global, (response, body) => { response, body }
|
|
@@ -226,7 +250,7 @@ const api = createApi<AppRouter>({ errorRegistry: [errorRegistry, authErrorRegis
|
|
|
226
250
|
|
|
227
251
|
```typescript
|
|
228
252
|
interface CallOptions {
|
|
229
|
-
timeout?: number;
|
|
253
|
+
timeout?: number; // ms until response headers; overrides ApiConfig.timeout
|
|
230
254
|
headers?: Record<string, string>;
|
|
231
255
|
cookies?: Record<string, string>; // override only; cookies are auto-forwarded otherwise
|
|
232
256
|
onRequest?: RequestInterceptor;
|
|
@@ -499,16 +523,17 @@ is named identically whether you call it (`api.listExamples`) or take its type
|
|
|
499
523
|
|
|
500
524
|
## Error handling
|
|
501
525
|
|
|
502
|
-
`ApiError` is thrown for non-2xx responses, network failures, and
|
|
526
|
+
`ApiError` is thrown for non-2xx responses, network failures, timeouts, and caller aborts:
|
|
503
527
|
|
|
504
528
|
```typescript
|
|
505
529
|
class ApiError extends Error {
|
|
506
530
|
constructor(
|
|
507
531
|
message: string,
|
|
508
|
-
public readonly status: number, // HTTP status; 0 for network; 408 for timeout
|
|
532
|
+
public readonly status: number, // HTTP status; 0 for network and aborted; 408 for timeout
|
|
509
533
|
public readonly url: string,
|
|
510
534
|
public readonly response?: unknown, // parsed error body
|
|
511
|
-
public readonly errorType?: 'http' | 'network' | 'timeout',
|
|
535
|
+
public readonly errorType?: 'http' | 'network' | 'timeout' | 'aborted',
|
|
536
|
+
options?: { cause?: unknown }, // 'aborted': cause is the signal's reason
|
|
512
537
|
) {}
|
|
513
538
|
}
|
|
514
539
|
```
|
|
@@ -521,12 +546,18 @@ try {
|
|
|
521
546
|
} catch (error) {
|
|
522
547
|
if (error instanceof ApiError) {
|
|
523
548
|
if (error.errorType === 'timeout') { /* 408 */ }
|
|
549
|
+
else if (error.errorType === 'aborted') { /* status 0; you aborted it — error.cause is the reason */ }
|
|
524
550
|
else if (error.errorType === 'network') { /* status 0 */ }
|
|
525
551
|
else { /* error.status, error.response */ }
|
|
526
552
|
}
|
|
527
553
|
}
|
|
528
554
|
```
|
|
529
555
|
|
|
556
|
+
The client classifies an abort by who triggered it, never by the error's name: `'timeout'`
|
|
557
|
+
when its own timer fired, `'aborted'` when your signal did — whichever came first. An
|
|
558
|
+
`AbortError` that a custom `fetch` throws on its own, with neither of those having fired, is
|
|
559
|
+
`'timeout'` too, as it always was.
|
|
560
|
+
|
|
530
561
|
**Custom errors**: if the backend body carries a `__type` discriminator and a matching entry
|
|
531
562
|
is registered in the client's `errorRegistry`, the client deserializes and throws the
|
|
532
563
|
**original typed error** instead of a generic `ApiError`. Otherwise it falls back to
|
|
@@ -620,12 +651,13 @@ export async function createUser(formData: FormData) {
|
|
|
620
651
|
HMAC-signed requests. Missing visitor IP in production means `SPFN_PROXY_SECRET` isn't set on
|
|
621
652
|
both sides (and/or `TRUSTED_PROXY_HOPS` is wrong), not that the header allowlist needs
|
|
622
653
|
`x-forwarded-for` — adding it back makes the IP spoofable. See *Header forwarding & client IP*.
|
|
623
|
-
- **Each `.headers()/.cookies()/.fetchOptions()/.onRequest()/.onResponse()` returns a new
|
|
654
|
+
- **Each `.headers()/.cookies()/.fetchOptions()/.timeout()/.onRequest()/.onResponse()` returns a new
|
|
624
655
|
builder.** Chain them in one expression; a dangling builder without `.call()` does nothing.
|
|
625
656
|
- **A client `onResponse` may throw, and the throw is the caller's.** It runs outside the
|
|
626
657
|
transport error handling, so `redirect('/login')` on a 401 navigates and a domain error
|
|
627
658
|
raised there is raised as itself — neither is relabelled an `ApiError` of type `'network'`.
|
|
628
|
-
Only a failed fetch (`0` / `'network'`)
|
|
659
|
+
Only a failed fetch (`0` / `'network'`), a timed-out one (`408` / `'timeout'`) and one the
|
|
660
|
+
caller aborted (`0` / `'aborted'`) are.
|
|
629
661
|
|
|
630
662
|
---
|
|
631
663
|
|