@spfn/core 0.2.0-beta.8 → 0.3.0-beta.1

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.
Files changed (95) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +444 -305
  3. package/dist/authz/index.d.ts +34 -0
  4. package/dist/authz/index.js +810 -0
  5. package/dist/authz/index.js.map +1 -0
  6. package/dist/{boss-DI1r4kTS.d.ts → boss-D16fO2oG.d.ts} +41 -1
  7. package/dist/cache/index.js +42 -30
  8. package/dist/cache/index.js.map +1 -1
  9. package/dist/codegen/index.d.ts +121 -13
  10. package/dist/codegen/index.js +212 -15
  11. package/dist/codegen/index.js.map +1 -1
  12. package/dist/config/index.d.ts +615 -6
  13. package/dist/config/index.js +124 -5
  14. package/dist/config/index.js.map +1 -1
  15. package/dist/contract/index.d.ts +220 -0
  16. package/dist/contract/index.js +558 -0
  17. package/dist/contract/index.js.map +1 -0
  18. package/dist/db/index.d.ts +528 -85
  19. package/dist/db/index.js +831 -122
  20. package/dist/db/index.js.map +1 -1
  21. package/dist/define-middleware-DfDP39Nq.d.ts +167 -0
  22. package/dist/env/index.d.ts +26 -2
  23. package/dist/env/index.js +15 -5
  24. package/dist/env/index.js.map +1 -1
  25. package/dist/env/loader.d.ts +26 -19
  26. package/dist/env/loader.js +32 -25
  27. package/dist/env/loader.js.map +1 -1
  28. package/dist/errors/index.d.ts +10 -0
  29. package/dist/errors/index.js +418 -5
  30. package/dist/errors/index.js.map +1 -1
  31. package/dist/event/index.d.ts +33 -3
  32. package/dist/event/index.js +24 -3
  33. package/dist/event/index.js.map +1 -1
  34. package/dist/event/sse/client.d.ts +42 -3
  35. package/dist/event/sse/client.js +128 -45
  36. package/dist/event/sse/client.js.map +1 -1
  37. package/dist/event/sse/index.d.ts +12 -5
  38. package/dist/event/sse/index.js +280 -32
  39. package/dist/event/sse/index.js.map +1 -1
  40. package/dist/event/ws/client.d.ts +59 -0
  41. package/dist/event/ws/client.js +273 -0
  42. package/dist/event/ws/client.js.map +1 -0
  43. package/dist/event/ws/index.d.ts +94 -0
  44. package/dist/event/ws/index.js +272 -0
  45. package/dist/event/ws/index.js.map +1 -0
  46. package/dist/job/index.d.ts +2 -2
  47. package/dist/job/index.js +155 -42
  48. package/dist/job/index.js.map +1 -1
  49. package/dist/logger/index.d.ts +5 -0
  50. package/dist/logger/index.js +14 -0
  51. package/dist/logger/index.js.map +1 -1
  52. package/dist/middleware/index.d.ts +347 -9
  53. package/dist/middleware/index.js +1462 -15
  54. package/dist/middleware/index.js.map +1 -1
  55. package/dist/nextjs/index.d.ts +2 -2
  56. package/dist/nextjs/index.js +42 -28
  57. package/dist/nextjs/index.js.map +1 -1
  58. package/dist/nextjs/server.d.ts +35 -51
  59. package/dist/nextjs/server.js +126 -60
  60. package/dist/nextjs/server.js.map +1 -1
  61. package/dist/ops/index.d.ts +107 -0
  62. package/dist/ops/index.js +476 -0
  63. package/dist/ops/index.js.map +1 -0
  64. package/dist/route/index.d.ts +8 -694
  65. package/dist/route/index.js +111 -22
  66. package/dist/route/index.js.map +1 -1
  67. package/dist/router-ukNdAZcN.d.ts +676 -0
  68. package/dist/security/index.d.ts +83 -0
  69. package/dist/security/index.js +173 -0
  70. package/dist/security/index.js.map +1 -0
  71. package/dist/server/index.d.ts +491 -22
  72. package/dist/server/index.js +1887 -308
  73. package/dist/server/index.js.map +1 -1
  74. package/dist/token-manager-BT5EnUAR.d.ts +278 -0
  75. package/dist/types-2AbaW4Ie.d.ts +205 -0
  76. package/dist/{types-BOPTApC2.d.ts → types-9oszaJqp.d.ts} +7 -2
  77. package/dist/types-Bvvig_tT.d.ts +115 -0
  78. package/dist/types-ZQODsBft.d.ts +282 -0
  79. package/package.json +244 -208
  80. package/dist/router-Di7ENoah.d.ts +0 -151
  81. package/dist/types-B-e_f2dQ.d.ts +0 -121
  82. package/docs/cache.md +0 -133
  83. package/docs/codegen.md +0 -74
  84. package/docs/database.md +0 -346
  85. package/docs/entity.md +0 -539
  86. package/docs/env.md +0 -477
  87. package/docs/errors.md +0 -319
  88. package/docs/event.md +0 -116
  89. package/docs/job.md +0 -131
  90. package/docs/logger.md +0 -108
  91. package/docs/middleware.md +0 -337
  92. package/docs/nextjs.md +0 -241
  93. package/docs/repository.md +0 -496
  94. package/docs/route.md +0 -497
  95. package/docs/server.md +0 -307
