@spfn/core 0.2.0-beta.53 → 0.2.0-beta.55
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/{boss-Cxqc-Oiw.d.ts → boss-CEik0yq-.d.ts} +7 -0
- package/dist/cache/index.js +10 -1
- package/dist/cache/index.js.map +1 -1
- package/dist/config/index.d.ts +213 -0
- package/dist/config/index.js +43 -1
- package/dist/config/index.js.map +1 -1
- package/dist/db/index.d.ts +57 -0
- package/dist/db/index.js +55 -8
- package/dist/db/index.js.map +1 -1
- package/dist/define-middleware-DuXD8Hvu.d.ts +167 -0
- package/dist/env/index.js +4 -3
- package/dist/env/index.js.map +1 -1
- package/dist/errors/index.d.ts +10 -0
- package/dist/errors/index.js +20 -2
- package/dist/errors/index.js.map +1 -1
- package/dist/event/index.d.ts +3 -3
- package/dist/event/index.js.map +1 -1
- package/dist/event/sse/client.d.ts +2 -2
- package/dist/event/sse/index.d.ts +4 -4
- package/dist/event/sse/index.js +41 -16
- package/dist/event/sse/index.js.map +1 -1
- package/dist/event/ws/client.d.ts +2 -2
- package/dist/event/ws/index.d.ts +3 -3
- package/dist/event/ws/index.js +75 -16
- package/dist/event/ws/index.js.map +1 -1
- package/dist/job/index.d.ts +2 -2
- package/dist/job/index.js +4 -4
- package/dist/job/index.js.map +1 -1
- package/dist/middleware/index.d.ts +64 -2
- package/dist/middleware/index.js +1030 -96
- package/dist/middleware/index.js.map +1 -1
- package/dist/nextjs/server.js +17 -0
- package/dist/nextjs/server.js.map +1 -1
- package/dist/route/index.d.ts +3 -165
- package/dist/server/index.d.ts +4 -4
- package/dist/server/index.js +125 -39
- package/dist/server/index.js.map +1 -1
- package/dist/{token-manager-C2Ag5-s8.d.ts → token-manager-jKD_EsSE.d.ts} +0 -1
- package/dist/{types-VpVQIsyB.d.ts → types-BFB72jbM.d.ts} +1 -1
- package/dist/{types-CKsmzaB8.d.ts → types-DVjf37yO.d.ts} +37 -1
- package/package.json +6 -6
|
@@ -47,6 +47,13 @@ interface JobOptions {
|
|
|
47
47
|
* @default 1
|
|
48
48
|
*/
|
|
49
49
|
batchSize?: number;
|
|
50
|
+
/**
|
|
51
|
+
* How often this worker polls the DB for new jobs, in seconds. Lower =
|
|
52
|
+
* faster pickup but more idle SELECT load; higher = less DB chatter but
|
|
53
|
+
* slower pickup. Overrides the JOB_POLLING_INTERVAL_SECONDS default.
|
|
54
|
+
* @default 2 (pg-boss default) or JOB_POLLING_INTERVAL_SECONDS
|
|
55
|
+
*/
|
|
56
|
+
pollingIntervalSeconds?: number;
|
|
50
57
|
}
|
|
51
58
|
/**
|
|
52
59
|
* Send options for individual job dispatch
|
package/dist/cache/index.js
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { logger } from '@spfn/core/logger';
|
|
2
|
+
import { env } from '@spfn/core/config';
|
|
2
3
|
|
|
3
4
|
// src/cache/cache-factory.ts
|
|
5
|
+
function resilienceOptions() {
|
|
6
|
+
return {
|
|
7
|
+
maxRetriesPerRequest: env.CACHE_MAX_RETRIES_PER_REQUEST,
|
|
8
|
+
enableOfflineQueue: env.CACHE_ENABLE_OFFLINE_QUEUE
|
|
9
|
+
};
|
|
10
|
+
}
|
|
4
11
|
var cacheLogger = logger.child("@spfn/core:cache");
|
|
5
12
|
function hasCacheConfig() {
|
|
6
13
|
return !!(process.env.CACHE_URL || process.env.CACHE_WRITE_URL || process.env.CACHE_READ_URL || process.env.CACHE_SENTINEL_HOSTS || process.env.CACHE_CLUSTER_NODES);
|
|
7
14
|
}
|
|
8
15
|
function createClient(RedisClient, url) {
|
|
9
|
-
const options = {};
|
|
16
|
+
const options = { ...resilienceOptions() };
|
|
10
17
|
if (url.startsWith("rediss://")) {
|
|
11
18
|
options.tls = {
|
|
12
19
|
rejectUnauthorized: process.env.CACHE_TLS_REJECT_UNAUTHORIZED !== "false"
|
|
@@ -46,6 +53,7 @@ async function createCacheFromEnv() {
|
|
|
46
53
|
return { host: hostname, port: Number(port) || 26379 };
|
|
47
54
|
});
|
|
48
55
|
const options = {
|
|
56
|
+
...resilienceOptions(),
|
|
49
57
|
sentinels,
|
|
50
58
|
name: masterName,
|
|
51
59
|
password
|
|
@@ -61,6 +69,7 @@ async function createCacheFromEnv() {
|
|
|
61
69
|
});
|
|
62
70
|
const clusterOptions = {
|
|
63
71
|
redisOptions: {
|
|
72
|
+
...resilienceOptions(),
|
|
64
73
|
password
|
|
65
74
|
}
|
|
66
75
|
};
|
package/dist/cache/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/cache/cache-factory.ts","../../src/cache/cache-manager.ts"],"names":["cacheLogger","logger"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Cache factory with automatic environment variable detection\n */\n\nimport type { Redis, Cluster, RedisOptions, ClusterOptions } from 'ioredis';\nimport { logger } from '@spfn/core/logger';\n\nconst cacheLogger = logger.child('@spfn/core:cache');\n\nexport interface CacheClients {\n /** Primary cache for writes (or both read/write if no replica) */\n write?: Redis | Cluster;\n /** Replica cache for reads (optional, falls back to write) */\n read?: Redis | Cluster;\n}\n\n/**\n * Check if any cache configuration exists in environment\n */\nfunction hasCacheConfig(): boolean\n{\n return !!(\n process.env.CACHE_URL ||\n process.env.CACHE_WRITE_URL ||\n process.env.CACHE_READ_URL ||\n process.env.CACHE_SENTINEL_HOSTS ||\n process.env.CACHE_CLUSTER_NODES\n );\n}\n\n/**\n * Create cache client with TLS support\n */\nfunction createClient(\n RedisClient: new (url: string, options?: RedisOptions) => Redis,\n url: string,\n): Redis\n{\n const options: RedisOptions = {};\n\n // TLS support for secure connections\n if (url.startsWith('rediss://'))\n {\n options.tls = {\n rejectUnauthorized: process.env.CACHE_TLS_REJECT_UNAUTHORIZED !== 'false',\n };\n }\n\n return new RedisClient(url, options);\n}\n\n/**\n * Create cache client(s) from environment variables\n *\n * Supported patterns (priority order):\n * 1. Single instance: CACHE_URL\n * 2. Master-Replica: CACHE_WRITE_URL + CACHE_READ_URL\n * 3. Sentinel: CACHE_SENTINEL_HOSTS + CACHE_MASTER_NAME\n * 4. Cluster: CACHE_CLUSTER_NODES\n *\n * @returns Cache client(s) or undefined if no configuration found\n *\n * @example\n * ```bash\n * # Single (most common)\n * CACHE_URL=redis://localhost:6379\n * CACHE_URL=rediss://secure.cache.com:6380 # TLS\n *\n * # Master-Replica\n * CACHE_WRITE_URL=redis://master:6379\n * CACHE_READ_URL=redis://replica:6379\n *\n * # Sentinel\n * CACHE_SENTINEL_HOSTS=sentinel1:26379,sentinel2:26379\n * CACHE_MASTER_NAME=mymaster\n * CACHE_PASSWORD=secret\n *\n * # Cluster\n * CACHE_CLUSTER_NODES=node1:6379,node2:6379,node3:6379\n * CACHE_PASSWORD=secret\n * ```\n */\nexport async function createCacheFromEnv(): Promise<CacheClients>\n{\n // Quick exit if no cache config\n if (!hasCacheConfig())\n {\n cacheLogger.info('No cache configuration found - running without cache');\n\n return { write: undefined, read: undefined };\n }\n\n try\n {\n // Dynamic import to avoid bundling if not used\n const ioredis = await import('ioredis');\n const RedisClient = ioredis.default;\n\n // Get environment variables\n const singleUrl = process.env.CACHE_URL;\n const writeUrl = process.env.CACHE_WRITE_URL;\n const readUrl = process.env.CACHE_READ_URL;\n const clusterNodes = process.env.CACHE_CLUSTER_NODES;\n const sentinelHosts = process.env.CACHE_SENTINEL_HOSTS;\n const masterName = process.env.CACHE_MASTER_NAME;\n const password = process.env.CACHE_PASSWORD;\n\n // 1. Single instance (most common - highest priority)\n if (singleUrl && !writeUrl && !readUrl && !clusterNodes)\n {\n const client = createClient(RedisClient, singleUrl);\n cacheLogger.debug('Created single cache instance', { url: singleUrl.replace(/:[^:@]+@/, ':***@') });\n\n return { write: client, read: client };\n }\n\n // 2. Master-Replica pattern (both URLs required)\n if (writeUrl && readUrl)\n {\n const write = createClient(RedisClient, writeUrl);\n const read = createClient(RedisClient, readUrl);\n cacheLogger.debug('Created master-replica cache instances');\n\n return { write, read };\n }\n\n // 3. Sentinel pattern\n if (sentinelHosts && masterName)\n {\n const sentinels = sentinelHosts.split(',').map((host) =>\n {\n const [hostname, port] = host.trim().split(':');\n\n return { host: hostname, port: Number(port) || 26379 };\n });\n\n const options: RedisOptions = {\n sentinels,\n name: masterName,\n password,\n };\n\n const client = new RedisClient(options);\n cacheLogger.debug('Created sentinel cache instance', { masterName, sentinels: sentinels.length });\n\n return { write: client, read: client };\n }\n\n // 4. Cluster pattern\n if (clusterNodes)\n {\n const nodes = clusterNodes.split(',').map((node) =>\n {\n const [host, port] = node.trim().split(':');\n\n return { host, port: Number(port) || 6379 };\n });\n\n const clusterOptions: ClusterOptions = {\n redisOptions: {\n password,\n },\n };\n\n const cluster = new RedisClient.Cluster(nodes, clusterOptions);\n cacheLogger.debug('Created cluster cache instance', { nodes: nodes.length });\n\n return { write: cluster, read: cluster };\n }\n\n // 5. Fallback: Single URL with other configs present\n if (singleUrl)\n {\n const client = createClient(RedisClient, singleUrl);\n cacheLogger.debug('Created cache instance (fallback)', { url: singleUrl.replace(/:[^:@]+@/, ':***@') });\n\n return { write: client, read: client };\n }\n\n // No valid configuration\n cacheLogger.info('No valid cache configuration found - running without cache');\n\n return { write: undefined, read: undefined };\n }\n catch (error)\n {\n if (error instanceof Error)\n {\n // Check if it's a missing dependency error\n if (error.message.includes('Cannot find module'))\n {\n cacheLogger.warn(\n 'Cache client library not installed',\n error,\n {\n suggestion: 'Install ioredis to enable cache: pnpm install ioredis',\n mode: 'disabled',\n },\n );\n }\n else\n {\n cacheLogger.warn(\n 'Failed to create cache client',\n error,\n { mode: 'disabled' },\n );\n }\n }\n else\n {\n cacheLogger.warn(\n 'Failed to create cache client',\n { error: String(error), mode: 'disabled' },\n );\n }\n\n return { write: undefined, read: undefined };\n }\n}\n\n/**\n * Create single cache client (backward compatibility)\n * Only returns write instance\n */\nexport async function createSingleCacheFromEnv(): Promise<Redis | Cluster | undefined>\n{\n const { write } = await createCacheFromEnv();\n\n return write;\n}\n","/**\n * Global cache instance manager\n * Provides singleton access to cache (Valkey/Redis) across all modules\n * Supports Master-Replica pattern with separate read/write instances\n *\n * When cache is unavailable, falls back to disabled mode gracefully\n */\n\nimport type { Redis, Cluster } from 'ioredis';\n\nimport { createCacheFromEnv } from './cache-factory';\nimport { logger } from '@spfn/core/logger';\n\nconst cacheLogger = logger.child('@spfn/core:cache');\n\n// ── globalThis singleton ──────────────────────────────────────────\n// CJS/ESM dual-package hazard: 모듈이 두 번 로드되어도\n// Symbol.for() + globalThis로 동일 상태를 공유한다.\ninterface CacheState\n{\n write: Redis | Cluster | undefined;\n read: Redis | Cluster | undefined;\n disabled: boolean;\n}\n\nconst CACHE_KEY = Symbol.for('@spfn/core:cache');\n\nconst state: CacheState = ((globalThis as any)[CACHE_KEY] ??= {\n write: undefined,\n read: undefined,\n disabled: false,\n});\n\n/**\n * Get global cache write instance\n *\n * @returns Cache write instance or undefined if disabled/not initialized\n *\n * @example\n * ```typescript\n * import { getCache } from '@spfn/core/cache';\n *\n * const cache = getCache();\n * if (cache) {\n * await cache.set('key', 'value');\n * } else {\n * // Cache disabled - handle gracefully\n * console.log('Cache unavailable, skipping...');\n * }\n * ```\n */\nexport function getCache(): Redis | Cluster | undefined\n{\n return state.write;\n}\n\n/**\n * Get global cache read instance (falls back to write if no replica)\n *\n * @returns Cache read instance or write instance as fallback, undefined if disabled\n *\n * @example\n * ```typescript\n * import { getCacheRead } from '@spfn/core/cache';\n *\n * const cache = getCacheRead();\n * if (cache) {\n * const value = await cache.get('key');\n * }\n * ```\n */\nexport function getCacheRead(): Redis | Cluster | undefined\n{\n return state.read ?? state.write;\n}\n\n/**\n * Check if cache is disabled (connection failed or not configured)\n *\n * @example\n * ```typescript\n * import { isCacheDisabled } from '@spfn/core/cache';\n *\n * if (isCacheDisabled()) {\n * // Use alternative strategy (e.g., in-memory cache, database)\n * return await fetchFromDatabase();\n * }\n * ```\n */\nexport function isCacheDisabled(): boolean\n{\n return state.disabled;\n}\n\n/**\n * Set global cache instances (for testing or manual configuration)\n *\n * @param write - Cache write instance\n * @param read - Cache read instance (optional, defaults to write)\n *\n * @example\n * ```typescript\n * import { setCache } from '@spfn/core/cache';\n * import Redis from 'ioredis';\n *\n * const write = new Redis('redis://master:6379');\n * const read = new Redis('redis://replica:6379');\n * setCache(write, read);\n * ```\n */\nexport function setCache(\n write: Redis | Cluster | undefined,\n read?: Redis | Cluster | undefined,\n): void\n{\n state.write = write;\n state.read = read ?? write;\n state.disabled = !write;\n}\n\n/**\n * Initialize cache from environment variables\n * Automatically called by startServer()\n *\n * Supported environment variables (priority order):\n * - VALKEY_URL / CACHE_URL (single instance)\n * - VALKEY_WRITE_URL + VALKEY_READ_URL (master-replica)\n * - VALKEY_SENTINEL_HOSTS + VALKEY_MASTER_NAME (sentinel)\n * - VALKEY_CLUSTER_NODES (cluster)\n * - VALKEY_TLS_REJECT_UNAUTHORIZED (TLS config)\n * - Legacy: REDIS_* (backward compatibility)\n *\n * @returns Object with write and read instances, or undefined if disabled\n *\n * @example\n * ```typescript\n * import { initCache } from '@spfn/core/cache';\n *\n * // Manual initialization (not needed if using startServer)\n * const { write, read, disabled } = await initCache();\n * if (!disabled) {\n * console.log('Cache available');\n * }\n * ```\n */\nexport async function initCache(): Promise<{\n write?: Redis | Cluster;\n read?: Redis | Cluster;\n disabled: boolean;\n}>\n{\n // Already initialized\n if (state.write)\n {\n return { write: state.write, read: state.read, disabled: state.disabled };\n }\n\n // Auto-detect from environment\n const { write, read } = await createCacheFromEnv();\n\n if (write)\n {\n try\n {\n // Test connection\n await write.ping();\n\n // Test read instance if different\n if (read && read !== write)\n {\n await read.ping();\n }\n\n state.write = write;\n state.read = read;\n state.disabled = false;\n\n const hasReplica = read && read !== write;\n cacheLogger.info(\n hasReplica\n ? 'Cache connected (Master-Replica)'\n : 'Cache connected',\n { mode: 'enabled' },\n );\n\n return { write: state.write, read: state.read, disabled: false };\n }\n catch (error)\n {\n cacheLogger.error(\n 'Cache connection failed - running in disabled mode',\n error instanceof Error ? error : new Error(String(error)),\n { mode: 'disabled' },\n );\n\n // Clean up failed connections\n try\n {\n await write.quit();\n if (read && read !== write)\n {\n await read.quit();\n }\n }\n catch\n {\n // Ignore cleanup errors\n }\n\n state.disabled = true;\n\n return { write: undefined, read: undefined, disabled: true };\n }\n }\n\n // No configuration or library not installed\n state.disabled = true;\n cacheLogger.info('Cache disabled - no configuration or library not installed', { mode: 'disabled' });\n\n return { write: undefined, read: undefined, disabled: true };\n}\n\n/**\n * Close all cache connections and cleanup\n *\n * @example\n * ```typescript\n * import { closeCache } from '@spfn/core/cache';\n *\n * // During graceful shutdown\n * await closeCache();\n * ```\n */\nexport async function closeCache(): Promise<void>\n{\n if (state.disabled)\n {\n cacheLogger.debug('Cache already disabled, nothing to close');\n\n return;\n }\n\n const closePromises: Promise<unknown>[] = [];\n\n if (state.write)\n {\n closePromises.push(\n state.write.quit().catch((err: Error) =>\n {\n cacheLogger.error('Error closing cache write instance', err);\n }),\n );\n }\n\n if (state.read && state.read !== state.write)\n {\n closePromises.push(\n state.read.quit().catch((err: Error) =>\n {\n cacheLogger.error('Error closing cache read instance', err);\n }),\n );\n }\n\n await Promise.all(closePromises);\n\n state.write = undefined;\n state.read = undefined;\n state.disabled = true;\n\n cacheLogger.info('Cache connections closed', { mode: 'disabled' });\n}\n\n/**\n * Get cache connection info (for debugging)\n *\n * @example\n * ```typescript\n * import { getCacheInfo } from '@spfn/core/cache';\n *\n * const info = getCacheInfo();\n * console.log(info);\n * // {\n * // hasWrite: true,\n * // hasRead: true,\n * // isReplica: true,\n * // disabled: false\n * // }\n * ```\n */\nexport function getCacheInfo(): {\n hasWrite: boolean;\n hasRead: boolean;\n isReplica: boolean;\n disabled: boolean;\n}\n{\n return {\n hasWrite: !!state.write,\n hasRead: !!state.read,\n isReplica: !!(state.read && state.read !== state.write),\n disabled: state.disabled,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/cache/cache-factory.ts","../../src/cache/cache-manager.ts"],"names":["cacheLogger","logger"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Cache factory with automatic environment variable detection\n */\n\nimport type { Redis, Cluster, RedisOptions, ClusterOptions } from 'ioredis';\nimport { logger } from '@spfn/core/logger';\nimport { env } from '@spfn/core/config';\n\n/**\n * Connection-resilience options applied to every cache client.\n *\n * ioredis defaults to an unbounded offline command queue and 20 retries per\n * request, so a post-startup cache outage can buffer every command and hang\n * awaits. Bound retries (CACHE_MAX_RETRIES_PER_REQUEST, default 3) so commands\n * fail fast instead of hanging; CACHE_ENABLE_OFFLINE_QUEUE (default true) can be\n * set false for strict fail-fast (reject immediately while disconnected).\n */\nfunction resilienceOptions(): Pick<RedisOptions, 'maxRetriesPerRequest' | 'enableOfflineQueue'>\n{\n return {\n maxRetriesPerRequest: env.CACHE_MAX_RETRIES_PER_REQUEST,\n enableOfflineQueue: env.CACHE_ENABLE_OFFLINE_QUEUE,\n };\n}\n\nconst cacheLogger = logger.child('@spfn/core:cache');\n\nexport interface CacheClients {\n /** Primary cache for writes (or both read/write if no replica) */\n write?: Redis | Cluster;\n /** Replica cache for reads (optional, falls back to write) */\n read?: Redis | Cluster;\n}\n\n/**\n * Check if any cache configuration exists in environment\n */\nfunction hasCacheConfig(): boolean\n{\n return !!(\n process.env.CACHE_URL ||\n process.env.CACHE_WRITE_URL ||\n process.env.CACHE_READ_URL ||\n process.env.CACHE_SENTINEL_HOSTS ||\n process.env.CACHE_CLUSTER_NODES\n );\n}\n\n/**\n * Create cache client with TLS support\n */\nfunction createClient(\n RedisClient: new (url: string, options?: RedisOptions) => Redis,\n url: string,\n): Redis\n{\n const options: RedisOptions = { ...resilienceOptions() };\n\n // TLS support for secure connections\n if (url.startsWith('rediss://'))\n {\n options.tls = {\n rejectUnauthorized: process.env.CACHE_TLS_REJECT_UNAUTHORIZED !== 'false',\n };\n }\n\n return new RedisClient(url, options);\n}\n\n/**\n * Create cache client(s) from environment variables\n *\n * Supported patterns (priority order):\n * 1. Single instance: CACHE_URL\n * 2. Master-Replica: CACHE_WRITE_URL + CACHE_READ_URL\n * 3. Sentinel: CACHE_SENTINEL_HOSTS + CACHE_MASTER_NAME\n * 4. Cluster: CACHE_CLUSTER_NODES\n *\n * @returns Cache client(s) or undefined if no configuration found\n *\n * @example\n * ```bash\n * # Single (most common)\n * CACHE_URL=redis://localhost:6379\n * CACHE_URL=rediss://secure.cache.com:6380 # TLS\n *\n * # Master-Replica\n * CACHE_WRITE_URL=redis://master:6379\n * CACHE_READ_URL=redis://replica:6379\n *\n * # Sentinel\n * CACHE_SENTINEL_HOSTS=sentinel1:26379,sentinel2:26379\n * CACHE_MASTER_NAME=mymaster\n * CACHE_PASSWORD=secret\n *\n * # Cluster\n * CACHE_CLUSTER_NODES=node1:6379,node2:6379,node3:6379\n * CACHE_PASSWORD=secret\n * ```\n */\nexport async function createCacheFromEnv(): Promise<CacheClients>\n{\n // Quick exit if no cache config\n if (!hasCacheConfig())\n {\n cacheLogger.info('No cache configuration found - running without cache');\n\n return { write: undefined, read: undefined };\n }\n\n try\n {\n // Dynamic import to avoid bundling if not used\n const ioredis = await import('ioredis');\n const RedisClient = ioredis.default;\n\n // Get environment variables\n const singleUrl = process.env.CACHE_URL;\n const writeUrl = process.env.CACHE_WRITE_URL;\n const readUrl = process.env.CACHE_READ_URL;\n const clusterNodes = process.env.CACHE_CLUSTER_NODES;\n const sentinelHosts = process.env.CACHE_SENTINEL_HOSTS;\n const masterName = process.env.CACHE_MASTER_NAME;\n const password = process.env.CACHE_PASSWORD;\n\n // 1. Single instance (most common - highest priority)\n if (singleUrl && !writeUrl && !readUrl && !clusterNodes)\n {\n const client = createClient(RedisClient, singleUrl);\n cacheLogger.debug('Created single cache instance', { url: singleUrl.replace(/:[^:@]+@/, ':***@') });\n\n return { write: client, read: client };\n }\n\n // 2. Master-Replica pattern (both URLs required)\n if (writeUrl && readUrl)\n {\n const write = createClient(RedisClient, writeUrl);\n const read = createClient(RedisClient, readUrl);\n cacheLogger.debug('Created master-replica cache instances');\n\n return { write, read };\n }\n\n // 3. Sentinel pattern\n if (sentinelHosts && masterName)\n {\n const sentinels = sentinelHosts.split(',').map((host) =>\n {\n const [hostname, port] = host.trim().split(':');\n\n return { host: hostname, port: Number(port) || 26379 };\n });\n\n const options: RedisOptions = {\n ...resilienceOptions(),\n sentinels,\n name: masterName,\n password,\n };\n\n const client = new RedisClient(options);\n cacheLogger.debug('Created sentinel cache instance', { masterName, sentinels: sentinels.length });\n\n return { write: client, read: client };\n }\n\n // 4. Cluster pattern\n if (clusterNodes)\n {\n const nodes = clusterNodes.split(',').map((node) =>\n {\n const [host, port] = node.trim().split(':');\n\n return { host, port: Number(port) || 6379 };\n });\n\n const clusterOptions: ClusterOptions = {\n redisOptions: {\n ...resilienceOptions(),\n password,\n },\n };\n\n const cluster = new RedisClient.Cluster(nodes, clusterOptions);\n cacheLogger.debug('Created cluster cache instance', { nodes: nodes.length });\n\n return { write: cluster, read: cluster };\n }\n\n // 5. Fallback: Single URL with other configs present\n if (singleUrl)\n {\n const client = createClient(RedisClient, singleUrl);\n cacheLogger.debug('Created cache instance (fallback)', { url: singleUrl.replace(/:[^:@]+@/, ':***@') });\n\n return { write: client, read: client };\n }\n\n // No valid configuration\n cacheLogger.info('No valid cache configuration found - running without cache');\n\n return { write: undefined, read: undefined };\n }\n catch (error)\n {\n if (error instanceof Error)\n {\n // Check if it's a missing dependency error\n if (error.message.includes('Cannot find module'))\n {\n cacheLogger.warn(\n 'Cache client library not installed',\n error,\n {\n suggestion: 'Install ioredis to enable cache: pnpm install ioredis',\n mode: 'disabled',\n },\n );\n }\n else\n {\n cacheLogger.warn(\n 'Failed to create cache client',\n error,\n { mode: 'disabled' },\n );\n }\n }\n else\n {\n cacheLogger.warn(\n 'Failed to create cache client',\n { error: String(error), mode: 'disabled' },\n );\n }\n\n return { write: undefined, read: undefined };\n }\n}\n\n/**\n * Create single cache client (backward compatibility)\n * Only returns write instance\n */\nexport async function createSingleCacheFromEnv(): Promise<Redis | Cluster | undefined>\n{\n const { write } = await createCacheFromEnv();\n\n return write;\n}\n","/**\n * Global cache instance manager\n * Provides singleton access to cache (Valkey/Redis) across all modules\n * Supports Master-Replica pattern with separate read/write instances\n *\n * When cache is unavailable, falls back to disabled mode gracefully\n */\n\nimport type { Redis, Cluster } from 'ioredis';\n\nimport { createCacheFromEnv } from './cache-factory';\nimport { logger } from '@spfn/core/logger';\n\nconst cacheLogger = logger.child('@spfn/core:cache');\n\n// ── globalThis singleton ──────────────────────────────────────────\n// CJS/ESM dual-package hazard: 모듈이 두 번 로드되어도\n// Symbol.for() + globalThis로 동일 상태를 공유한다.\ninterface CacheState\n{\n write: Redis | Cluster | undefined;\n read: Redis | Cluster | undefined;\n disabled: boolean;\n}\n\nconst CACHE_KEY = Symbol.for('@spfn/core:cache');\n\nconst state: CacheState = ((globalThis as any)[CACHE_KEY] ??= {\n write: undefined,\n read: undefined,\n disabled: false,\n});\n\n/**\n * Get global cache write instance\n *\n * @returns Cache write instance or undefined if disabled/not initialized\n *\n * @example\n * ```typescript\n * import { getCache } from '@spfn/core/cache';\n *\n * const cache = getCache();\n * if (cache) {\n * await cache.set('key', 'value');\n * } else {\n * // Cache disabled - handle gracefully\n * console.log('Cache unavailable, skipping...');\n * }\n * ```\n */\nexport function getCache(): Redis | Cluster | undefined\n{\n return state.write;\n}\n\n/**\n * Get global cache read instance (falls back to write if no replica)\n *\n * @returns Cache read instance or write instance as fallback, undefined if disabled\n *\n * @example\n * ```typescript\n * import { getCacheRead } from '@spfn/core/cache';\n *\n * const cache = getCacheRead();\n * if (cache) {\n * const value = await cache.get('key');\n * }\n * ```\n */\nexport function getCacheRead(): Redis | Cluster | undefined\n{\n return state.read ?? state.write;\n}\n\n/**\n * Check if cache is disabled (connection failed or not configured)\n *\n * @example\n * ```typescript\n * import { isCacheDisabled } from '@spfn/core/cache';\n *\n * if (isCacheDisabled()) {\n * // Use alternative strategy (e.g., in-memory cache, database)\n * return await fetchFromDatabase();\n * }\n * ```\n */\nexport function isCacheDisabled(): boolean\n{\n return state.disabled;\n}\n\n/**\n * Set global cache instances (for testing or manual configuration)\n *\n * @param write - Cache write instance\n * @param read - Cache read instance (optional, defaults to write)\n *\n * @example\n * ```typescript\n * import { setCache } from '@spfn/core/cache';\n * import Redis from 'ioredis';\n *\n * const write = new Redis('redis://master:6379');\n * const read = new Redis('redis://replica:6379');\n * setCache(write, read);\n * ```\n */\nexport function setCache(\n write: Redis | Cluster | undefined,\n read?: Redis | Cluster | undefined,\n): void\n{\n state.write = write;\n state.read = read ?? write;\n state.disabled = !write;\n}\n\n/**\n * Initialize cache from environment variables\n * Automatically called by startServer()\n *\n * Supported environment variables (priority order):\n * - VALKEY_URL / CACHE_URL (single instance)\n * - VALKEY_WRITE_URL + VALKEY_READ_URL (master-replica)\n * - VALKEY_SENTINEL_HOSTS + VALKEY_MASTER_NAME (sentinel)\n * - VALKEY_CLUSTER_NODES (cluster)\n * - VALKEY_TLS_REJECT_UNAUTHORIZED (TLS config)\n * - Legacy: REDIS_* (backward compatibility)\n *\n * @returns Object with write and read instances, or undefined if disabled\n *\n * @example\n * ```typescript\n * import { initCache } from '@spfn/core/cache';\n *\n * // Manual initialization (not needed if using startServer)\n * const { write, read, disabled } = await initCache();\n * if (!disabled) {\n * console.log('Cache available');\n * }\n * ```\n */\nexport async function initCache(): Promise<{\n write?: Redis | Cluster;\n read?: Redis | Cluster;\n disabled: boolean;\n}>\n{\n // Already initialized\n if (state.write)\n {\n return { write: state.write, read: state.read, disabled: state.disabled };\n }\n\n // Auto-detect from environment\n const { write, read } = await createCacheFromEnv();\n\n if (write)\n {\n try\n {\n // Test connection\n await write.ping();\n\n // Test read instance if different\n if (read && read !== write)\n {\n await read.ping();\n }\n\n state.write = write;\n state.read = read;\n state.disabled = false;\n\n const hasReplica = read && read !== write;\n cacheLogger.info(\n hasReplica\n ? 'Cache connected (Master-Replica)'\n : 'Cache connected',\n { mode: 'enabled' },\n );\n\n return { write: state.write, read: state.read, disabled: false };\n }\n catch (error)\n {\n cacheLogger.error(\n 'Cache connection failed - running in disabled mode',\n error instanceof Error ? error : new Error(String(error)),\n { mode: 'disabled' },\n );\n\n // Clean up failed connections\n try\n {\n await write.quit();\n if (read && read !== write)\n {\n await read.quit();\n }\n }\n catch\n {\n // Ignore cleanup errors\n }\n\n state.disabled = true;\n\n return { write: undefined, read: undefined, disabled: true };\n }\n }\n\n // No configuration or library not installed\n state.disabled = true;\n cacheLogger.info('Cache disabled - no configuration or library not installed', { mode: 'disabled' });\n\n return { write: undefined, read: undefined, disabled: true };\n}\n\n/**\n * Close all cache connections and cleanup\n *\n * @example\n * ```typescript\n * import { closeCache } from '@spfn/core/cache';\n *\n * // During graceful shutdown\n * await closeCache();\n * ```\n */\nexport async function closeCache(): Promise<void>\n{\n if (state.disabled)\n {\n cacheLogger.debug('Cache already disabled, nothing to close');\n\n return;\n }\n\n const closePromises: Promise<unknown>[] = [];\n\n if (state.write)\n {\n closePromises.push(\n state.write.quit().catch((err: Error) =>\n {\n cacheLogger.error('Error closing cache write instance', err);\n }),\n );\n }\n\n if (state.read && state.read !== state.write)\n {\n closePromises.push(\n state.read.quit().catch((err: Error) =>\n {\n cacheLogger.error('Error closing cache read instance', err);\n }),\n );\n }\n\n await Promise.all(closePromises);\n\n state.write = undefined;\n state.read = undefined;\n state.disabled = true;\n\n cacheLogger.info('Cache connections closed', { mode: 'disabled' });\n}\n\n/**\n * Get cache connection info (for debugging)\n *\n * @example\n * ```typescript\n * import { getCacheInfo } from '@spfn/core/cache';\n *\n * const info = getCacheInfo();\n * console.log(info);\n * // {\n * // hasWrite: true,\n * // hasRead: true,\n * // isReplica: true,\n * // disabled: false\n * // }\n * ```\n */\nexport function getCacheInfo(): {\n hasWrite: boolean;\n hasRead: boolean;\n isReplica: boolean;\n disabled: boolean;\n}\n{\n return {\n hasWrite: !!state.write,\n hasRead: !!state.read,\n isReplica: !!(state.read && state.read !== state.write),\n disabled: state.disabled,\n };\n}\n"]}
|
package/dist/config/index.d.ts
CHANGED
|
@@ -72,6 +72,16 @@ declare const coreEnvSchema: {
|
|
|
72
72
|
} & {
|
|
73
73
|
key: "DB_POOL_MAX";
|
|
74
74
|
};
|
|
75
|
+
DB_POOL_READ_MAX: {
|
|
76
|
+
description: string;
|
|
77
|
+
required: boolean;
|
|
78
|
+
examples: number[];
|
|
79
|
+
} & {
|
|
80
|
+
type: "number";
|
|
81
|
+
validator: (value: string) => number;
|
|
82
|
+
} & {
|
|
83
|
+
key: "DB_POOL_READ_MAX";
|
|
84
|
+
};
|
|
75
85
|
DB_POOL_IDLE_TIMEOUT: {
|
|
76
86
|
description: string;
|
|
77
87
|
default: number;
|
|
@@ -212,6 +222,26 @@ declare const coreEnvSchema: {
|
|
|
212
222
|
} & {
|
|
213
223
|
key: "TRANSACTION_TIMEOUT";
|
|
214
224
|
};
|
|
225
|
+
TRANSACTION_IDLE_TIMEOUT: {
|
|
226
|
+
description: string;
|
|
227
|
+
default: number;
|
|
228
|
+
examples: number[];
|
|
229
|
+
} & {
|
|
230
|
+
type: "number";
|
|
231
|
+
validator: (value: string) => number;
|
|
232
|
+
} & {
|
|
233
|
+
key: "TRANSACTION_IDLE_TIMEOUT";
|
|
234
|
+
};
|
|
235
|
+
JOB_POLLING_INTERVAL_SECONDS: {
|
|
236
|
+
description: string;
|
|
237
|
+
default: number;
|
|
238
|
+
examples: number[];
|
|
239
|
+
} & {
|
|
240
|
+
type: "number";
|
|
241
|
+
validator: (value: string) => number;
|
|
242
|
+
} & {
|
|
243
|
+
key: "JOB_POLLING_INTERVAL_SECONDS";
|
|
244
|
+
};
|
|
215
245
|
DB_DEBUG_TRACE: {
|
|
216
246
|
description: string;
|
|
217
247
|
default: boolean;
|
|
@@ -337,6 +367,36 @@ declare const coreEnvSchema: {
|
|
|
337
367
|
} & {
|
|
338
368
|
key: "CACHE_TLS_REJECT_UNAUTHORIZED";
|
|
339
369
|
};
|
|
370
|
+
CACHE_MAX_RETRIES_PER_REQUEST: {
|
|
371
|
+
description: string;
|
|
372
|
+
default: number;
|
|
373
|
+
examples: number[];
|
|
374
|
+
} & {
|
|
375
|
+
type: "number";
|
|
376
|
+
validator: (value: string) => number;
|
|
377
|
+
} & {
|
|
378
|
+
key: "CACHE_MAX_RETRIES_PER_REQUEST";
|
|
379
|
+
};
|
|
380
|
+
CACHE_ENABLE_OFFLINE_QUEUE: {
|
|
381
|
+
description: string;
|
|
382
|
+
default: boolean;
|
|
383
|
+
examples: boolean[];
|
|
384
|
+
} & {
|
|
385
|
+
type: "boolean";
|
|
386
|
+
validator: (value: string) => boolean;
|
|
387
|
+
} & {
|
|
388
|
+
key: "CACHE_ENABLE_OFFLINE_QUEUE";
|
|
389
|
+
};
|
|
390
|
+
DB_MAX_ROWS: {
|
|
391
|
+
description: string;
|
|
392
|
+
default: number;
|
|
393
|
+
examples: number[];
|
|
394
|
+
} & {
|
|
395
|
+
type: "number";
|
|
396
|
+
validator: (value: string) => number;
|
|
397
|
+
} & {
|
|
398
|
+
key: "DB_MAX_ROWS";
|
|
399
|
+
};
|
|
340
400
|
PORT: {
|
|
341
401
|
description: string;
|
|
342
402
|
default: number;
|
|
@@ -493,6 +553,17 @@ declare const coreEnvSchema: {
|
|
|
493
553
|
} & {
|
|
494
554
|
key: "SPFN_PROXY_SECRET_PREVIOUS";
|
|
495
555
|
};
|
|
556
|
+
TRUSTED_PROXY_HOPS: {
|
|
557
|
+
description: string;
|
|
558
|
+
default: number;
|
|
559
|
+
nextjs: boolean;
|
|
560
|
+
examples: number[];
|
|
561
|
+
} & {
|
|
562
|
+
type: "number";
|
|
563
|
+
validator: (value: string) => number;
|
|
564
|
+
} & {
|
|
565
|
+
key: "TRUSTED_PROXY_HOPS";
|
|
566
|
+
};
|
|
496
567
|
};
|
|
497
568
|
|
|
498
569
|
/**
|
|
@@ -557,6 +628,16 @@ declare const registry: _spfn_core_env.EnvRegistry<{
|
|
|
557
628
|
} & {
|
|
558
629
|
key: "DB_POOL_MAX";
|
|
559
630
|
};
|
|
631
|
+
DB_POOL_READ_MAX: {
|
|
632
|
+
description: string;
|
|
633
|
+
required: boolean;
|
|
634
|
+
examples: number[];
|
|
635
|
+
} & {
|
|
636
|
+
type: "number";
|
|
637
|
+
validator: (value: string) => number;
|
|
638
|
+
} & {
|
|
639
|
+
key: "DB_POOL_READ_MAX";
|
|
640
|
+
};
|
|
560
641
|
DB_POOL_IDLE_TIMEOUT: {
|
|
561
642
|
description: string;
|
|
562
643
|
default: number;
|
|
@@ -697,6 +778,26 @@ declare const registry: _spfn_core_env.EnvRegistry<{
|
|
|
697
778
|
} & {
|
|
698
779
|
key: "TRANSACTION_TIMEOUT";
|
|
699
780
|
};
|
|
781
|
+
TRANSACTION_IDLE_TIMEOUT: {
|
|
782
|
+
description: string;
|
|
783
|
+
default: number;
|
|
784
|
+
examples: number[];
|
|
785
|
+
} & {
|
|
786
|
+
type: "number";
|
|
787
|
+
validator: (value: string) => number;
|
|
788
|
+
} & {
|
|
789
|
+
key: "TRANSACTION_IDLE_TIMEOUT";
|
|
790
|
+
};
|
|
791
|
+
JOB_POLLING_INTERVAL_SECONDS: {
|
|
792
|
+
description: string;
|
|
793
|
+
default: number;
|
|
794
|
+
examples: number[];
|
|
795
|
+
} & {
|
|
796
|
+
type: "number";
|
|
797
|
+
validator: (value: string) => number;
|
|
798
|
+
} & {
|
|
799
|
+
key: "JOB_POLLING_INTERVAL_SECONDS";
|
|
800
|
+
};
|
|
700
801
|
DB_DEBUG_TRACE: {
|
|
701
802
|
description: string;
|
|
702
803
|
default: boolean;
|
|
@@ -822,6 +923,36 @@ declare const registry: _spfn_core_env.EnvRegistry<{
|
|
|
822
923
|
} & {
|
|
823
924
|
key: "CACHE_TLS_REJECT_UNAUTHORIZED";
|
|
824
925
|
};
|
|
926
|
+
CACHE_MAX_RETRIES_PER_REQUEST: {
|
|
927
|
+
description: string;
|
|
928
|
+
default: number;
|
|
929
|
+
examples: number[];
|
|
930
|
+
} & {
|
|
931
|
+
type: "number";
|
|
932
|
+
validator: (value: string) => number;
|
|
933
|
+
} & {
|
|
934
|
+
key: "CACHE_MAX_RETRIES_PER_REQUEST";
|
|
935
|
+
};
|
|
936
|
+
CACHE_ENABLE_OFFLINE_QUEUE: {
|
|
937
|
+
description: string;
|
|
938
|
+
default: boolean;
|
|
939
|
+
examples: boolean[];
|
|
940
|
+
} & {
|
|
941
|
+
type: "boolean";
|
|
942
|
+
validator: (value: string) => boolean;
|
|
943
|
+
} & {
|
|
944
|
+
key: "CACHE_ENABLE_OFFLINE_QUEUE";
|
|
945
|
+
};
|
|
946
|
+
DB_MAX_ROWS: {
|
|
947
|
+
description: string;
|
|
948
|
+
default: number;
|
|
949
|
+
examples: number[];
|
|
950
|
+
} & {
|
|
951
|
+
type: "number";
|
|
952
|
+
validator: (value: string) => number;
|
|
953
|
+
} & {
|
|
954
|
+
key: "DB_MAX_ROWS";
|
|
955
|
+
};
|
|
825
956
|
PORT: {
|
|
826
957
|
description: string;
|
|
827
958
|
default: number;
|
|
@@ -978,6 +1109,17 @@ declare const registry: _spfn_core_env.EnvRegistry<{
|
|
|
978
1109
|
} & {
|
|
979
1110
|
key: "SPFN_PROXY_SECRET_PREVIOUS";
|
|
980
1111
|
};
|
|
1112
|
+
TRUSTED_PROXY_HOPS: {
|
|
1113
|
+
description: string;
|
|
1114
|
+
default: number;
|
|
1115
|
+
nextjs: boolean;
|
|
1116
|
+
examples: number[];
|
|
1117
|
+
} & {
|
|
1118
|
+
type: "number";
|
|
1119
|
+
validator: (value: string) => number;
|
|
1120
|
+
} & {
|
|
1121
|
+
key: "TRUSTED_PROXY_HOPS";
|
|
1122
|
+
};
|
|
981
1123
|
}>;
|
|
982
1124
|
/**
|
|
983
1125
|
* Validated environment configuration
|
|
@@ -1035,6 +1177,16 @@ declare const env: _spfn_core_env.InferEnvType<{
|
|
|
1035
1177
|
} & {
|
|
1036
1178
|
key: "DB_POOL_MAX";
|
|
1037
1179
|
};
|
|
1180
|
+
DB_POOL_READ_MAX: {
|
|
1181
|
+
description: string;
|
|
1182
|
+
required: boolean;
|
|
1183
|
+
examples: number[];
|
|
1184
|
+
} & {
|
|
1185
|
+
type: "number";
|
|
1186
|
+
validator: (value: string) => number;
|
|
1187
|
+
} & {
|
|
1188
|
+
key: "DB_POOL_READ_MAX";
|
|
1189
|
+
};
|
|
1038
1190
|
DB_POOL_IDLE_TIMEOUT: {
|
|
1039
1191
|
description: string;
|
|
1040
1192
|
default: number;
|
|
@@ -1175,6 +1327,26 @@ declare const env: _spfn_core_env.InferEnvType<{
|
|
|
1175
1327
|
} & {
|
|
1176
1328
|
key: "TRANSACTION_TIMEOUT";
|
|
1177
1329
|
};
|
|
1330
|
+
TRANSACTION_IDLE_TIMEOUT: {
|
|
1331
|
+
description: string;
|
|
1332
|
+
default: number;
|
|
1333
|
+
examples: number[];
|
|
1334
|
+
} & {
|
|
1335
|
+
type: "number";
|
|
1336
|
+
validator: (value: string) => number;
|
|
1337
|
+
} & {
|
|
1338
|
+
key: "TRANSACTION_IDLE_TIMEOUT";
|
|
1339
|
+
};
|
|
1340
|
+
JOB_POLLING_INTERVAL_SECONDS: {
|
|
1341
|
+
description: string;
|
|
1342
|
+
default: number;
|
|
1343
|
+
examples: number[];
|
|
1344
|
+
} & {
|
|
1345
|
+
type: "number";
|
|
1346
|
+
validator: (value: string) => number;
|
|
1347
|
+
} & {
|
|
1348
|
+
key: "JOB_POLLING_INTERVAL_SECONDS";
|
|
1349
|
+
};
|
|
1178
1350
|
DB_DEBUG_TRACE: {
|
|
1179
1351
|
description: string;
|
|
1180
1352
|
default: boolean;
|
|
@@ -1300,6 +1472,36 @@ declare const env: _spfn_core_env.InferEnvType<{
|
|
|
1300
1472
|
} & {
|
|
1301
1473
|
key: "CACHE_TLS_REJECT_UNAUTHORIZED";
|
|
1302
1474
|
};
|
|
1475
|
+
CACHE_MAX_RETRIES_PER_REQUEST: {
|
|
1476
|
+
description: string;
|
|
1477
|
+
default: number;
|
|
1478
|
+
examples: number[];
|
|
1479
|
+
} & {
|
|
1480
|
+
type: "number";
|
|
1481
|
+
validator: (value: string) => number;
|
|
1482
|
+
} & {
|
|
1483
|
+
key: "CACHE_MAX_RETRIES_PER_REQUEST";
|
|
1484
|
+
};
|
|
1485
|
+
CACHE_ENABLE_OFFLINE_QUEUE: {
|
|
1486
|
+
description: string;
|
|
1487
|
+
default: boolean;
|
|
1488
|
+
examples: boolean[];
|
|
1489
|
+
} & {
|
|
1490
|
+
type: "boolean";
|
|
1491
|
+
validator: (value: string) => boolean;
|
|
1492
|
+
} & {
|
|
1493
|
+
key: "CACHE_ENABLE_OFFLINE_QUEUE";
|
|
1494
|
+
};
|
|
1495
|
+
DB_MAX_ROWS: {
|
|
1496
|
+
description: string;
|
|
1497
|
+
default: number;
|
|
1498
|
+
examples: number[];
|
|
1499
|
+
} & {
|
|
1500
|
+
type: "number";
|
|
1501
|
+
validator: (value: string) => number;
|
|
1502
|
+
} & {
|
|
1503
|
+
key: "DB_MAX_ROWS";
|
|
1504
|
+
};
|
|
1303
1505
|
PORT: {
|
|
1304
1506
|
description: string;
|
|
1305
1507
|
default: number;
|
|
@@ -1456,6 +1658,17 @@ declare const env: _spfn_core_env.InferEnvType<{
|
|
|
1456
1658
|
} & {
|
|
1457
1659
|
key: "SPFN_PROXY_SECRET_PREVIOUS";
|
|
1458
1660
|
};
|
|
1661
|
+
TRUSTED_PROXY_HOPS: {
|
|
1662
|
+
description: string;
|
|
1663
|
+
default: number;
|
|
1664
|
+
nextjs: boolean;
|
|
1665
|
+
examples: number[];
|
|
1666
|
+
} & {
|
|
1667
|
+
type: "number";
|
|
1668
|
+
validator: (value: string) => number;
|
|
1669
|
+
} & {
|
|
1670
|
+
key: "TRUSTED_PROXY_HOPS";
|
|
1671
|
+
};
|
|
1459
1672
|
}>;
|
|
1460
1673
|
|
|
1461
1674
|
export { env, coreEnvSchema as envSchema, registry };
|
package/dist/config/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineEnvSchema,
|
|
1
|
+
import { defineEnvSchema, envNumber, envString, envUrl, envBoolean, envEnum, parseRedisUrl, parsePostgresUrl, createEnvRegistry } from '@spfn/core/env';
|
|
2
2
|
|
|
3
3
|
// src/config/index.ts
|
|
4
4
|
var coreEnvSchema = defineEnvSchema({
|
|
@@ -42,6 +42,11 @@ var coreEnvSchema = defineEnvSchema({
|
|
|
42
42
|
default: 10,
|
|
43
43
|
examples: [10, 20, 50]
|
|
44
44
|
}),
|
|
45
|
+
DB_POOL_READ_MAX: envNumber({
|
|
46
|
+
description: "Maximum connections for the read-replica pool. Defaults to DB_POOL_MAX. Set lower so write.max + read.max stays under the server max_connections (each process otherwise opens up to 2 \xD7 DB_POOL_MAX when a replica is configured).",
|
|
47
|
+
required: false,
|
|
48
|
+
examples: [5, 10, 20]
|
|
49
|
+
}),
|
|
45
50
|
DB_POOL_IDLE_TIMEOUT: envNumber({
|
|
46
51
|
description: "Database connection idle timeout in seconds",
|
|
47
52
|
default: 30,
|
|
@@ -124,6 +129,19 @@ var coreEnvSchema = defineEnvSchema({
|
|
|
124
129
|
default: 3e4,
|
|
125
130
|
examples: [1e4, 3e4, 6e4]
|
|
126
131
|
}),
|
|
132
|
+
TRANSACTION_IDLE_TIMEOUT: envNumber({
|
|
133
|
+
description: "Max time (ms) a transaction may sit idle (no running query) before Postgres terminates it and reclaims the pooled connection. Guards against external I/O held inside a transaction starving the connection pool. 0 disables.",
|
|
134
|
+
default: 3e4,
|
|
135
|
+
examples: [1e4, 3e4, 0]
|
|
136
|
+
}),
|
|
137
|
+
// ========================================================================
|
|
138
|
+
// Jobs (pg-boss)
|
|
139
|
+
// ========================================================================
|
|
140
|
+
JOB_POLLING_INTERVAL_SECONDS: envNumber({
|
|
141
|
+
description: "How often each pg-boss worker polls the DB for new jobs (seconds). Lower = faster pickup, more idle SELECT load; higher = less DB chatter, slower pickup. Per-job override via job options.",
|
|
142
|
+
default: 2,
|
|
143
|
+
examples: [1, 2, 10]
|
|
144
|
+
}),
|
|
127
145
|
// ========================================================================
|
|
128
146
|
// Database - Development
|
|
129
147
|
// ========================================================================
|
|
@@ -204,6 +222,24 @@ var coreEnvSchema = defineEnvSchema({
|
|
|
204
222
|
default: true,
|
|
205
223
|
examples: [true, false]
|
|
206
224
|
}),
|
|
225
|
+
CACHE_MAX_RETRIES_PER_REQUEST: envNumber({
|
|
226
|
+
description: "Max ioredis retries per command before it rejects (fail fast instead of hanging on a cache outage). ioredis default is 20.",
|
|
227
|
+
default: 3,
|
|
228
|
+
examples: [1, 3, 20]
|
|
229
|
+
}),
|
|
230
|
+
CACHE_ENABLE_OFFLINE_QUEUE: envBoolean({
|
|
231
|
+
description: "Queue commands while the cache is disconnected (true) vs reject immediately for strict fail-fast (false). Default true keeps resilience to brief blips.",
|
|
232
|
+
default: true,
|
|
233
|
+
examples: [true, false]
|
|
234
|
+
}),
|
|
235
|
+
// ========================================================================
|
|
236
|
+
// Database - Query limits
|
|
237
|
+
// ========================================================================
|
|
238
|
+
DB_MAX_ROWS: envNumber({
|
|
239
|
+
description: "Safety ceiling for rows returned by repository findMany (0 = unlimited). When >0, an unbounded query is capped and an explicit limit is clamped, guarding against accidentally loading a whole large table.",
|
|
240
|
+
default: 0,
|
|
241
|
+
examples: [0, 1e3, 1e4]
|
|
242
|
+
}),
|
|
207
243
|
// ========================================================================
|
|
208
244
|
// Server - Core
|
|
209
245
|
// ========================================================================
|
|
@@ -302,6 +338,12 @@ var coreEnvSchema = defineEnvSchema({
|
|
|
302
338
|
sensitive: true,
|
|
303
339
|
nextjs: false,
|
|
304
340
|
examples: ["v1:<old secret>", "v1:<old>,v0:<older>"]
|
|
341
|
+
}),
|
|
342
|
+
TRUSTED_PROXY_HOPS: envNumber({
|
|
343
|
+
description: "Number of trusted reverse proxies in front of the Next.js proxy (e.g. cloud LB + nginx = 2). Read by the proxy to extract the real client IP from the inbound X-Forwarded-For (counting from the right, which your own infra appends and a client cannot spoof) and forward it to the backend for rate limiting. Set it to your actual hop count; too low trusts a client-spoofable entry, too high collapses users behind a shared proxy IP.",
|
|
344
|
+
default: 1,
|
|
345
|
+
nextjs: true,
|
|
346
|
+
examples: [1, 2, 3]
|
|
305
347
|
})
|
|
306
348
|
});
|
|
307
349
|
|
package/dist/config/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/config/schema.ts","../../src/config/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Core Package Environment Variable Schema\n *\n * Centralized schema definition for all environment variables used in @spfn/core.\n * This provides type safety, validation, and documentation for environment configuration.\n *\n * @module config/schema\n */\n\nimport {\n defineEnvSchema,\n envEnum,\n envNumber,\n envBoolean,\n envUrl,\n envString,\n parsePostgresUrl,\n parseRedisUrl,\n} from '@spfn/core/env';\n\n/**\n * Core package environment variable schema\n *\n * Defines all environment variables with:\n * - Type information\n * - Default values\n * - Validation rules\n * - Documentation\n *\n * @example\n * ```typescript\n * import { coreEnvSchema } from '@spfn/core/config';\n *\n * // Access schema information\n * console.log(coreEnvSchema.DB_POOL_MAX.description);\n * console.log(coreEnvSchema.DB_POOL_MAX.default);\n * ```\n */\nexport const coreEnvSchema = defineEnvSchema({\n // ========================================================================\n // Core Environment\n // ========================================================================\n\n NODE_ENV: envEnum(['local', 'development', 'staging', 'production', 'test'] as const, {\n description: 'Node.js runtime environment',\n default: 'local',\n nextjs: true,\n }),\n\n // ========================================================================\n // Database - Connection\n // ========================================================================\n\n DATABASE_URL: envString({\n description: 'Primary database connection URL',\n required: false,\n sensitive: true,\n validator: parsePostgresUrl,\n examples: ['postgresql://user:password@localhost:5432/dbname'],\n }),\n\n DATABASE_WRITE_URL: envString({\n description: 'Write database URL (master-replica pattern)',\n required: false,\n sensitive: true,\n validator: parsePostgresUrl,\n examples: ['postgresql://user:password@master:5432/dbname'],\n }),\n\n DATABASE_READ_URL: envString({\n description: 'Read database URL (master-replica pattern)',\n required: false,\n sensitive: true,\n validator: parsePostgresUrl,\n examples: ['postgresql://user:password@replica:5432/dbname'],\n }),\n\n // ========================================================================\n // Database - Connection Pool\n // ========================================================================\n\n DB_POOL_MAX: envNumber({\n description: 'Maximum number of database connections in pool',\n default: 10,\n examples: [10, 20, 50],\n }),\n\n DB_POOL_IDLE_TIMEOUT: envNumber({\n description: 'Database connection idle timeout in seconds',\n default: 30,\n examples: [20, 30, 60],\n }),\n\n // ========================================================================\n // Database - Retry Configuration\n // ========================================================================\n\n DB_RETRY_MAX: envNumber({\n description: 'Maximum number of database connection retry attempts',\n default: 3,\n examples: [3, 5, 10],\n }),\n\n DB_RETRY_INITIAL_DELAY: envNumber({\n description: 'Initial delay between database retry attempts (milliseconds)',\n default: 100,\n examples: [50, 100, 200],\n }),\n\n DB_RETRY_MAX_DELAY: envNumber({\n description: 'Maximum delay cap for database retry attempts (milliseconds)',\n default: 10000,\n examples: [5000, 10000, 30000],\n }),\n\n DB_RETRY_FACTOR: envNumber({\n description: 'Exponential backoff factor for database retry delays',\n default: 2,\n examples: [2, 1.5, 3],\n }),\n\n // ========================================================================\n // Database - Health Check\n // ========================================================================\n\n DB_HEALTH_CHECK_ENABLED: envBoolean({\n description: 'Enable periodic database health checks',\n default: true,\n examples: [true, false],\n }),\n\n DB_HEALTH_CHECK_INTERVAL: envNumber({\n description: 'Database health check interval (milliseconds)',\n default: 60000,\n examples: [30000, 60000, 120000],\n }),\n\n DB_HEALTH_CHECK_RECONNECT: envBoolean({\n description: 'Reconnect to database on health check failure',\n default: true,\n examples: [true, false],\n }),\n\n DB_HEALTH_CHECK_MAX_RETRIES: envNumber({\n description: 'Maximum health check retry attempts before marking as failed',\n default: 3,\n examples: [3, 5, 10],\n }),\n\n DB_HEALTH_CHECK_RETRY_INTERVAL: envNumber({\n description: 'Interval between health check retry attempts (milliseconds)',\n default: 5000,\n examples: [5000, 10000, 15000],\n }),\n\n // ========================================================================\n // Database - Monitoring\n // ========================================================================\n\n DB_MONITORING_ENABLED: envBoolean({\n description: 'Enable database query performance monitoring',\n default: false,\n examples: [true, false],\n }),\n\n DB_MONITORING_SLOW_THRESHOLD: envNumber({\n description: 'Slow query threshold for monitoring (milliseconds)',\n default: 1000,\n examples: [500, 1000, 2000],\n }),\n\n DB_MONITORING_LOG_QUERIES: envBoolean({\n description: 'Log all database queries (not just slow queries)',\n default: false,\n examples: [true, false],\n }),\n\n // ========================================================================\n // Database - Transaction\n // ========================================================================\n\n TRANSACTION_TIMEOUT: envNumber({\n description: 'Transaction timeout in milliseconds',\n default: 30000,\n examples: [10000, 30000, 60000],\n }),\n\n // ========================================================================\n // Database - Development\n // ========================================================================\n\n DB_DEBUG_TRACE: envBoolean({\n description: 'Enable detailed debug tracing for database operations',\n default: false,\n examples: [true, false],\n }),\n\n // ========================================================================\n // Drizzle ORM\n // ========================================================================\n\n DRIZZLE_SCHEMA_PATH: envString({\n description: 'Path to Drizzle schema configuration',\n required: false,\n default: './src/server/entities/config.ts',\n examples: ['./src/db/schema.ts', './src/server/entities/config.ts'],\n }),\n\n DRIZZLE_OUT_DIR: envString({\n description: 'Output directory for Drizzle migrations',\n required: false,\n default: './drizzle',\n examples: ['./drizzle', './migrations'],\n }),\n\n // ========================================================================\n // Logger - Core\n // ========================================================================\n\n SPFN_LOG_LEVEL: envEnum(['debug', 'info', 'warn', 'error', 'fatal'] as const, {\n description: 'Minimum log level to output',\n default: 'info',\n }),\n\n // ========================================================================\n // Cache (Redis/Valkey)\n // ========================================================================\n\n CACHE_URL: envString({\n description: 'Single Redis/Valkey instance URL',\n required: false,\n sensitive: true,\n validator: parseRedisUrl,\n examples: ['redis://localhost:6379', 'rediss://secure.cache.com:6380'],\n }),\n\n CACHE_WRITE_URL: envString({\n description: 'Master Redis/Valkey URL for writes (master-replica pattern)',\n required: false,\n sensitive: true,\n validator: parseRedisUrl,\n examples: ['redis://master:6379'],\n }),\n\n CACHE_READ_URL: envString({\n description: 'Replica Redis/Valkey URL for reads (master-replica pattern)',\n required: false,\n sensitive: true,\n validator: parseRedisUrl,\n examples: ['redis://replica:6379'],\n }),\n\n CACHE_SENTINEL_HOSTS: envString({\n description: 'Comma-separated Redis Sentinel hosts',\n required: false,\n examples: ['sentinel1:26379,sentinel2:26379'],\n }),\n\n CACHE_CLUSTER_NODES: envString({\n description: 'Comma-separated Redis Cluster nodes',\n required: false,\n examples: ['node1:6379,node2:6379,node3:6379'],\n }),\n\n CACHE_MASTER_NAME: envString({\n description: 'Redis Sentinel master name',\n required: false,\n examples: ['mymaster'],\n }),\n\n CACHE_PASSWORD: envString({\n description: 'Redis/Valkey authentication password',\n required: false,\n sensitive: true,\n examples: ['your-redis-password'],\n }),\n\n CACHE_TLS_REJECT_UNAUTHORIZED: envBoolean({\n description: 'Verify TLS certificates for secure Redis connections',\n default: true,\n examples: [true, false],\n }),\n\n // ========================================================================\n // Server - Core\n // ========================================================================\n\n PORT: envNumber({\n description: 'Server port number',\n default: 4000,\n examples: [3000, 4000, 8080],\n }),\n\n HOST: envString({\n description: 'Server hostname',\n default: 'localhost',\n required: false,\n examples: ['localhost', '0.0.0.0', '127.0.0.1'],\n }),\n\n // ========================================================================\n // Server - Timeout\n // ========================================================================\n\n SERVER_TIMEOUT: envNumber({\n description: 'Request timeout in milliseconds',\n default: 120000,\n examples: [60000, 120000, 300000],\n }),\n\n SERVER_KEEPALIVE_TIMEOUT: envNumber({\n description: 'Keep-alive timeout in milliseconds',\n default: 65000,\n examples: [30000, 65000, 120000],\n }),\n\n SERVER_HEADERS_TIMEOUT: envNumber({\n description: 'Headers timeout in milliseconds',\n default: 60000,\n examples: [30000, 60000, 120000],\n }),\n\n SHUTDOWN_TIMEOUT: envNumber({\n description: 'Graceful shutdown timeout in milliseconds (must be less than k8s terminationGracePeriodSeconds minus preStop sleep, with safety margin)',\n default: 280000,\n examples: [30000, 120000, 280000],\n }),\n\n // ========================================================================\n // Fetch (Node.js undici) - outbound HTTP request timeout\n // ========================================================================\n\n FETCH_CONNECT_TIMEOUT: envNumber({\n description: 'Fetch TCP connection timeout in milliseconds (time to establish socket connection to upstream server)',\n default: 10000,\n examples: [5000, 10000, 30000],\n }),\n\n FETCH_HEADERS_TIMEOUT: envNumber({\n description: 'Fetch headers timeout in milliseconds (time to receive response headers after request sent)',\n default: 300000,\n examples: [120000, 300000, 600000],\n }),\n\n FETCH_BODY_TIMEOUT: envNumber({\n description: 'Fetch body timeout in milliseconds (time between body data chunks from upstream server)',\n default: 300000,\n examples: [120000, 300000, 600000],\n }),\n\n // ========================================================================\n // Next.js Integration\n // ========================================================================\n\n SPFN_API_URL: envUrl({\n description: 'SPFN API URL (used by Next.js to call backend)',\n required: true,\n nextjs: true,\n examples: ['http://localhost:8790', 'https://api.your-app.com'],\n }),\n\n NEXT_PUBLIC_SPFN_API_URL: envUrl({\n description: 'SPFN API URL (used by Next.js to call backend)',\n required: true,\n nextjs: true,\n examples: ['http://localhost:8790', 'https://api.your-app.com'],\n }),\n\n SPFN_APP_URL: envUrl({\n description: 'Next.js application URL (used by SPFN server)',\n required: false,\n nextjs: true,\n examples: ['http://localhost:3790', 'https://your-app.com'],\n }),\n\n RPC_PROXY_TIMEOUT: envNumber({\n description: 'RPC proxy request timeout in milliseconds (AbortController timeout for proxied requests to backend, should be shorter than FETCH_HEADERS_TIMEOUT)',\n default: 120000,\n nextjs: true,\n examples: [60000, 120000, 280000],\n }),\n\n // ========================================================================\n // Proxy → Backend trust (HMAC signing)\n // ========================================================================\n\n SPFN_PROXY_SECRET: envString({\n description: 'Shared secret for signing proxy→backend requests (HMAC-SHA256). Read by BOTH processes — the Next.js proxy (to sign) and the SPFN backend (to verify) — so it belongs in .env.local (loaded by both; the backend reads it via loadEnv, Next.js reads it server-side without exposing it to the browser). Set the SAME value on both. Leave unset to disable proxy-guard signing.',\n required: false,\n sensitive: true,\n nextjs: true,\n examples: ['<32+ byte random hex>', 'v2:<32+ byte random hex>'],\n }),\n\n SPFN_PROXY_SECRET_PREVIOUS: envString({\n description: 'Previous (grace) proxy keys still accepted for verification during rotation — comma-separated <keyId>:<secret>. The proxy never signs with these; they only keep requests signed with the prior key verifying until a rollout settles. Backend-only (verification), so it belongs in .env.server, NOT exposed to the Next.js process.',\n required: false,\n sensitive: true,\n nextjs: false,\n examples: ['v1:<old secret>', 'v1:<old>,v0:<older>'],\n }),\n});\n","/**\n * Core Package Configuration\n *\n * @example\n * ```typescript\n * import { registry } from '@spfn/core/config';\n *\n * const env = registry.validate();\n * console.log(env.DB_POOL_MAX);\n * ```\n *\n * @module config\n */\n\nimport { createEnvRegistry } from '@spfn/core/env';\nimport { coreEnvSchema } from './schema';\n\n/**\n * Core environment schema\n */\nexport { coreEnvSchema as envSchema } from './schema';\n\n/**\n * Environment registry\n *\n * @example\n * ```typescript\n * // Reset for testing\n * registry.reset();\n * ```\n */\nexport const registry = createEnvRegistry(coreEnvSchema);\n\n/**\n * Validated environment configuration\n */\nexport const env = registry.validate();\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/config/schema.ts","../../src/config/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Core Package Environment Variable Schema\n *\n * Centralized schema definition for all environment variables used in @spfn/core.\n * This provides type safety, validation, and documentation for environment configuration.\n *\n * @module config/schema\n */\n\nimport {\n defineEnvSchema,\n envEnum,\n envNumber,\n envBoolean,\n envUrl,\n envString,\n parsePostgresUrl,\n parseRedisUrl,\n} from '@spfn/core/env';\n\n/**\n * Core package environment variable schema\n *\n * Defines all environment variables with:\n * - Type information\n * - Default values\n * - Validation rules\n * - Documentation\n *\n * @example\n * ```typescript\n * import { coreEnvSchema } from '@spfn/core/config';\n *\n * // Access schema information\n * console.log(coreEnvSchema.DB_POOL_MAX.description);\n * console.log(coreEnvSchema.DB_POOL_MAX.default);\n * ```\n */\nexport const coreEnvSchema = defineEnvSchema({\n // ========================================================================\n // Core Environment\n // ========================================================================\n\n NODE_ENV: envEnum(['local', 'development', 'staging', 'production', 'test'] as const, {\n description: 'Node.js runtime environment',\n default: 'local',\n nextjs: true,\n }),\n\n // ========================================================================\n // Database - Connection\n // ========================================================================\n\n DATABASE_URL: envString({\n description: 'Primary database connection URL',\n required: false,\n sensitive: true,\n validator: parsePostgresUrl,\n examples: ['postgresql://user:password@localhost:5432/dbname'],\n }),\n\n DATABASE_WRITE_URL: envString({\n description: 'Write database URL (master-replica pattern)',\n required: false,\n sensitive: true,\n validator: parsePostgresUrl,\n examples: ['postgresql://user:password@master:5432/dbname'],\n }),\n\n DATABASE_READ_URL: envString({\n description: 'Read database URL (master-replica pattern)',\n required: false,\n sensitive: true,\n validator: parsePostgresUrl,\n examples: ['postgresql://user:password@replica:5432/dbname'],\n }),\n\n // ========================================================================\n // Database - Connection Pool\n // ========================================================================\n\n DB_POOL_MAX: envNumber({\n description: 'Maximum number of database connections in pool',\n default: 10,\n examples: [10, 20, 50],\n }),\n\n DB_POOL_READ_MAX: envNumber({\n description: 'Maximum connections for the read-replica pool. Defaults to DB_POOL_MAX. Set lower so write.max + read.max stays under the server max_connections (each process otherwise opens up to 2 × DB_POOL_MAX when a replica is configured).',\n required: false,\n examples: [5, 10, 20],\n }),\n\n DB_POOL_IDLE_TIMEOUT: envNumber({\n description: 'Database connection idle timeout in seconds',\n default: 30,\n examples: [20, 30, 60],\n }),\n\n // ========================================================================\n // Database - Retry Configuration\n // ========================================================================\n\n DB_RETRY_MAX: envNumber({\n description: 'Maximum number of database connection retry attempts',\n default: 3,\n examples: [3, 5, 10],\n }),\n\n DB_RETRY_INITIAL_DELAY: envNumber({\n description: 'Initial delay between database retry attempts (milliseconds)',\n default: 100,\n examples: [50, 100, 200],\n }),\n\n DB_RETRY_MAX_DELAY: envNumber({\n description: 'Maximum delay cap for database retry attempts (milliseconds)',\n default: 10000,\n examples: [5000, 10000, 30000],\n }),\n\n DB_RETRY_FACTOR: envNumber({\n description: 'Exponential backoff factor for database retry delays',\n default: 2,\n examples: [2, 1.5, 3],\n }),\n\n // ========================================================================\n // Database - Health Check\n // ========================================================================\n\n DB_HEALTH_CHECK_ENABLED: envBoolean({\n description: 'Enable periodic database health checks',\n default: true,\n examples: [true, false],\n }),\n\n DB_HEALTH_CHECK_INTERVAL: envNumber({\n description: 'Database health check interval (milliseconds)',\n default: 60000,\n examples: [30000, 60000, 120000],\n }),\n\n DB_HEALTH_CHECK_RECONNECT: envBoolean({\n description: 'Reconnect to database on health check failure',\n default: true,\n examples: [true, false],\n }),\n\n DB_HEALTH_CHECK_MAX_RETRIES: envNumber({\n description: 'Maximum health check retry attempts before marking as failed',\n default: 3,\n examples: [3, 5, 10],\n }),\n\n DB_HEALTH_CHECK_RETRY_INTERVAL: envNumber({\n description: 'Interval between health check retry attempts (milliseconds)',\n default: 5000,\n examples: [5000, 10000, 15000],\n }),\n\n // ========================================================================\n // Database - Monitoring\n // ========================================================================\n\n DB_MONITORING_ENABLED: envBoolean({\n description: 'Enable database query performance monitoring',\n default: false,\n examples: [true, false],\n }),\n\n DB_MONITORING_SLOW_THRESHOLD: envNumber({\n description: 'Slow query threshold for monitoring (milliseconds)',\n default: 1000,\n examples: [500, 1000, 2000],\n }),\n\n DB_MONITORING_LOG_QUERIES: envBoolean({\n description: 'Log all database queries (not just slow queries)',\n default: false,\n examples: [true, false],\n }),\n\n // ========================================================================\n // Database - Transaction\n // ========================================================================\n\n TRANSACTION_TIMEOUT: envNumber({\n description: 'Transaction timeout in milliseconds',\n default: 30000,\n examples: [10000, 30000, 60000],\n }),\n\n TRANSACTION_IDLE_TIMEOUT: envNumber({\n description: 'Max time (ms) a transaction may sit idle (no running query) before Postgres terminates it and reclaims the pooled connection. Guards against external I/O held inside a transaction starving the connection pool. 0 disables.',\n default: 30000,\n examples: [10000, 30000, 0],\n }),\n\n // ========================================================================\n // Jobs (pg-boss)\n // ========================================================================\n\n JOB_POLLING_INTERVAL_SECONDS: envNumber({\n description: 'How often each pg-boss worker polls the DB for new jobs (seconds). Lower = faster pickup, more idle SELECT load; higher = less DB chatter, slower pickup. Per-job override via job options.',\n default: 2,\n examples: [1, 2, 10],\n }),\n\n // ========================================================================\n // Database - Development\n // ========================================================================\n\n DB_DEBUG_TRACE: envBoolean({\n description: 'Enable detailed debug tracing for database operations',\n default: false,\n examples: [true, false],\n }),\n\n // ========================================================================\n // Drizzle ORM\n // ========================================================================\n\n DRIZZLE_SCHEMA_PATH: envString({\n description: 'Path to Drizzle schema configuration',\n required: false,\n default: './src/server/entities/config.ts',\n examples: ['./src/db/schema.ts', './src/server/entities/config.ts'],\n }),\n\n DRIZZLE_OUT_DIR: envString({\n description: 'Output directory for Drizzle migrations',\n required: false,\n default: './drizzle',\n examples: ['./drizzle', './migrations'],\n }),\n\n // ========================================================================\n // Logger - Core\n // ========================================================================\n\n SPFN_LOG_LEVEL: envEnum(['debug', 'info', 'warn', 'error', 'fatal'] as const, {\n description: 'Minimum log level to output',\n default: 'info',\n }),\n\n // ========================================================================\n // Cache (Redis/Valkey)\n // ========================================================================\n\n CACHE_URL: envString({\n description: 'Single Redis/Valkey instance URL',\n required: false,\n sensitive: true,\n validator: parseRedisUrl,\n examples: ['redis://localhost:6379', 'rediss://secure.cache.com:6380'],\n }),\n\n CACHE_WRITE_URL: envString({\n description: 'Master Redis/Valkey URL for writes (master-replica pattern)',\n required: false,\n sensitive: true,\n validator: parseRedisUrl,\n examples: ['redis://master:6379'],\n }),\n\n CACHE_READ_URL: envString({\n description: 'Replica Redis/Valkey URL for reads (master-replica pattern)',\n required: false,\n sensitive: true,\n validator: parseRedisUrl,\n examples: ['redis://replica:6379'],\n }),\n\n CACHE_SENTINEL_HOSTS: envString({\n description: 'Comma-separated Redis Sentinel hosts',\n required: false,\n examples: ['sentinel1:26379,sentinel2:26379'],\n }),\n\n CACHE_CLUSTER_NODES: envString({\n description: 'Comma-separated Redis Cluster nodes',\n required: false,\n examples: ['node1:6379,node2:6379,node3:6379'],\n }),\n\n CACHE_MASTER_NAME: envString({\n description: 'Redis Sentinel master name',\n required: false,\n examples: ['mymaster'],\n }),\n\n CACHE_PASSWORD: envString({\n description: 'Redis/Valkey authentication password',\n required: false,\n sensitive: true,\n examples: ['your-redis-password'],\n }),\n\n CACHE_TLS_REJECT_UNAUTHORIZED: envBoolean({\n description: 'Verify TLS certificates for secure Redis connections',\n default: true,\n examples: [true, false],\n }),\n\n CACHE_MAX_RETRIES_PER_REQUEST: envNumber({\n description: 'Max ioredis retries per command before it rejects (fail fast instead of hanging on a cache outage). ioredis default is 20.',\n default: 3,\n examples: [1, 3, 20],\n }),\n\n CACHE_ENABLE_OFFLINE_QUEUE: envBoolean({\n description: 'Queue commands while the cache is disconnected (true) vs reject immediately for strict fail-fast (false). Default true keeps resilience to brief blips.',\n default: true,\n examples: [true, false],\n }),\n\n // ========================================================================\n // Database - Query limits\n // ========================================================================\n\n DB_MAX_ROWS: envNumber({\n description: 'Safety ceiling for rows returned by repository findMany (0 = unlimited). When >0, an unbounded query is capped and an explicit limit is clamped, guarding against accidentally loading a whole large table.',\n default: 0,\n examples: [0, 1000, 10000],\n }),\n\n // ========================================================================\n // Server - Core\n // ========================================================================\n\n PORT: envNumber({\n description: 'Server port number',\n default: 4000,\n examples: [3000, 4000, 8080],\n }),\n\n HOST: envString({\n description: 'Server hostname',\n default: 'localhost',\n required: false,\n examples: ['localhost', '0.0.0.0', '127.0.0.1'],\n }),\n\n // ========================================================================\n // Server - Timeout\n // ========================================================================\n\n SERVER_TIMEOUT: envNumber({\n description: 'Request timeout in milliseconds',\n default: 120000,\n examples: [60000, 120000, 300000],\n }),\n\n SERVER_KEEPALIVE_TIMEOUT: envNumber({\n description: 'Keep-alive timeout in milliseconds',\n default: 65000,\n examples: [30000, 65000, 120000],\n }),\n\n SERVER_HEADERS_TIMEOUT: envNumber({\n description: 'Headers timeout in milliseconds',\n default: 60000,\n examples: [30000, 60000, 120000],\n }),\n\n SHUTDOWN_TIMEOUT: envNumber({\n description: 'Graceful shutdown timeout in milliseconds (must be less than k8s terminationGracePeriodSeconds minus preStop sleep, with safety margin)',\n default: 280000,\n examples: [30000, 120000, 280000],\n }),\n\n // ========================================================================\n // Fetch (Node.js undici) - outbound HTTP request timeout\n // ========================================================================\n\n FETCH_CONNECT_TIMEOUT: envNumber({\n description: 'Fetch TCP connection timeout in milliseconds (time to establish socket connection to upstream server)',\n default: 10000,\n examples: [5000, 10000, 30000],\n }),\n\n FETCH_HEADERS_TIMEOUT: envNumber({\n description: 'Fetch headers timeout in milliseconds (time to receive response headers after request sent)',\n default: 300000,\n examples: [120000, 300000, 600000],\n }),\n\n FETCH_BODY_TIMEOUT: envNumber({\n description: 'Fetch body timeout in milliseconds (time between body data chunks from upstream server)',\n default: 300000,\n examples: [120000, 300000, 600000],\n }),\n\n // ========================================================================\n // Next.js Integration\n // ========================================================================\n\n SPFN_API_URL: envUrl({\n description: 'SPFN API URL (used by Next.js to call backend)',\n required: true,\n nextjs: true,\n examples: ['http://localhost:8790', 'https://api.your-app.com'],\n }),\n\n NEXT_PUBLIC_SPFN_API_URL: envUrl({\n description: 'SPFN API URL (used by Next.js to call backend)',\n required: true,\n nextjs: true,\n examples: ['http://localhost:8790', 'https://api.your-app.com'],\n }),\n\n SPFN_APP_URL: envUrl({\n description: 'Next.js application URL (used by SPFN server)',\n required: false,\n nextjs: true,\n examples: ['http://localhost:3790', 'https://your-app.com'],\n }),\n\n RPC_PROXY_TIMEOUT: envNumber({\n description: 'RPC proxy request timeout in milliseconds (AbortController timeout for proxied requests to backend, should be shorter than FETCH_HEADERS_TIMEOUT)',\n default: 120000,\n nextjs: true,\n examples: [60000, 120000, 280000],\n }),\n\n // ========================================================================\n // Proxy → Backend trust (HMAC signing)\n // ========================================================================\n\n SPFN_PROXY_SECRET: envString({\n description: 'Shared secret for signing proxy→backend requests (HMAC-SHA256). Read by BOTH processes — the Next.js proxy (to sign) and the SPFN backend (to verify) — so it belongs in .env.local (loaded by both; the backend reads it via loadEnv, Next.js reads it server-side without exposing it to the browser). Set the SAME value on both. Leave unset to disable proxy-guard signing.',\n required: false,\n sensitive: true,\n nextjs: true,\n examples: ['<32+ byte random hex>', 'v2:<32+ byte random hex>'],\n }),\n\n SPFN_PROXY_SECRET_PREVIOUS: envString({\n description: 'Previous (grace) proxy keys still accepted for verification during rotation — comma-separated <keyId>:<secret>. The proxy never signs with these; they only keep requests signed with the prior key verifying until a rollout settles. Backend-only (verification), so it belongs in .env.server, NOT exposed to the Next.js process.',\n required: false,\n sensitive: true,\n nextjs: false,\n examples: ['v1:<old secret>', 'v1:<old>,v0:<older>'],\n }),\n\n TRUSTED_PROXY_HOPS: envNumber({\n description: 'Number of trusted reverse proxies in front of the Next.js proxy (e.g. cloud LB + nginx = 2). Read by the proxy to extract the real client IP from the inbound X-Forwarded-For (counting from the right, which your own infra appends and a client cannot spoof) and forward it to the backend for rate limiting. Set it to your actual hop count; too low trusts a client-spoofable entry, too high collapses users behind a shared proxy IP.',\n default: 1,\n nextjs: true,\n examples: [1, 2, 3],\n }),\n});\n","/**\n * Core Package Configuration\n *\n * @example\n * ```typescript\n * import { registry } from '@spfn/core/config';\n *\n * const env = registry.validate();\n * console.log(env.DB_POOL_MAX);\n * ```\n *\n * @module config\n */\n\nimport { createEnvRegistry } from '@spfn/core/env';\nimport { coreEnvSchema } from './schema';\n\n/**\n * Core environment schema\n */\nexport { coreEnvSchema as envSchema } from './schema';\n\n/**\n * Environment registry\n *\n * @example\n * ```typescript\n * // Reset for testing\n * registry.reset();\n * ```\n */\nexport const registry = createEnvRegistry(coreEnvSchema);\n\n/**\n * Validated environment configuration\n */\nexport const env = registry.validate();\n"]}
|