@xfilecom/xframe 0.1.12 → 0.1.14

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xfilecom/xframe",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "Scaffold full-stack app: Nest + @xfilecom/backend-core, Vite/React + @xfilecom/front-core",
5
5
  "license": "UNLICENSED",
6
6
  "bin": {
@@ -16,7 +16,7 @@ Nest API (`@xfilecom/backend-core`) + Vite/React client·admin (`@xfilecom/front
16
16
 
17
17
  ## URLs
18
18
 
19
- - API: `http://localhost:3000` — `GET /health`
19
+ - API: `http://localhost:3000` — `GET /health` (`AppService`: DB 로드·연결 여부만; 쿼리 패턴은 `docs/DATABASE.md`)
20
20
  - Client: `http://localhost:3001` — `CommonResponse` 형태로 `/health` 표시
21
21
  - Admin: `http://localhost:3002`
22
22
 
@@ -28,6 +28,10 @@ Nest API (`@xfilecom/backend-core`) + Vite/React client·admin (`@xfilecom/front
28
28
 
29
29
  자세한 설명은 `shared/README.md` 참고.
30
30
 
31
+ ## Database (MySQL + Drizzle)
32
+
33
+ 설정 흐름, `DatabaseService` / `DatabaseQuery` 주입, Drizzle Kit 스크립트는 **[docs/DATABASE.md](./docs/DATABASE.md)** 를 보면 됩니다.
34
+
31
35
  ## GitLab `~/.npmrc` 와 충돌 시
32
36
 
33
37
  프로젝트 루트 `.npmrc`의 `@xfilecom:registry`가 npm 공개 레지스트리를 가리키므로, `@xfilecom/*` 설치는 여기서 우선합니다.
@@ -1,11 +1,17 @@
1
1
  import { Controller, Get } from '@nestjs/common';
2
- import { Public } from '@xfilecom/backend-core';
2
+ import { ControllerHelpers, Public } from '@xfilecom/backend-core';
3
+ import { AppService } from './app.service';
3
4
 
4
5
  @Controller()
5
6
  export class AppController {
7
+ constructor(
8
+ private readonly appService: AppService,
9
+ private readonly controllerHelpers: ControllerHelpers,
10
+ ) {}
11
+
6
12
  @Public()
7
13
  @Get('health')
8
14
  health() {
9
- return { ok: true, service: '__SERVICE_LABEL__' };
15
+ return this.controllerHelpers.success(this.appService.getHealth());
10
16
  }
11
17
  }
@@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
2
2
  import { CoreModule, type CoreModuleOptions } from '@xfilecom/backend-core';
3
3
  import { appConfig } from './config.loader';
4
4
  import { AppController } from './app.controller';
5
+ import { AppService } from './app.service';
5
6
  import { schema } from '../../../shared/schema';
6
7
 
7
8
  function coreOptionsFromYaml(): CoreModuleOptions {
@@ -20,5 +21,6 @@ function coreOptionsFromYaml(): CoreModuleOptions {
20
21
  @Module({
21
22
  imports: [CoreModule.forHttpApi(coreOptionsFromYaml())],
22
23
  controllers: [AppController],
24
+ providers: [AppService],
23
25
  })
24
26
  export class AppModule {}
@@ -0,0 +1,38 @@
1
+ import { Injectable, Optional } from '@nestjs/common';
2
+ import { DatabaseService } from '@xfilecom/backend-core';
3
+
4
+ /**
5
+ * DB 사용은 backend-core 에서 두 가지가 있습니다.
6
+ *
7
+ * 1. **DatabaseService** — `this.database.db` 로 Drizzle API 직접.
8
+ * 2. **DatabaseQuery** — `findOne` / `count` / `select` 등 헬퍼.
9
+ *
10
+ * `GET /health` 는 특정 테이블을 가정하지 않음 (`db:pull` 등으로 스키마가 바뀌어도 깨지지 않게).
11
+ * 도메인 쿼리는 별도 서비스에서 스키마 심볼·`DatabaseQuery` 로 작성하면 됨. → `docs/DATABASE.md` §3.
12
+ */
13
+ @Injectable()
14
+ export class AppService {
15
+ constructor(@Optional() private readonly database?: DatabaseService) {}
16
+
17
+ serviceLabel(): string {
18
+ return '__SERVICE_LABEL__';
19
+ }
20
+
21
+ /**
22
+ * `GET /health`. `core.database.auto: false` 이면 DB 모듈 미로드 → optional 미주입.
23
+ */
24
+ getHealth(): {
25
+ ok: true;
26
+ service: string;
27
+ database: { moduleLoaded: boolean; connected: boolean };
28
+ } {
29
+ const service = this.serviceLabel();
30
+ const moduleLoaded = this.database !== undefined;
31
+ const connected = moduleLoaded && this.database.isConnected();
32
+ return {
33
+ ok: true,
34
+ service,
35
+ database: { moduleLoaded, connected },
36
+ };
37
+ }
38
+ }
@@ -0,0 +1,122 @@
1
+ # Database (MySQL + Drizzle)
2
+
3
+ xframe 프로젝트는 **`@xfilecom/backend-core`** 가 MySQL 풀과 Drizzle 인스턴스를 붙이고, **`shared/schema`** 의 테이블 정의를 넘깁니다.
4
+
5
+ ## 1. 설정
6
+
7
+ | 단계 | 위치 |
8
+ |------|------|
9
+ | 연결 정보 | `shared/config/api/application.yml` 의 `database.*` |
10
+ | 로드 | `apps/api/src/config.loader.ts` → `loadApplicationYamlSync` + `syncEnvKeys` 로 `DB_*` 동기화 |
11
+ | 켜기/끄기 | 같은 YAML 의 `core.database.auto` (`true` 이면 부팅 시 연결 시도) |
12
+
13
+ `CONFIG_SOURCE` 기본값은 `yaml` 입니다. 배포에서 프로세스 환경변수로 YAML 을 덮어쓰려면 `yamlWithEnvOverrides` 를 쓰면 됩니다.
14
+
15
+ ## 2. 스키마
16
+
17
+ - 테이블은 `shared/schema/schema.ts` (등)에 `drizzle-orm/mysql-core` 로 정의합니다.
18
+ - `shared/schema/index.ts` 에서 `export const schema = …` 로 **객체 하나**로 묶어 `app.module.ts` 의 `CoreModule` 에 넘깁니다.
19
+ - Nest 빌드는 `apps/api/tsconfig.build.json` 에 `../../shared/schema/**/*.ts` 가 포함되어 있습니다.
20
+
21
+ `DatabaseQuery` 에 넘기는 **테이블 이름 문자열**은 DB 테이블명이 아니라, **`schema` 객체의 프로퍼티 이름**과 같아야 합니다.
22
+ 예: `export const appMeta = mysqlTable('app_meta', …)` 이면 문자열은 `'appMeta'` 입니다.
23
+
24
+ ## 3. Nest 에서 쓰는 두 가지 방식
25
+
26
+ `CoreModule` 이 DB 를 켠 상태면 **`DatabaseService`** 와 **`DatabaseQuery`** 를 어디서든 주입할 수 있습니다 (`@Global()`). 둘 중 하나만 써도 되고, 같이 주입해도 됩니다.
27
+
28
+ | | **`DatabaseService`** | **`DatabaseQuery`** |
29
+ |---|------------------------|---------------------|
30
+ | **역할** | 풀 + Drizzle 인스턴스 (`get db()`) | 문자열 테이블 키 기준 CRUD 헬퍼 |
31
+ | **쿼리** | `drizzle-orm` API 직접 (`select`, `eq`, `sql` …) | `findOne`, `select`, `insert`, `count` … |
32
+ | **타입** | 스키마 심볼 import (`appMeta` 등) — 권장 | 테이블 키는 문자열 (`'appMeta'`) |
33
+ | **언제** | 복잡한 조인·raw·Drizzle 문서 그대로 | 단순 CRUD·페이지네이션·object `where` |
34
+
35
+ 스캐폴드 **`apps/api/src/app.service.ts`** 의 `GET /health` 는 **DB 모듈 로드 여부·연결 여부**만 돌려주며, 어떤 테이블도 가정하지 않습니다. 실제 쿼리는 아래 패턴으로 별도 서비스를 두면 됩니다.
36
+
37
+ ### A. Drizzle API 직접 (`DatabaseService.db`)
38
+
39
+ 타입 안전하게 쓰려면 스키마에서 테이블을 import 합니다.
40
+
41
+ ```typescript
42
+ import { Injectable } from '@nestjs/common';
43
+ import { eq } from 'drizzle-orm';
44
+ import { DatabaseService } from '@xfilecom/backend-core';
45
+ import { appMeta } from '../../../shared/schema';
46
+
47
+ @Injectable()
48
+ export class MetaService {
49
+ constructor(private readonly database: DatabaseService) {}
50
+
51
+ async getValue(key: string) {
52
+ const db = this.database.db;
53
+ const [row] = await db
54
+ .select()
55
+ .from(appMeta)
56
+ .where(eq(appMeta.key, key))
57
+ .limit(1);
58
+ return row?.value ?? null;
59
+ }
60
+ }
61
+ ```
62
+
63
+ 연결이 안 된 상태에서 `this.database.db` 를 쓰면 예외가 납니다. 필요하면 `this.database.isConnected()` 로 확인하세요.
64
+
65
+ ### B. 헬퍼 CRUD (`DatabaseQuery`)
66
+
67
+ 문자열 테이블 키·plain object `where`·페이지네이션 등을 쓰기 쉽게 한 래퍼입니다.
68
+
69
+ ```typescript
70
+ import { Injectable } from '@nestjs/common';
71
+ import { DatabaseQuery } from '@xfilecom/backend-core';
72
+
73
+ @Injectable()
74
+ export class MetaService {
75
+ constructor(private readonly dbQuery: DatabaseQuery) {}
76
+
77
+ findByKey(key: string) {
78
+ return this.dbQuery.findOne('appMeta', {
79
+ where: { key },
80
+ fields: ['id', 'key', 'value'],
81
+ });
82
+ }
83
+ }
84
+ ```
85
+
86
+ `select` / `insert` / `update` / `delete` / `count` / `exists` / `findById` 등은 **`DatabaseQuery` 타입 정의·구현**(`@xfilecom/backend-core`)을 보면 됩니다.
87
+
88
+ ## 4. 모듈에 서비스 등록
89
+
90
+ ```typescript
91
+ @Module({
92
+ imports: [CoreModule.forHttpApi(coreOptionsFromYaml())],
93
+ controllers: [AppController],
94
+ providers: [MetaService],
95
+ })
96
+ export class AppModule {}
97
+ ```
98
+
99
+ `DatabaseModule` 을 다시 import 할 필요는 없습니다.
100
+
101
+ ## 공통 응답 (`CommonResponseDto`)
102
+
103
+ `CoreModule.forHttpApi` 는 기본으로 **`ResponseTransformInterceptor`** 를 켜서, 컨트롤러가 객체만 반환해도 `{ code, data, meta?, error }` 형태로 감쌉니다.
104
+ 그래도 **`ControllerHelpers.success(payload)`** 로 `CommonResponseDto` 를 직접 반환하는 편이 의도가 분명합니다. `CoreModule` 이 `@Global()` 이라 **`ControllerHelpers` 는 주입만 하면 됩니다** — `apps/api/src/app.controller.ts` 의 `GET /health` 가 그 패턴입니다.
105
+
106
+ ## 5. Drizzle Kit (루트에서 실행)
107
+
108
+ 프로젝트 **루트**에서 스크립트를 실행합니다 (`process.cwd()` 가 루트여야 `drizzle.env.ts` 가 YAML 을 찾습니다).
109
+
110
+ | 스크립트 | 설명 |
111
+ |----------|------|
112
+ | `npm run db:generate` | 스키마 diff → SQL 마이그레이션 생성 |
113
+ | `npm run db:push` | 스키마를 DB 에 맞춤 (개발용) |
114
+ | `npm run db:migrate` | 마이그레이션 적용 |
115
+ | `npm run db:studio` | Drizzle Studio |
116
+ | `npm run db:pull` | DB 역추출 (introspect) |
117
+
118
+ 원시 SQL·시드는 `shared/sql/` 에 두는 패턴을 권장합니다.
119
+
120
+ ## 6. 연결 실패 시
121
+
122
+ `DatabaseService` 는 연결에 실패해도 **애플리케이션 기동은 계속**하고 경고만 남깁니다. DB 가 필요한 핸들러에서는 `isConnected()` 확인 또는 try/catch 로 처리하세요.
@@ -6,7 +6,7 @@
6
6
  |------|------|
7
7
  | **`config/api`** | Nest API YAML (`application.yml`, `application-<env>.yml`) |
8
8
  | **`config/web/client`**, **`config/web/admin`** | 각 Vite 앱별 YAML (`VITE_API_BASE_URL`, `VITE_APP_TITLE` 등 주입) |
9
- | **`schema/`** | Drizzle 등 DB 스키마(TS). `apps/api`의 `tsconfig.build.json`에 경로를 포함하거나, 스키마만 `apps/api/src/database`에 두고 여기서 re-export 하는 식으로 맞추면 됩니다. |
9
+ | **`schema/`** | Drizzle 등 DB 스키마(TS). `apps/api`의 `tsconfig.build.json`에 경로를 포함하거나, 스키마만 `apps/api/src/database`에 두고 여기서 re-export 하는 식으로 맞추면 됩니다. Nest 에서의 주입·쿼리 패턴은 루트 **[docs/DATABASE.md](../docs/DATABASE.md)** 참고. |
10
10
  | **`sql/`** | 마이그레이션·시드·원시 SQL (`drizzle-kit`, 수동 스크립트 등) |
11
11
  | **`endpoint/`** | HTTP 계약(OpenAPI yaml, 공유 DTO 타입, 라우트 메타) — 제품에 맞게 확장 |
12
12
 
@@ -10,8 +10,8 @@ http:
10
10
  # • 기본 CONFIG_SOURCE=yaml (`apps/api/src/config.loader.ts`) → 이 파일이 DB 근거, 셸의 DB_* 로 덮이지 않음.
11
11
  # 배포에서 env 우선이면 CONFIG_SOURCE=yamlWithEnvOverrides
12
12
  # • 호스트: Docker MySQL 을 맥/윈에서 포트 포워딩으로 쓸 때는 `127.0.0.1` 권장 (`localhost` 는 소켓/해석 차이).
13
- # • DB 이름·비밀번호는 DB 클라이언트(GUI)글자 하나까지 동일해야 함 (예: my_db vs my-db, milk1209 vs milk!209).
14
- # • 비밀번호에 ! @ # YAML 특수문자, DB 이름에 하이픈이 있으면 반드시 따옴표: password: 'a!b' , name: 'my-db'
13
+ # • DB 이름·비밀번호는 MySQL/GUI 와 동일해야 함 (언더스코어 vs 하이픈 이름 혼동 주의).
14
+ # • YAML 보통 따옴표 없이 써도 됨. ! : @ 등으로 파싱이 꼬일 때만 작은따옴표로 감싸면 됨.
15
15
  # • 로그에 Access denied … user'@'172.x.x.x 면 컨테이너가 보는 클라이언트 주소입니다. MySQL 에서 GRANT 대상 호스트(% 등) 확인.
16
16
  #
17
17
  database:
@@ -3,3 +3,10 @@
3
3
  - Drizzle `schema.ts` / 테이블 정의를 여기 두고, `apps/api`에서 import 해 `CoreModule`의 `database: { auto: true, schema }`에 넘깁니다.
4
4
  - `index.ts`는 `export const schema` 로 테이블을 묶습니다. `npm run db:pull`(역추출) 후 `relations.ts`가 생기면 Drizzle 문서에 맞게 `schema` export를 정리하세요.
5
5
  - Nest 기본 빌드는 `apps/api/src`만 컴파일하므로, `shared/schema`에 TS를 두면 **`apps/api/tsconfig.build.json`의 `include`** 에 `../../shared/schema/**/*.ts` 를 추가하거나, 스키마를 `apps/api/src/database/schema`에 두고 이 폴더는 문서·SQL 덤프용으로만 써도 됩니다.
6
+
7
+ ## `DatabaseQuery` 와 테이블 이름
8
+
9
+ `@xfilecom/backend-core` 의 `DatabaseQuery` 는 첫 인자로 **문자열 테이블 키**를 받습니다. 이 값은 MySQL 의 물리 테이블명이 아니라 **`schema` 객체의 키**와 같아야 합니다.
10
+ 예: `export const appMeta = mysqlTable('app_meta', …)` → `findOne('appMeta', …)`.
11
+
12
+ Drizzle 을 직접 쓸 때는 `DatabaseService.db` 와 함께 `appMeta` 심볼을 import 하면 됩니다. 전체 흐름은 **[docs/DATABASE.md](../../docs/DATABASE.md)** 를 참고하세요.
@@ -9,4 +9,8 @@ export type CommonEnvelope<T> = {
9
9
  export type HealthData = {
10
10
  ok?: boolean;
11
11
  service?: string;
12
+ database?: {
13
+ moduleLoaded: boolean;
14
+ connected: boolean;
15
+ };
12
16
  };
@@ -1,6 +0,0 @@
1
- # Nest API·Drizzle 부트스트랩은 루트 .env 를 읽지 않음. DB 등은 `shared/config/api/application*.yml` 기준.
2
- # 기본 CONFIG_SOURCE=yaml → 셸에서 export 한 DB_* 로 YAML 이 바뀌지 않음. 덮어쓰기: CONFIG_SOURCE=yamlWithEnvOverrides
3
- #
4
- # (선택) 로컬 도구용으로만 .env 를 쓸 경우 — 앱/ drizzle-kit 은 자동 로드하지 않음.
5
- #
6
- # 접속 오류 시: GUI 와 동일한 host·user·password·database 이름을 YAML 에 맞출 것.