@@ -1,17 +1,27 @@
1
1
  /**
2
2
  * Environment Variable Loader
3
3
  *
4
- * Next.js 스타일의 환경변수 파일 로딩
4
+ * Next.js 스타일의 환경변수 파일 로딩 (환경별 분리 지원)
5
+ *
6
+ * 로딩 우선순위 (낮음 -> 높음, 나중 파일이 덮어씀):
7
+ * 1. .env - 기본값 (committed)
8
+ * 2. .env.{NODE_ENV} - 환경별 오버라이드 (committed)
9
+ * 3. .env.local - 로컬 오버라이드 (gitignored, test에서 스킵)
10
+ * 4. .env.{NODE_ENV}.local - 환경별 시크릿 (gitignored)
11
+ * 5. .env.server - 서버 전용 (gitignored, Next.js 미로드)
5
12
  *
6
13
  * @example
7
14
  * ```typescript
8
15
  * import { loadEnv } from '@spfn/core/env/loader';
9
16
  *
10
- * // SPFN 서버 진입점에서 호출
17
+ * // 기본 사용 (NODE_ENV 자동 감지)
11
18
  * loadEnv();
12
19
  *
13
- * // 이후 스키마 검증
14
- * const env = createEnvRegistry(envSchema).validate();
20
+ * // 특정 환경 지정
21
+ * loadEnv({ nodeEnv: 'production' });
22
+ *
23
+ * // 서버 레이어 제외 (Next.js 클라이언트용)
24
+ * loadEnv({ server: false });
15
25
  * ```
16
26
  *
17
27
  * @module env/loader
@@ -25,6 +35,16 @@ interface LoadEnvOptions {
25
35
  * @default process.cwd()
26
36
  */
27
37
  cwd?: string;
38
+ /**
39
+ * NODE_ENV 값 (환경별 .env 파일 결정)
40
+ * @default process.env.NODE_ENV || 'local'
41
+ */
42
+ nodeEnv?: string;
43
+ /**
44
+ * 서버 전용 파일 포함 여부 (.env.server)
45
+ * @default true
46
+ */
47
+ server?: boolean;
28
48
  /**
29
49
  * 디버그 모드 (로드된 파일 로깅)
30
50
  * @default false
@@ -52,24 +72,11 @@ interface LoadEnvResult {
52
72
  /**
53
73
  * 프로젝트 루트의 환경변수 파일들을 규칙에 따라 로드
54
74
  *
55
- * Next.js 스타일의 우선순위를 따름:
56
- * - .env .env.local .env.server .env.server.local
57
- * - 나중에 로드된 값이 이전 값을 덮어씀
75
+ * 모든 파일을 파싱 후 머지한 뒤 process.env에 한번에 적용.
76
+ * 이미 process.env 존재하는 키는 덮어쓰지 않음 (플랫폼 주입 보호).
58
77
  *
59
78
  * @param options - 로드 옵션
60
79
  * @returns 로드 결과 (로드된 파일, 키 목록)
61
- *
62
- * @example
63
- * ```typescript
64
- * // 기본 사용
65
- * loadEnv();
66
- *
67
- * // 커스텀 경로
68
- * loadEnv({ cwd: '/path/to/project' });
69
- *
70
- * // 디버그 모드
71
- * loadEnv({ debug: true });
72
- * ```
73
80
  */
