@spfn/core 0.2.0-beta.52 → 0.2.0-beta.54
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 +285 -0
- package/dist/config/index.js +60 -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 +171 -2
- package/dist/middleware/index.js +1186 -10
- package/dist/middleware/index.js.map +1 -1
- package/dist/nextjs/server.d.ts +10 -0
- package/dist/nextjs/server.js +115 -1
- package/dist/nextjs/server.js.map +1 -1
- package/dist/route/index.d.ts +3 -165
- package/dist/server/index.d.ts +31 -5
- package/dist/server/index.js +172 -36
- 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;
|
|
@@ -469,6 +529,41 @@ declare const coreEnvSchema: {
|
|
|
469
529
|
} & {
|
|
470
530
|
key: "RPC_PROXY_TIMEOUT";
|
|
471
531
|
};
|
|
532
|
+
SPFN_PROXY_SECRET: {
|
|
533
|
+
description: string;
|
|
534
|
+
required: boolean;
|
|
535
|
+
sensitive: boolean;
|
|
536
|
+
nextjs: boolean;
|
|
537
|
+
examples: string[];
|
|
538
|
+
} & {
|
|
539
|
+
type: "string";
|
|
540
|
+
validator: (value: string) => string;
|
|
541
|
+
} & {
|
|
542
|
+
key: "SPFN_PROXY_SECRET";
|
|
543
|
+
};
|
|
544
|
+
SPFN_PROXY_SECRET_PREVIOUS: {
|
|
545
|
+
description: string;
|
|
546
|
+
required: boolean;
|
|
547
|
+
sensitive: boolean;
|
|
548
|
+
nextjs: boolean;
|
|
549
|
+
examples: string[];
|
|
550
|
+
} & {
|
|
551
|
+
type: "string";
|
|
552
|
+
validator: (value: string) => string;
|
|
553
|
+
} & {
|
|
554
|
+
key: "SPFN_PROXY_SECRET_PREVIOUS";
|
|
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
|
+
};
|
|
472
567
|
};
|
|
473
568
|
|
|
474
569
|
/**
|
|
@@ -533,6 +628,16 @@ declare const registry: _spfn_core_env.EnvRegistry<{
|
|
|
533
628
|
} & {
|
|
534
629
|
key: "DB_POOL_MAX";
|
|
535
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
|
+
};
|
|
536
641
|
DB_POOL_IDLE_TIMEOUT: {
|
|
537
642
|
description: string;
|
|
538
643
|
default: number;
|
|
@@ -673,6 +778,26 @@ declare const registry: _spfn_core_env.EnvRegistry<{
|
|
|
673
778
|
} & {
|
|
674
779
|
key: "TRANSACTION_TIMEOUT";
|
|
675
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
|
+
};
|
|
676
801
|
DB_DEBUG_TRACE: {
|
|
677
802
|
description: string;
|
|
678
803
|
default: boolean;
|
|
@@ -798,6 +923,36 @@ declare const registry: _spfn_core_env.EnvRegistry<{
|
|
|
798
923
|
} & {
|
|
799
924
|
key: "CACHE_TLS_REJECT_UNAUTHORIZED";
|
|
800
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
|
+
};
|
|
801
956
|
PORT: {
|
|
802
957
|
description: string;
|
|
803
958
|
default: number;
|
|
@@ -930,6 +1085,41 @@ declare const registry: _spfn_core_env.EnvRegistry<{
|
|
|
930
1085
|
} & {
|
|
931
1086
|
key: "RPC_PROXY_TIMEOUT";
|
|
932
1087
|
};
|
|
1088
|
+
SPFN_PROXY_SECRET: {
|
|
1089
|
+
description: string;
|
|
1090
|
+
required: boolean;
|
|
1091
|
+
sensitive: boolean;
|
|
1092
|
+
nextjs: boolean;
|
|
1093
|
+
examples: string[];
|
|
1094
|
+
} & {
|
|
1095
|
+
type: "string";
|
|
1096
|
+
validator: (value: string) => string;
|
|
1097
|
+
} & {
|
|
1098
|
+
key: "SPFN_PROXY_SECRET";
|
|
1099
|
+
};
|
|
1100
|
+
SPFN_PROXY_SECRET_PREVIOUS: {
|
|
1101
|
+
description: string;
|
|
1102
|
+
required: boolean;
|
|
1103
|
+
sensitive: boolean;
|
|
1104
|
+
nextjs: boolean;
|
|
1105
|
+
examples: string[];
|
|
1106
|
+
} & {
|
|
1107
|
+
type: "string";
|
|
1108
|
+
validator: (value: string) => string;
|
|
1109
|
+
} & {
|
|
1110
|
+
key: "SPFN_PROXY_SECRET_PREVIOUS";
|
|
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
|
+
};
|
|
933
1123
|
}>;
|
|
934
1124
|
/**
|
|
935
1125
|
* Validated environment configuration
|
|
@@ -987,6 +1177,16 @@ declare const env: _spfn_core_env.InferEnvType<{
|
|
|
987
1177
|
} & {
|
|
988
1178
|
key: "DB_POOL_MAX";
|
|
989
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
|
+
};
|
|
990
1190
|
DB_POOL_IDLE_TIMEOUT: {
|
|
991
1191
|
description: string;
|
|
992
1192
|
default: number;
|
|
@@ -1127,6 +1327,26 @@ declare const env: _spfn_core_env.InferEnvType<{
|
|
|
1127
1327
|
} & {
|
|
1128
1328
|
key: "TRANSACTION_TIMEOUT";
|
|
1129
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
|
+
};
|
|
1130
1350
|
DB_DEBUG_TRACE: {
|
|
1131
1351
|
description: string;
|
|
1132
1352
|
default: boolean;
|
|
@@ -1252,6 +1472,36 @@ declare const env: _spfn_core_env.InferEnvType<{
|
|
|
1252
1472
|
} & {
|
|
1253
1473
|
key: "CACHE_TLS_REJECT_UNAUTHORIZED";
|
|
1254
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
|
+
};
|
|
1255
1505
|
PORT: {
|
|
1256
1506
|
description: string;
|
|
1257
1507
|
default: number;
|
|
@@ -1384,6 +1634,41 @@ declare const env: _spfn_core_env.InferEnvType<{
|
|
|
1384
1634
|
} & {
|
|
1385
1635
|
key: "RPC_PROXY_TIMEOUT";
|
|
1386
1636
|
};
|
|
1637
|
+
SPFN_PROXY_SECRET: {
|
|
1638
|
+
description: string;
|
|
1639
|
+
required: boolean;
|
|
1640
|
+
sensitive: boolean;
|
|
1641
|
+
nextjs: boolean;
|
|
1642
|
+
examples: string[];
|
|
1643
|
+
} & {
|
|
1644
|
+
type: "string";
|
|
1645
|
+
validator: (value: string) => string;
|
|
1646
|
+
} & {
|
|
1647
|
+
key: "SPFN_PROXY_SECRET";
|
|
1648
|
+
};
|
|
1649
|
+
SPFN_PROXY_SECRET_PREVIOUS: {
|
|
1650
|
+
description: string;
|
|
1651
|
+
required: boolean;
|
|
1652
|
+
sensitive: boolean;
|
|
1653
|
+
nextjs: boolean;
|
|
1654
|
+
examples: string[];
|
|
1655
|
+
} & {
|
|
1656
|
+
type: "string";
|
|
1657
|
+
validator: (value: string) => string;
|
|
1658
|
+
} & {
|
|
1659
|
+
key: "SPFN_PROXY_SECRET_PREVIOUS";
|
|
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
|
+
};
|
|
1387
1672
|
}>;
|
|
1388
1673
|
|
|
1389
1674
|
export { env, coreEnvSchema as envSchema, registry };
|
package/dist/config/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineEnvSchema, envNumber,
|
|
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
|
// ========================================================================
|
|
@@ -285,6 +321,29 @@ var coreEnvSchema = defineEnvSchema({
|
|
|
285
321
|
default: 12e4,
|
|
286
322
|
nextjs: true,
|
|
287
323
|
examples: [6e4, 12e4, 28e4]
|
|
324
|
+
}),
|
|
325
|
+
// ========================================================================
|
|
326
|
+
// Proxy → Backend trust (HMAC signing)
|
|
327
|
+
// ========================================================================
|
|
328
|
+
SPFN_PROXY_SECRET: envString({
|
|
329
|
+
description: "Shared secret for signing proxy\u2192backend requests (HMAC-SHA256). Read by BOTH processes \u2014 the Next.js proxy (to sign) and the SPFN backend (to verify) \u2014 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.",
|
|
330
|
+
required: false,
|
|
331
|
+
sensitive: true,
|
|
332
|
+
nextjs: true,
|
|
333
|
+
examples: ["<32+ byte random hex>", "v2:<32+ byte random hex>"]
|
|
334
|
+
}),
|
|
335
|
+
SPFN_PROXY_SECRET_PREVIOUS: envString({
|
|
336
|
+
description: "Previous (grace) proxy keys still accepted for verification during rotation \u2014 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.",
|
|
337
|
+
required: false,
|
|
338
|
+
sensitive: true,
|
|
339
|
+
nextjs: false,
|
|
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]
|
|
288
347
|
})
|
|
289
348
|
});
|
|
290
349
|
|