npm package: @serverkit-lib/runtime-package
Description: Serverkit Runtime Module Package
Author: ServerKit Master
Latest version: 0.0.47

# @serverkit-lib/runtime-package

![version](https://img.shields.io/badge/version-0.0.47-blue)
![Node.js](https://img.shields.io/badge/Node.js-20%2B-brightgreen)
![build](https://img.shields.io/badge/build-Debug-blue)
![license](https://img.shields.io/badge/license-MIT-green)

# 소개

이 모듈(`@serverkit-lib/runtime-package`)은 서버키트로 생성된 서버에서 런타임 시 동적으로 사용 가능한 기능들을 제공합니다.

# SCM

SCM은 `.scm` 파일 기반 데이터 구조의 유효성 검사를 처리하며, 데이터베이스 작업 전후로 데이터를 검증하는 데 최적화된 유틸리티를 제공합니다. 이 모듈은 다음과 같은 주요 기능을 제공합니다:

## SCM 주요 기능

1. **데이터 유효성 검사**

    - 특정 데이터의 전체 혹은 일부 유효성을 검사.

2. **데이터 구조 기반 검증**

    - 초기화에서 등록된 사용자 정의 데이터 필드 리스트를 바탕으로 데이터 구조 검증.

3. **Prisma 통합**
    - Prisma Middleware를 통해 자동 Validation 처리 지원.

## 주요 메서드

**`getInstance()`**  
 `Scheme` 클래스의 싱글턴 인스턴스를 반환합니다.
이 메서드로 이미 생성된 값을 참조할 수 있습니다. runtime-module의 `scmInstance` 객체를 참고해주세요. 이 객체는 Scheme 클래스의 모든 기능을 모두 사용할 수 있습니다.

**`validation(data: Record<string, any>): Promise<void>`**

- 주어진 데이터의 유효성을 검사합니다.
- data 파라미터로 넘어온 데이터만 유효성을 검사합니다. 전체 데이터를 기준으로 강한 검사를 원하면 `fullValidation`을 참고해주세요.
- 유효성 검사가 실패하면 `SCMError`를 throw합니다.

**`fullValidation(data: any, scmId: string): Promise<void>`**  
 특정 `scmId`에 대한 모든 필드의 유효성을 검사합니다.

- 모든 필드에 대한 데이터 유효성을 검증하므로 scmId가 가리키는 모든 필드를 기준으로 검사합니다. 즉, canBeNull이 false가 아닌 모든 필드에 대해 검사하므로 빠져있는 필드에 해당하는 값이 있을 경우 실패합니다.
- 하나라도 유효성 조건에 어긋나면 `SCMError`를 throw합니다.

**`insertDataValidation(data: any, scmId: string): Promise<void>`**  
 prisma를 활용한 data Insert 작업 전에 유효성을 검사합니다.

- 유효성 검사가 실패하면 `SCMError`를 throw합니다.
- 이 함수는 initializeValidation에서 자동으로 처리됩니다. 별도로 검증을 원할 경우 사용해주세요.

**`updateDataValidation(data: any, scmId: string): Promise<void>`**  
 prisma를 활용한 data Update 작업 전에 유효성을 검사합니다.

- 유효성 검사가 실패하면 `SCMError`를 throw합니다.
- 이 함수는 initializeValidation에서 자동으로 처리됩니다. 별도로 검증을 원할 경우 사용해주세요.

**`getFieldTypeToInputType(fieldType: string): string`**  
 주어진 필드 타입에 대해 데이터베이스 입력 타입을 반환합니다.

### SCM 주요 활용 예시

```javascript
const { scmInstance } = require('@serverkit-lib/runtime-package');  // 런타임 모듈
const scmJson = require("./scm.json");

// SCM 애셋 JSON 포함.
scmInstance.scm = scmJson;

// Primsa Pre 이벤트를 지정하여 데이터가 Create/Update 시 Validation 처리되게 합니다.
const prisma = require("./vqm/prisma");
prisma.$use(async (params: any, next: any) => {
    try {
        // validation은 prisma에 설정한 모델 기준으로 작동하며 모델이 없을 경우 무시합니다.
        const model = params.model ?? "";
        if (model == "") {
            return next(params); // Validation 대상이 아닌 DB작업이라 판단하며 실제 DB 작업 처리.
        }
        const findScm: ISCMRequest | undefined = this.scm.find((v) => {
            return v.dbm.includes(model);
        });
        if (!findScm) {
            return next(params); // Validation 대상이 아닌 DB작업이라 판단하며 실제 DB 작업 처리.
        }

        // 생성 작업 전처리
        if (["create", "createMany"].includes(params.action)) {
            if (params.action == "create") {
                await this.insertDataValidation(params.args.data, findScm.id);
            } else {
                for (const d of params.args.data) {
                    await this.insertDataValidation(d, findScm.id);
                }
            }
            console.log("Completely Data Validation (Create)");
        }

        // 수정 작업 전처리
        else if (["update", "updateMany"].includes(params.action)) {
            await this.updateDataValidation(params.args.data, findScm.id);
            console.log("Completely Data Validation (Update)");
        }

        // 실제 DB 작업 개시.
        return await next(params);
    } catch (e) {
        throw e;
    }
});
```

### SCMError

SCMError는 Node.js의 Error 클래스를 상속한 커스텀 Error 클래스로 Scheme 클래스 내에서 발생하는 모든 작업에 대한 예외상황 발생 시 반환되는 에러 형식입니다.

#### 주요 속성

**fields?: string[]**

- 유효성 검사 중 실패한 필드 목록.

#### 주요 메서드

**`info: { service, message, code, timestamp, fields }`**

- 에러 정보를 객체로 반환.
- fields 속성을 포함하여 유효성 검사 중 실패한 필드에 대한 정보를 제공합니다.

---

# Drive

Drive 클래스는 싱글턴 패턴을 사용하여 인스턴스를 관리하며, 파일 저장소와 관련된 모든 작업을 수행합니다.

## 주요 기능

1. **파일 저장소 설정**

    - 로컬 저장소 또는 클라우드 저장소(AWS S3 등)를 선택하여 사용할 수 있습니다.
    - 저장소 유형에 따라 적절한 드라이버를 할당합니다.

2. **파일 메타데이터 관리**

    - 특정 파일의 메타데이터를 조회할 수 있습니다.

3. **파일 업로드 및 다운로드**

    - 임시 파일 업로드 핸들러 제공
    - 정식 업로드 핸들러 제공
    - 파일 다운로드 핸들러 제공

4. **파일 삭제 기능**

    - 단일 파일 삭제
    - 여러 파일 삭제
    - 임시 파일만 삭제
    - 고아 파일 정리 기능 (비활성화됨)

## 주요 클래스 및 메서드

### **`getInstance()`**

싱글턴 인스턴스를 반환하는 정적 메서드입니다.

```typescript
const drive = Drive.getInstance();
```

### **`configs(options: DriveOptions)`**

드라이브의 설정을 변경하는 메서드입니다.

- 로컬 저장소 설정 시 `localPath` 필수
- 클라우드 저장소 설정 시 `provider` 옵션 필요

### **파일 작업 메서드**

#### **`getMetadata(fileId: string): Promise<IFileMetadata | null>`**

특정 파일의 메타데이터를 가져옵니다.

```typescript
const metadata = await drive.getMetadata("file123");
```

#### **`getBuffer(fileId: string): Promise<Buffer[] | null>`**

파일의 버퍼 데이터를 반환합니다.

```typescript
const buffer = await drive.getBuffer("file123");
```

#### **`confirm(fileId: string): Promise<void>`**

임시 파일을 정식 파일로 승격합니다.

```typescript
await drive.confirm("file123");
```

#### **파일 업로드 및 다운로드 핸들러**

- **`uploadTmpHandler(): RequestHandler`** → 임시 파일 업로드 핸들러 반환
- **`uploadHandler(): RequestHandler`** → 정식 업로드 핸들러 반환
- **`downloadFileHandler(fileId: string): RequestHandler`** → 파일 다운로드 핸들러 반환

```typescript
app.post("/upload", drive.uploadHandler());
```

#### **파일 삭제 메서드**

- **`deleteOne(fileId: string): Promise<void>`** → 특정 파일 삭제
- **`deleteMany(fileIdList: string[]): Promise<number>`** → 여러 파일 삭제
- **`deleteTmp(req: Express.Request): Promise<number>`** → 임시 파일만 삭제 (Request Handler를 요구합니다. 내부에서 req 객체를 찾아 자동 삭제합니다.)

```typescript
await drive.deleteOne("file123");
```

## 향후 개선 사항

1. Google Cloud Storage 및 Azure Blob Storage 지원 추가
2. 클라우드 파일 관리 로직 강화
3. 미사용 파일 자동 정리 기능 추가

이 클래스는 파일 저장소 작업을 효율적으로 수행할 수 있도록 설계되었습니다. 프로젝트 내에서 파일 관리가 필요하다면 **Drive** 클래스를 활용하세요!

## Filedrive.Drive 클래스 사용 예시

```json
// package.json
{
    "name": "test_runtime_drive",
    "version": "1.0.0",
    "main": "index.js",
    "scripts": {
        "start": "node app"
    },
    "description": "파일 드라이브 테스트",
    "dependencies": {
        "@serverkit-lib/runtime-package": 0.0.47,
        "express": "^4.21.2"
    }
}
```

```javascript
// app.js
const { driveInstance } = require("@serverkit-lib/runtime-package");
const express = require("express");
const path = require("path");

// Logger: Info
function log(message) {
    console.log(`\x1b[34m ℹ️ ${message}\x1b[0m`);
}

// Express 앱 초기화
const app = express();
app.use(express.urlencoded({ extended: true })); // URL-encoded 데이터 처리
app.use((err, req, res, next) => {
    console.error("Global Error Handler:", err);
    res.status(500).json({
        error: err.message || "Internal Server Error",
        details: err.details || "No additional details",
        stack: err.stack || "No stack trace available"
    });
});

// 파일 드라이브 설정 초기화 (선택 사항).
driveInstance.configs({
    type: "local",
    localPath: path.resolve(__dirname, "upload")
});

// tmp 파일 업로드 핸들러 설정
// ※ app.use(express.json()) 보다 먼저 호출해야 tmp 파일 업로드, 메타데이터를 추출하고
// req.body에 해당 키에 파일 키 매핑이 가능합니다.
app.use(driveInstance.uploadTmpHandler());
app.use(express.json());

// 메타데이터 가져오기
app.get("/file/metadata/:id", async (req: express.Request, res: express.Response) => {
    try {
        const param: string = req.params["id"] ?? "";
        const files: IFileMetadata | null = await driveInstance.getMetadata(param);
        res.send(files);
    } catch (error) {
        console.log(error);
        res.status(500).json(error);
    }
});

// 일반 파일 업로드
app.post("/file/upload", driveInstance.uploadHandler());


// tmp 파일 승격 처리
app.post("/file/confirm", async (req: express.Request, res: express.Response) => {
    const id: string = req.body["id"] ?? "";
    try {
        await driveInstance.confirm(id);
        res.send("정상 처리");
    } catch (error) {
        res.status(500).json(error);
    }
});

// 파일 다운로드 처리.
app.get("/file/download/:fileId", driveInstance.downloadFileHandler());

// 서버 시작
const PORT = 7777;
app.listen(PORT, () => {
    log(`Test server running at localhost:${PORT}`);
});
```