@gaonjs/config 0.16.1 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/apps.d.ts CHANGED
@@ -26,6 +26,7 @@ export declare function resolveApps(cwd?: string): Promise<DiscoveredApp[]>;
26
26
  * 를 반영해 덧붙인다. 여기서는 순수 이름·라우트·컨트롤러·hosts 만.
27
27
  *
28
28
  * hosts: app.config.ts 의 `hosts:` 배열을 그대로 옮긴다(v0.16 §3.3 · 운영
29
- * 도메인). 문자열 배열만 신뢰 잘못된 값은 조용히 무시한다.
29
+ * 도메인). 잘못된 항목은 조용히 버리지 않고 부팅 에러로 알린다(결정 231 · §7.5.3)
30
+ * — hosts 오타(빈 문자열·비문자열)로 도메인 라우팅이 조용히 빠지던 사고를 막는다.
30
31
  */
31
32
  export declare function toAppSpec(app: DiscoveredApp): AppSpec;
package/dist/apps.js CHANGED
@@ -71,13 +71,25 @@ export async function resolveApps(cwd = process.cwd()) {
71
71
  * 를 반영해 덧붙인다. 여기서는 순수 이름·라우트·컨트롤러·hosts 만.
72
72
  *
73
73
  * hosts: app.config.ts 의 `hosts:` 배열을 그대로 옮긴다(v0.16 §3.3 · 운영
74
- * 도메인). 문자열 배열만 신뢰 잘못된 값은 조용히 무시한다.
74
+ * 도메인). 잘못된 항목은 조용히 버리지 않고 부팅 에러로 알린다(결정 231 · §7.5.3)
75
+ * — hosts 오타(빈 문자열·비문자열)로 도메인 라우팅이 조용히 빠지던 사고를 막는다.
75
76
  */
