npm package: @ccr_team/sg-word-filter-node
Description: 금칙어 필터링 모듈 - 데이터 주입 방식
Author: Your Organization
Latest version: 1.0.2

# sg-word-filter-node

[![npm version](https://img.shields.io/npm/v/sg-word-filter-node.svg)](https://www.npmjs.com/package/sg-word-filter-node)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

고성능 금칙어 필터링 모듈 (Node.js) - 정확 일치 및 부분 문자열 검색 지원

## ✨ 특징

- ⚡ **빠른 검색**: Set 기반 O(1) 정확 일치 + Aho-Corasick 부분 문자열 검색
- 🔒 **싱글톤 패턴**: 앱 라이프사이클 동안 단 한 번만 초기화
- 🛡️ **타입 안전**: 완전한 에러 처리 및 검증
- 🌐 **다국어 지원**: UTF-8 완벽 지원 (한글, 영어, 일본어 등)
- 📦 **경량**: 핵심 기능만 포함 (압축 크기 ~6KB)
- 🧪 **완벽한 테스트**: 100% 코드 커버리지

## 📦 설치

### npm (공개 레지스트리)

```bash
npm install sg-word-filter-node
```

### GitHub Packages (인하우스)

1. `.npmrc` 파일 설정:

```ini
@yourorg:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=YOUR_GITHUB_PAT
```

2. 설치:

```bash
npm install @yourorg/sg-word-filter-node
```

자세한 배포 가이드는 [DEPLOY.md](./DEPLOY.md)를 참조하세요.

## 🚀 빠른 시작

### 1. 금칙어 파일 준비 (`config/blocklist.json`)

```json
["나쁜말", "욕설", "비속어", "금지어"]
```

### 2. 모듈 초기화 및 사용

```javascript
const sgWordFilter = require('sg-word-filter-node');
const path = require('path');

// 애플리케이션 시작 시 초기화 (단 한 번만!)
const blocklistPath = path.join(__dirname, 'config', 'blocklist.json');
sgWordFilter.init(blocklistPath);

// ===== 정확 일치 검색 =====
console.log(sgWordFilter.isForbidden('나쁜말'));  // true
console.log(sgWordFilter.isForbidden('정상단어'));  // false

// 텍스트에서 금칙어 찾기
const words = sgWordFilter.findForbiddenWords('이것은 나쁜말 과 욕설 입니다');
console.log(words);  // ['나쁜말', '욕설']

// 금칙어 포함 여부만 확인
const hasBad = sgWordFilter.hasForbiddenWords('이것은 나쁜말입니다');
console.log(hasBad);  // true

// ===== 부분 문자열 검색 (Aho-Corasick) =====
const results = sgWordFilter.findForbiddenSubstrings('나쁜말과욕설입니다');
console.log(results);
// [
//   { word: '나쁜말', index: 0 },
//   { word: '욕설', index: 5 }
// ]

// 부분 문자열 포함 여부
const hasSubstring = sgWordFilter.hasForbiddenSubstrings('나쁜것');
console.log(hasSubstring);  // true (금칙어 목록에 "나쁜"이 있다면)
```

## 📖 API 레퍼런스

### 초기화 및 상태 관리

#### `init(filePath, options)`

금칙어 목록을 파일에서 로드하여 초기화합니다. 앱 라이프사이클 중 **단 한 번만** 호출되어야 합니다.

```javascript
sgWordFilter.init('./config/blocklist.json', { silent: true });
```

**Parameters:**
- `filePath` (string): JSON 배열 형태의 금칙어 파일 경로
- `options` (object, optional):
  - `silent` (boolean): true일 경우 콘솔 로그 출력 억제 (기본값: false)

**Throws:**
- `FilterError`: 파일을 찾을 수 없거나, JSON 형식이 잘못되었거나, 배열이 아닌 경우

#### `getStatus()`

모듈의 현재 상태 정보를 반환합니다.

```javascript
const status = sgWordFilter.getStatus();
console.log(status);
// {
//   initialized: true,
//   count: 10,
//   filePath: '/absolute/path/to/blocklist.json',
//   loadedAt: '2025-11-18T10:30:00.000Z'
// }
```

**Returns:** `{ initialized, count, filePath, loadedAt }`

#### `reset()`

모듈을 초기화 이전 상태로 되돌립니다. 주로 테스트 용도로 사용됩니다.

```javascript
sgWordFilter.reset();
```

### 정확 일치 검색

#### `isForbidden(word)`

단어가 금칙어 목록에 **정확히 일치**하는지 확인합니다.

```javascript
sgWordFilter.isForbidden('나쁜말');  // true
sgWordFilter.isForbidden('나쁜');    // false (부분 일치는 false)
```

**Parameters:**
- `word` (string): 확인할 단어

**Returns:** `boolean`

#### `findForbiddenWords(text, options)`

텍스트를 단어 단위로 분리하여 금칙어를 찾습니다.

```javascript
const words = sgWordFilter.findForbiddenWords('나쁜말 과 욕설');
// ['나쁜말', '욕설']

// 사용자 정의 구분자
const words2 = sgWordFilter.findForbiddenWords('나쁜말,욕설,정상', { delimiter: ',' });
// ['나쁜말', '욕설']
```

**Parameters:**
- `text` (string): 검색할 텍스트
- `options` (object, optional):
  - `delimiter` (string|RegExp): 단어 구분자 (기본값: `/\s+/`)

**Returns:** `string[]` - 발견된 금칙어 배열

#### `hasForbiddenWords(text, options)`

텍스트에 금칙어가 포함되어 있는지 확인합니다.

```javascript
sgWordFilter.hasForbiddenWords('이것은 나쁜말입니다');  // true
```

**Parameters:**
- `text` (string): 검색할 텍스트
- `options` (object, optional): `findForbiddenWords()`와 동일

**Returns:** `boolean`

### 부분 문자열 검색 (Aho-Corasick)

#### `findForbiddenSubstrings(text, options)`

Aho-Corasick 알고리즘을 사용하여 텍스트에서 금칙어를 **부분 문자열**로 검색합니다.

```javascript
// 금칙어 목록: ['나쁜', '욕']
const results = sgWordFilter.findForbiddenSubstrings('나쁜말과 욕설입니다');
// [
//   { word: '나쁜', index: 0 },
//   { word: '욕', index: 6 }
// ]

// 대소문자 구분
const results2 = sgWordFilter.findForbiddenSubstrings('Bad WORST', { caseSensitive: true });
```

**Parameters:**
- `text` (string): 검색할 텍스트
- `options` (object, optional):
  - `caseSensitive` (boolean): 대소문자 구분 여부 (기본값: false)
  - `whitelist` (string[]): 예외 처리할 정상 단어 목록 (기본값: DEFAULT_WHITELIST)
  - `useWhitelist` (boolean): 화이트리스트 사용 여부 (기본값: true)

**Returns:** `Array<{word: string, index: number}>` - 발견된 금칙어와 위치 배열

**화이트리스트 기능:**
금칙어가 정상 단어(예: "홈페이지", "결제") 내부에 포함된 경우 오탐을 방지합니다.

```javascript
// 금칙어 목록: ['페이', '결재']
// 기본 화이트리스트: ['홈페이지', '웹페이지', '결제', '페이스북', ...]

// 화이트리스트 적용 (기본)
sgWordFilter.findForbiddenSubstrings('홈페이지 방문');
// [] - "페이"가 "홈페이지" 내부에 있으므로 제외됨

// 화이트리스트 비활성화
sgWordFilter.findForbiddenSubstrings('홈페이지 방문', { useWhitelist: false });
// [{ word: '페이', index: 2 }] - 모든 패턴 감지

// 커스텀 화이트리스트
sgWordFilter.findForbiddenSubstrings('내페이지', { 
  whitelist: ['내페이지'] 
});
// [] - 커스텀 화이트리스트 적용
```

**시간 복잡도:** O(n + m) - n: 텍스트 길이, m: 매칭 결과 수

#### `hasForbiddenSubstrings(text, options)`

텍스트에 금칙어 부분 문자열이 포함되어 있는지 확인합니다.

```javascript
// 금칙어 목록: ['나쁜']
sgWordFilter.hasForbiddenSubstrings('나쁜말');  // true
sgWordFilter.hasForbiddenSubstrings('좋은말');  // false
```

**Parameters:**
- `text` (string): 검색할 텍스트
- `options` (object, optional): `findForbiddenSubstrings()`와 동일

**Returns:** `boolean`

### 에러 클래스

#### `FilterError`

모듈에서 발생하는 모든 에러는 `FilterError` 클래스를 사용합니다.

```javascript
try {
  sgWordFilter.init('/invalid/path.json');
} catch (error) {
  if (error instanceof sgWordFilter.FilterError) {
    console.error(`에러 코드: ${error.code}`);
    console.error(`메시지: ${error.message}`);
  }
}
```

**에러 코드:**
- `FILE_NOT_FOUND`: 파일을 찾을 수 없음
- `INVALID_JSON`: JSON 파싱 실패
- `INVALID_FORMAT`: 배열 형태가 아님
- `NOT_INITIALIZED`: 초기화되지 않은 상태에서 함수 호출
- `INVALID_INPUT`: 잘못된 입력 타입
- `UNEXPECTED_ERROR`: 예상치 못한 오류

## 🎯 사용 사례

### Express.js 미들웨어

```javascript
const express = require('express');
const sgWordFilter = require('sg-word-filter-node');

const app = express();
app.use(express.json());

// 앱 시작 시 초기화
sgWordFilter.init('./config/blocklist.json');

// 미들웨어로 사용
app.use((req, res, next) => {
  if (req.body.comment) {
    const badWords = sgWordFilter.findForbiddenWords(req.body.comment);
    if (badWords.length > 0) {
      return res.status(400).json({
        error: '금칙어가 포함되어 있습니다',
        words: badWords
      });
    }
  }
  next();
});

app.post('/comment', (req, res) => {
  res.json({ success: true });
});

app.listen(3000);
```

### 채팅 시스템

```javascript
const sgWordFilter = require('sg-word-filter-node');

class ChatSystem {
  constructor() {
    sgWordFilter.init('./config/blocklist.json', { silent: true });
  }

  sendMessage(userId, message) {
    // 부분 문자열 검색으로 우회 시도 방지
    const violations = sgWordFilter.findForbiddenSubstrings(message);
    
    if (violations.length > 0) {
      return {
        success: false,
        error: '메시지에 부적절한 내용이 포함되어 있습니다',
        violations: violations
      };
    }

    // 메시지 전송 로직
    return { success: true };
  }
}
```

## 🔬 알고리즘 비교

| 기능 | 정확 일치 (Set) | 부분 문자열 (Aho-Corasick) |
|------|----------------|---------------------------|
| 시간 복잡도 | O(1) | O(n + m) |
| 공간 복잡도 | O(k) | O(k * avg_len) |
| 사용 케이스 | "나쁜말" 전체 검출 | "나쁜" → "나쁜말", "나쁜것" 검출 |
| 우회 방지 | 약함 | 강함 |

- k: 금칙어 개수
- n: 텍스트 길이
- m: 매칭 결과 수
- avg_len: 평균 금칙어 길이

## 📊 성능

```javascript
// 벤치마크 (10,000개 금칙어, 1,000자 텍스트)
init():                   ~50ms  (최초 1회만)
isForbidden():            ~0.001ms (Set lookup)
findForbiddenWords():     ~0.5ms  (텍스트 분리 + Set lookup)
findForbiddenSubstrings(): ~2ms   (Aho-Corasick)
```

## 🧪 테스트

```bash
# 전체 테스트 실행
npm test

# 커버리지 확인
npm run test:coverage

# Watch 모드
npm run test:watch
```

## 📝 변경 로그

[CHANGELOG.md](./CHANGELOG.md) 참조

## 🤝 기여

이슈 제기 및 풀 리퀘스트를 환영합니다!

1. Fork the Project
2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
4. Push to the Branch (`git push origin feature/AmazingFeature`)
5. Open a Pull Request

## 📄 라이센스

MIT License - [LICENSE](./LICENSE) 파일 참조

## 👥 관련 프로젝트

- [sg-word-filter-python](https://github.com/yourorg/sg-word-filter-python) - Python 버전

## 📞 지원

- 🐛 버그 리포트: [Issues](https://github.com/yourorg/sg-word-filter-node/issues)
- 💬 질문 및 토론: [Discussions](https://github.com/yourorg/sg-word-filter-node/discussions)
- 📧 이메일: dev@yourcompany.com

---

**Made with ❤️ by Your Organization**