74
81
  declare function loadEnv(options?: LoadEnvOptions): LoadEnvResult;
75
82
  /**
@@ -5,58 +5,65 @@ import { logger } from '@spfn/core/logger';
5
5
 
6
6
  // src/env/loader.ts
7
7
  var envLogger = logger.child("@spfn/core:env-loader");
8
- var ENV_FILES = [
9
- ".env",
10
- ".env.local",
11
- ".env.server",
12
- ".env.server.local"
13
- ];
8
+ function getEnvFiles(nodeEnv, server) {
9
+ const files = [
10
+ ".env",
11
+ `.env.${nodeEnv}`
12
+ ];
13
+ if (nodeEnv !== "test") {
14
+ files.push(".env.local");
15
+ }
16
+ files.push(`.env.${nodeEnv}.local`);
17
+ if (server) {
18
+ files.push(".env.server");
19
+ }
20
+ return files;
21
+ }
14
22
  function parseEnvFile(filePath) {
15
23
  if (!existsSync(filePath)) {
16
24
  return null;
17
25
  }
18
- const content = readFileSync(filePath, "utf-8");
19
- return parse(content);
26
+ return parse(readFileSync(filePath, "utf-8"));
20
27
  }
21
28
  function loadEnv(options = {}) {
22
29
  const {
23
30
  cwd = process.cwd(),
31
+ nodeEnv = process.env.NODE_ENV || "local",
32
+ server = true,
24
33
  debug = false,
25
34
  override = false
26
35
  } = options;
36
+ const envFiles = getEnvFiles(nodeEnv, server);
27
37
  const loadedFiles = [];
28
- const loadedKeys = /* @__PURE__ */ new Set();
29
- for (const fileName of ENV_FILES) {
38
+ const existingKeys = new Set(Object.keys(process.env));
39
+ const merged = {};
40
+ for (const fileName of envFiles) {
30
41
  const filePath = resolve(cwd, fileName);
31
42
  const parsed = parseEnvFile(filePath);
32
43
  if (parsed === null) {
33
44
  continue;
34
45
  }
35
46
  loadedFiles.push(fileName);
36
- for (const [key, value] of Object.entries(parsed)) {
37
- if (!override && process.env[key] !== void 0) {
38
- continue;
39
- }
40
- process.env[key] = value;
41
- loadedKeys.add(key);
47
+ Object.assign(merged, parsed);
48
+ }
49
+ const loadedKeys = [];
50
+ for (const [key, value] of Object.entries(merged)) {
51
+ if (!override && existingKeys.has(key)) {
52
+ continue;
42
53
  }
54
+ process.env[key] = value;
55
+ loadedKeys.push(key);
43
56
  }
44
57
  if (debug && loadedFiles.length > 0) {
45
58
  envLogger.debug(`Loaded env files: ${loadedFiles.join(", ")}`);
46
- envLogger.debug(`Loaded ${loadedKeys.size} environment variables`);
59
+ envLogger.debug(`Loaded ${loadedKeys.length} environment variables`);
47
60
  }
48
- return {
49
- loadedFiles,
50
- loadedKeys: Array.from(loadedKeys)
51
- };
61
+ return { loadedFiles, loadedKeys };
52
62
  }
53
63
  var isEnvLoaded = false;
54
64
  function loadEnvOnce(options = {}) {
55
65
  if (isEnvLoaded) {
56
- return {
57
- loadedFiles: [],
58
- loadedKeys: []
59
- };
66
+ return { loadedFiles: [], loadedKeys: [] };
60
67
  }
61
68
  isEnvLoaded = true;
62
69
  return loadEnv(options);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/env/loader.ts"],"names":[],"mappings":";;;;;;AAwBA,IAAM,SAAA,GAAY,MAAA,CAAO,KAAA,CAAM,uBAAuB,CAAA;AAkCtD,IAAM,SAAA,GAAY;AAAA,EACd,MAAA;AAAA,EACA,YAAA;AAAA,EACA,aAAA;AAAA,EACA;AACJ,CAAA;AAKA,SAAS,aAAa,QAAA,EACtB;AACI,EAAA,IAAI,CAAC,UAAA,CAAW,QAAQ,CAAA,EACxB;AACI,IAAA,OAAO,IAAA;AAAA,EACX;AAEA,EAAA,MAAM,OAAA,GAAU,YAAA,CAAa,QAAA,EAAU,OAAO,CAAA;AAC9C,EAAA,OAAO,MAAM,OAAO,CAAA;AACxB;AAwCO,SAAS,OAAA,CAAQ,OAAA,GAA0B,EAAC,EACnD;AACI,EAAA,MAAM;AAAA,IACF,GAAA,GAAM,QAAQ,GAAA,EAAI;AAAA,IAClB,KAAA,GAAQ,KAAA;AAAA,IACR,QAAA,GAAW;AAAA,GACf,GAAI,OAAA;AAEJ,EAAA,MAAM,cAAwB,EAAC;AAC/B,EAAA,MAAM,UAAA,uBAAiB,GAAA,EAAY;AAEnC,EAAA,KAAA,MAAW,YAAY,SAAA,EACvB;AACI,IAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,GAAA,EAAK,QAAQ,CAAA;AACtC,IAAA,MAAM,MAAA,GAAS,aAAa,QAAQ,CAAA;AAEpC,IAAA,IAAI,WAAW,IAAA,EACf;AACI,MAAA;AAAA,IACJ;AAEA,IAAA,WAAA,CAAY,KAAK,QAAQ,CAAA;AAEzB,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,EAChD;AAEI,MAAA,IAAI,CAAC,QAAA,IAAY,OAAA,CAAQ,GAAA,CAAI,GAAG,MAAM,MAAA,EACtC;AACI,QAAA;AAAA,MACJ;AAEA,MAAA,OAAA,CAAQ,GAAA,CAAI,GAAG,CAAA,GAAI,KAAA;AACnB,MAAA,UAAA,CAAW,IAAI,GAAG,CAAA;AAAA,IACtB;AAAA,EACJ;AAEA,EAAA,IAAI,KAAA,IAAS,WAAA,CAAY,MAAA,GAAS,CAAA,EAClC;AACI,IAAA,SAAA,CAAU,MAAM,CAAA,kBAAA,EAAqB,WAAA,CAAY,IAAA,CAAK,IAAI,CAAC,CAAA,CAAE,CAAA;AAC7D,IAAA,SAAA,CAAU,KAAA,CAAM,CAAA,OAAA,EAAU,UAAA,CAAW,IAAI,CAAA,sBAAA,CAAwB,CAAA;AAAA,EACrE;AAEA,EAAA,OAAO;AAAA,IACH,WAAA;AAAA,IACA,UAAA,EAAY,KAAA,CAAM,IAAA,CAAK,UAAU;AAAA,GACrC;AACJ;AAKA,IAAI,WAAA,GAAc,KAAA;AAQX,SAAS,WAAA,CAAY,OAAA,GAA0B,EAAC,EACvD;AACI,EAAA,IAAI,WAAA,EACJ;AACI,IAAA,OAAO;AAAA,MACH,aAAa,EAAC;AAAA,MACd,YAAY;AAAC,KACjB;AAAA,EACJ;AAEA,EAAA,WAAA,GAAc,IAAA;AACd,EAAA,OAAO,QAAQ,OAAO,CAAA;AAC1B;AAKO,SAAS,iBAAA,GAChB;AACI,EAAA,WAAA,GAAc,KAAA;AAClB","file":"loader.js","sourcesContent":["/**\n * Environment Variable Loader\n *\n * Next.js 스타일의 환경변수 파일 로딩\n *\n * @example\n * ```typescript\n * import { loadEnv } from '@spfn/core/env/loader';\n *\n * // SPFN 서버 진입점에서 호출\n * loadEnv();\n *\n * // 이후 스키마 검증\n * const env = createEnvRegistry(envSchema).validate();\n * ```\n *\n * @module env/loader\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { parse } from 'dotenv';\nimport { logger } from '@spfn/core/logger';\n\nconst envLogger = logger.child('@spfn/core:env-loader');\n\n/**\n * loadEnv 옵션\n */\nexport interface LoadEnvOptions\n{\n /**\n * 프로젝트 루트 경로\n * @default process.cwd()\n */\n cwd?: string;\n\n /**\n * 디버그 모드 (로드된 파일 로깅)\n * @default false\n */\n debug?: boolean;\n\n /**\n * 기존 process.env 값 덮어쓰기 허용\n * @default false\n */\n override?: boolean;\n}\n\n/**\n * 환경변수 파일 로딩 순서 (우선순위 낮음 높음)\n *\n * 1. .env - 기본값 (커밋 O)\n * 2. .env.local - 로컬 오버라이드 (커밋 X)\n * 3. .env.server - 서버 전용 기본값 (커밋 O)\n * 4. .env.server.local - 서버 전용 민감정보 (커밋 X)\n */\nconst ENV_FILES = [\n '.env',\n '.env.local',\n '.env.server',\n '.env.server.local',\n] as const;\n\n/**\n * 단일 .env 파일 파싱\n */\nfunction parseEnvFile(filePath: string): Record<string, string> | null\n{\n if (!existsSync(filePath))\n {\n return null;\n }\n\n const content = readFileSync(filePath, 'utf-8');\n return parse(content);\n}\n\n/**\n * 환경변수 로드 결과\n */\nexport interface LoadEnvResult\n{\n /**\n * 로드된 파일 목록\n */\n loadedFiles: string[];\n\n /**\n * 로드된 환경변수 키 목록\n */\n loadedKeys: string[];\n}\n\n/**\n * 프로젝트 루트의 환경변수 파일들을 규칙에 따라 로드\n *\n * Next.js 스타일의 우선순위를 따름:\n * - .env .env.local .env.server .env.server.local\n * - 나중에 로드된 값이 이전 값을 덮어씀\n *\n * @param options - 로드 옵션\n * @returns 로드 결과 (로드된 파일, 키 목록)\n *\n * @example\n * ```typescript\n * // 기본 사용\n * loadEnv();\n *\n * // 커스텀 경로\n * loadEnv({ cwd: '/path/to/project' });\n *\n * // 디버그 모드\n * loadEnv({ debug: true });\n * ```\n */\nexport function loadEnv(options: LoadEnvOptions = {}): LoadEnvResult\n{\n const {\n cwd = process.cwd(),\n debug = false,\n override = false,\n } = options;\n\n const loadedFiles: string[] = [];\n const loadedKeys = new Set<string>();\n\n for (const fileName of ENV_FILES)\n {\n const filePath = resolve(cwd, fileName);\n const parsed = parseEnvFile(filePath);\n\n if (parsed === null)\n {\n continue;\n }\n\n loadedFiles.push(fileName);\n\n for (const [key, value] of Object.entries(parsed))\n {\n // 기존 값이 있고 override가 false 스킵\n if (!override && process.env[key] !== undefined)\n {\n continue;\n }\n\n process.env[key] = value;\n loadedKeys.add(key);\n }\n }\n\n if (debug && loadedFiles.length > 0)\n {\n envLogger.debug(`Loaded env files: ${loadedFiles.join(', ')}`);\n envLogger.debug(`Loaded ${loadedKeys.size} environment variables`);\n }\n\n return {\n loadedFiles,\n loadedKeys: Array.from(loadedKeys),\n };\n}\n\n/**\n * 환경변수가 이미 로드되었는지 확인하는 플래그\n */\nlet isEnvLoaded = false;\n\n/**\n * 환경변수를 한 번만 로드 (중복 호출 방지)\n *\n * @param options - 로드 옵션\n * @returns 로드 결과 (이미 로드된 경우 빈 결과)\n */\nexport function loadEnvOnce(options: LoadEnvOptions = {}): LoadEnvResult\n{\n if (isEnvLoaded)\n {\n return {\n loadedFiles: [],\n loadedKeys: [],\n };\n }\n\n isEnvLoaded = true;\n return loadEnv(options);\n}\n\n/**\n * 환경변수 로드 상태 리셋 (테스트용)\n */\nexport function resetEnvLoadState(): void\n{\n isEnvLoaded = false;\n}\n"]}
1
+ {"version":3,"sources":["../../src/env/loader.ts"],"names":[],"mappings":"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","file":"loader.js","sourcesContent":["/**\n * Environment Variable Loader\n *\n * Next.js 스타일의 환경변수 파일 로딩 (환경별 분리 지원)\n *\n * 로딩 우선순위 (낮음 -> 높음, 나중 파일이 덮어씀):\n * 1. .env - 기본값 (committed)\n * 2. .env.{NODE_ENV} - 환경별 오버라이드 (committed)\n * 3. .env.local - 로컬 오버라이드 (gitignored, test에서 스킵)\n * 4. .env.{NODE_ENV}.local - 환경별 시크릿 (gitignored)\n * 5. .env.server - 서버 전용 (gitignored, Next.js 미로드)\n *\n * @example\n * ```typescript\n * import { loadEnv } from '@spfn/core/env/loader';\n *\n * // 기본 사용 (NODE_ENV 자동 감지)\n * loadEnv();\n *\n * // 특정 환경 지정\n * loadEnv({ nodeEnv: 'production' });\n *\n * // 서버 레이어 제외 (Next.js 클라이언트용)\n * loadEnv({ server: false });\n * ```\n *\n * @module env/loader\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { parse } from 'dotenv';\nimport { logger } from '@spfn/core/logger';\n\nconst envLogger = logger.child('@spfn/core:env-loader');\n\n/**\n * loadEnv 옵션\n */\nexport interface LoadEnvOptions\n{\n /**\n * 프로젝트 루트 경로\n * @default process.cwd()\n */\n cwd?: string;\n\n /**\n * NODE_ENV 값 (환경별 .env 파일 결정)\n * @default process.env.NODE_ENV || 'local'\n */\n nodeEnv?: string;\n\n /**\n * 서버 전용 파일 포함 여부 (.env.server)\n * @default true\n */\n server?: boolean;\n\n /**\n * 디버그 모드 (로드된 파일 로깅)\n * @default false\n */\n debug?: boolean;\n\n /**\n * 기존 process.env 값 덮어쓰기 허용\n * @default false\n */\n override?: boolean;\n}\n\n/**\n * 환경변수 로드 결과\n */\nexport interface LoadEnvResult\n{\n /**\n * 로드된 파일 목록\n */\n loadedFiles: string[];\n\n /**\n * 로드된 환경변수 목록\n */\n loadedKeys: string[];\n}\n\n/**\n * NODE_ENV에 따른 .env 파일 목록 생성 (우선순위 낮음 -> 높음)\n *\n * 구체적인 파일이 승리:\n * - environment > base\n * - server > shared\n * - local > committed\n */\nfunction getEnvFiles(nodeEnv: string, server: boolean): string[]\n{\n const files: string[] = [\n '.env',\n `.env.${nodeEnv}`,\n ];\n\n // test 환경에서는 .env.local 스킵 (테스트 결정론성 보장)\n if (nodeEnv !== 'test')\n {\n files.push('.env.local');\n }\n\n files.push(`.env.${nodeEnv}.local`);\n\n if (server)\n {\n files.push('.env.server');\n }\n\n return files;\n}\n\n/**\n * 단일 .env 파일 파싱\n */\nfunction parseEnvFile(filePath: string): Record<string, string> | null\n{\n if (!existsSync(filePath))\n {\n return null;\n }\n\n return parse(readFileSync(filePath, 'utf-8'));\n}\n\n/**\n * 프로젝트 루트의 환경변수 파일들을 규칙에 따라 로드\n *\n * 모든 파일을 파싱 머지한 process.env 한번에 적용.\n * 이미 process.env 존재하는 키는 덮어쓰지 않음 (플랫폼 주입 보호).\n *\n * @param options - 로드 옵션\n * @returns 로드 결과 (로드된 파일, 키 목록)\n */\nexport function loadEnv(options: LoadEnvOptions = {}): LoadEnvResult\n{\n const {\n cwd = process.cwd(),\n nodeEnv = process.env.NODE_ENV || 'local',\n server = true,\n debug = false,\n override = false,\n } = options;\n\n const envFiles = getEnvFiles(nodeEnv, server);\n const loadedFiles: string[] = [];\n\n // 1) 기존 process.env 키 스냅샷 저장\n const existingKeys = new Set(Object.keys(process.env));\n\n // 2) 모든 .env 파일 파싱 후 머지 (나중 파일이 승리)\n const merged: Record<string, string> = {};\n\n for (const fileName of envFiles)\n {\n const filePath = resolve(cwd, fileName);\n const parsed = parseEnvFile(filePath);\n\n if (parsed === null)\n {\n continue;\n }\n\n loadedFiles.push(fileName);\n Object.assign(merged, parsed);\n }\n\n // 3) 머지된 결과를 process.env에 적용\n const loadedKeys: string[] = [];\n\n for (const [key, value] of Object.entries(merged))\n {\n // 기존 process.env에 이미 있는 키는 스킵 (override가 false 때)\n if (!override && existingKeys.has(key))\n {\n continue;\n }\n\n process.env[key] = value;\n loadedKeys.push(key);\n }\n\n if (debug && loadedFiles.length > 0)\n {\n envLogger.debug(`Loaded env files: ${loadedFiles.join(', ')}`);\n envLogger.debug(`Loaded ${loadedKeys.length} environment variables`);\n }\n\n return { loadedFiles, loadedKeys };\n}\n\n/**\n * 환경변수가 이미 로드되었는지 확인하는 플래그\n */\nlet isEnvLoaded = false;\n\n/**\n * 환경변수를 한 번만 로드 (중복 호출 방지)\n *\n * @param options - 로드 옵션\n * @returns 로드 결과 (이미 로드된 경우 빈 결과)\n */\nexport function loadEnvOnce(options: LoadEnvOptions = {}): LoadEnvResult\n{\n if (isEnvLoaded)\n {\n return { loadedFiles: [], loadedKeys: [] };\n }\n\n isEnvLoaded = true;\n\n return loadEnv(options);\n}\n\n/**\n * 환경변수 로드 상태 리셋 (테스트용)\n */\nexport function resetEnvLoadState(): void\n{\n isEnvLoaded = false;\n}\n"]}
@@ -179,6 +179,14 @@ declare class DatabaseError<TDetails extends Record<string, unknown> = Record<st
179
179
  statusCode?: number;
180
180
  details?: TDetails;
181
181
  });
182
+ /**
183
+ * Whether this error's message/details are derived from the raw database
184
+ * driver (SQL text, table/column/constraint names, parameter values) and
185
+ * therefore must NOT be exposed to clients in production. Defined as a
186
+ * prototype getter so it is never serialized into the response by toJSON().
187
+ * Subclasses with a safe, constructed message override this to `false`.
188
+ */
189
+ get internal(): boolean;
182
190
  }
183
191
  /**
184
192
  * Connection Error (503 Service Unavailable)
@@ -215,6 +223,7 @@ declare class EntityNotFoundError extends QueryError {
215
223
  resource: string;
216
224
  id: string | number;
217
225
  });
226
+ get internal(): boolean;
218
227
  }
219
228
  /**
220
229
  * Constraint Violation Error (400 Bad Request)
@@ -263,6 +272,7 @@ declare class DuplicateEntryError extends QueryError {
263
272
  field: string;
264
273
  value: string | number;
265
274
  });
275
+ get internal(): boolean;
266
276
  }
267
277
 
268
278
  type databaseErrors_ConnectionError = ConnectionError;