76
77
  export function toAppSpec(app) {
77
78
  const raw = app.appConfig?.hosts;
78
- const hosts = Array.isArray(raw)
79
- ? raw.filter((h) => typeof h === 'string' && h.length > 0)
80
- : undefined;
79
+ let hosts;
80
+ if (raw !== undefined) {
81
+ if (!Array.isArray(raw)) {
82
+ throw new Error(`[@gaonjs/config] apps/${app.name}/app.config.ts 의 hosts 는 문자열 배열이어야 합니다(받은 타입: ${typeof raw}).\n` +
83
+ ` → hosts: ['app.example.com', 'www.example.com'] 형태로 지정하세요.`);
84
+ }
85
+ raw.forEach((h, i) => {
86
+ if (typeof h !== 'string' || h.length === 0) {
87
+ throw new Error(`[@gaonjs/config] apps/${app.name}/app.config.ts 의 hosts[${i}] 가 유효하지 않습니다: ${JSON.stringify(h)}.\n` +
88
+ ` → 각 host 는 비어있지 않은 문자열(도메인)이어야 합니다 — 오타·빈 값·주석 잔재를 확인하세요.`);
89
+ }
90
+ });
91
+ hosts = raw;
92
+ }
81
93
  return {
82
94
  name: app.name,
83
95
  routes: app.routes,
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { defineConfig, defineAppConfig, type GaonConfig, type AppConfig, type AppSessionConfig, type DbConfig, type RedisConfig, type NatsConfig, type HubConfig, type MailConfig, type StorageConfig, type StorageDiskConfig, type I18nConfig, type WebConfig, } from './types.js';
2
2
  export { loadGaonConfig, findConfigPath } from './load.js';
3
3
  export { resolveApps, toAppSpec, type DiscoveredApp, } from './apps.js';
4
- export { wireGaon, wireDomain, type WiredGaon, type WiredDomain } from './wire.js';
4
+ export { wireGaon, wireDomain, applyTimezone, type WiredGaon, type WiredDomain } from './wire.js';
5
5
  export { connectTestDatabase, type TestDbHandle } from './testkit.js';
package/dist/index.js CHANGED
@@ -9,6 +9,6 @@
9
9
  export { defineConfig, defineAppConfig, } from './types.js';
10
10
  export { loadGaonConfig, findConfigPath } from './load.js';
11
11
  export { resolveApps, toAppSpec, } from './apps.js';
12
- export { wireGaon, wireDomain } from './wire.js';
12
+ export { wireGaon, wireDomain, applyTimezone } from './wire.js';
13
13
  // 테스트 DB 배선 (결정 111 · gaon test 격리)
14
14
  export { connectTestDatabase } from './testkit.js';
package/dist/types.d.ts CHANGED
@@ -110,8 +110,9 @@ export interface GaonConfig {
110
110
  readonly i18n?: I18nConfig;
111
111
  readonly web?: WebConfig;
112
112
  /**
113
- * 타임존(§3.3 정본에 명시). 현재는 문서상 값이며 M9-A 에서는 저장만 —
114
- * 사용처(스케줄러·로깅 포맷)는 배터리가 후속 웨이브에서 소비한다.
113
+ * 타임존(§3.3 · IANA 이름 예 'Asia/Seoul'·'UTC'). 결정 230: 부팅 process.env.TZ
114
+ * **실제 배선**한다 이후 Date·로깅 타임스탬프·스케줄러(크론)가 값을 따른다.
115
+ * 유효하지 않은 IANA 이름은 부팅 에러(조용한 no-op 아님).
115
116
  */
116
117
  readonly timezone?: string;
117
118
  }
package/dist/wire.d.ts CHANGED
@@ -3,6 +3,14 @@ import { Redis } from 'ioredis';
3
3
  import { type GaonNats } from '@gaonjs/async';
4
4
  import type { GaonConfig } from './types.js';
5
5
  import { type DiscoveredApp } from './apps.js';
6
+ /**
7
+ * 결정 230: gaon.config.ts 의 `timezone` 을 **실제로 배선**한다(이전엔 저장만·조용한 no-op).
8
+ * process.env.TZ 를 세워 이후 Date·로깅 타임스탬프·스케줄러(크론)가 선언 타임존을 따르게 한다.
9
+ * Node 는 런타임 TZ 변경을 반영한다(실측 · 첫 Date 사용 전에 부팅 경로에서 세운다). 유효하지
10
+ * 않은 IANA 이름은 process.env.TZ 에 넣으면 조용히 UTC 로 떨어지므로, 넣기 전에 검증해
11
+ * 부팅 에러(§7.5.3)로 알린다 — 또 다른 조용한 실패를 만들지 않는다.
12
+ */
13
+ export declare function applyTimezone(config: GaonConfig): void;
6
14
  /** 부팅 결과 — 이후 serve 가 listen 한다. close() 로 배선을 뒤집는다. */
7
15
  export interface WiredGaon {
8
16
  readonly app: FastifyInstance;
package/dist/wire.js CHANGED
@@ -16,6 +16,28 @@ import { createApp, redisSessionStore, setValidationMessageResolver, setLocaleRe
16
16
  import { resolveApps, toAppSpec } from './apps.js';
17
17
  import { existsSync } from 'node:fs';
18
18
  import { resolve as resolvePath, join } from 'node:path';
19
+ /**
20
+ * 결정 230: gaon.config.ts 의 `timezone` 을 **실제로 배선**한다(이전엔 저장만·조용한 no-op).
21
+ * process.env.TZ 를 세워 이후 Date·로깅 타임스탬프·스케줄러(크론)가 선언 타임존을 따르게 한다.
22
+ * Node 는 런타임 TZ 변경을 반영한다(실측 · 첫 Date 사용 전에 부팅 경로에서 세운다). 유효하지
23
+ * 않은 IANA 이름은 process.env.TZ 에 넣으면 조용히 UTC 로 떨어지므로, 넣기 전에 검증해
24
+ * 부팅 에러(§7.5.3)로 알린다 — 또 다른 조용한 실패를 만들지 않는다.
25
+ */
26
+ export function applyTimezone(config) {
27
+ const tz = config.timezone;
28
+ if (tz === undefined)
29
+ return;
30
+ try {
31
+ // 유효하지 않은 존이면 RangeError(실측). 부작용 없는 검증 전용 호출.
32
+ new Intl.DateTimeFormat('en-US', { timeZone: tz });
33
+ }
34
+ catch {
35
+ throw new Error(`[@gaonjs/config] timezone = ${JSON.stringify(tz)} 는 유효한 IANA 타임존이 아닙니다.\n` +
36
+ ` → gaon.config.ts 의 timezone 을 'Asia/Seoul'·'UTC'·'America/New_York' 같은 IANA 이름으로 지정하세요\n` +
37
+ ` (목록: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).`);
38
+ }
39
+ process.env.TZ = tz;
40
+ }
19
41
  /**
20
42
  * gaon.config.ts 의 **프로세스 전역 도메인 배터리**(DB 커넥션·메일·스토리지·
21
43
  * i18n)를 배선한다. serve(웹)와 work(워커)가 함께 태우는 공통 경로다 — 잡
@@ -25,6 +47,8 @@ import { resolve as resolvePath, join } from 'node:path';
25
47
  */
26
48
  export async function wireDomain(config, cwd = process.cwd()) {
27
49
  const root = resolvePath(cwd);
50
+ // 0) 타임존 — 어떤 배터리보다 먼저 적용해 이후 Date·로깅·스케줄러가 선언 타임존을 본다(결정 230).
51
+ applyTimezone(config);
28
52
  // 1) DB 커넥션 — 키별로 등록(main + 명명 커넥션 · §4.5). 실 연결은 첫 쿼리에서(pool lazy).
29
53
  if (config.db) {
30
54
  for (const [key, cfg] of Object.entries(config.db)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gaonjs/config",
3
- "version": "0.16.1",
3
+ "version": "0.17.0",
4
4
  "description": "Gaon 루트 설정 로더·자동 배선 (gaon.config.ts)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -25,13 +25,13 @@
25
25
  ],
26
26
  "dependencies": {
27
27
  "ioredis": "^5.4.1",
28
- "@gaonjs/core": "0.2.2",
29
- "@gaonjs/async": "0.13.0",
30
- "@gaonjs/data": "0.16.3",
31
- "@gaonjs/mail": "0.2.1",
32
- "@gaonjs/storage": "0.1.3",
33
- "@gaonjs/i18n": "0.2.1",
34
- "@gaonjs/web": "0.19.1"
28
+ "@gaonjs/core": "0.2.3",
29
+ "@gaonjs/async": "0.15.0",
30
+ "@gaonjs/data": "0.17.0",
31
+ "@gaonjs/i18n": "0.2.2",
32
+ "@gaonjs/mail": "0.3.0",
33
+ "@gaonjs/storage": "0.1.4",
34
+ "@gaonjs/web": "0.19.2"
35
35
  },
36
36
  "devDependencies": {
37
37
  "fastify": "^5.0.0"