bjd-code 0.0.1 → 0.1.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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +116 -2
  3. package/dist/cli/index.d.ts +3 -0
  4. package/dist/cli/index.d.ts.map +1 -0
  5. package/dist/cli/index.js +114 -0
  6. package/dist/cli/index.js.map +1 -0
  7. package/dist/core/download.d.ts +12 -0
  8. package/dist/core/download.d.ts.map +1 -0
  9. package/dist/core/download.js +34 -0
  10. package/dist/core/download.js.map +1 -0
  11. package/dist/core/filter.d.ts +14 -0
  12. package/dist/core/filter.d.ts.map +1 -0
  13. package/dist/core/filter.js +30 -0
  14. package/dist/core/filter.js.map +1 -0
  15. package/dist/core/parser.d.ts +5 -0
  16. package/dist/core/parser.d.ts.map +1 -0
  17. package/dist/core/parser.js +70 -0
  18. package/dist/core/parser.js.map +1 -0
  19. package/dist/core/tree.d.ts +15 -0
  20. package/dist/core/tree.d.ts.map +1 -0
  21. package/dist/core/tree.js +95 -0
  22. package/dist/core/tree.js.map +1 -0
  23. package/dist/index.d.ts +17 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +37 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/types/index.d.ts +70 -0
  28. package/dist/types/index.d.ts.map +1 -0
  29. package/dist/types/index.js +2 -0
  30. package/dist/types/index.js.map +1 -0
  31. package/dist/utils/code.d.ts +34 -0
  32. package/dist/utils/code.d.ts.map +1 -0
  33. package/dist/utils/code.js +68 -0
  34. package/dist/utils/code.js.map +1 -0
  35. package/dist/utils/normalize.d.ts +4 -0
  36. package/dist/utils/normalize.d.ts.map +1 -0
  37. package/dist/utils/normalize.js +21 -0
  38. package/dist/utils/normalize.js.map +1 -0
  39. package/dist/utils/paths.d.ts +13 -0
  40. package/dist/utils/paths.d.ts.map +1 -0
  41. package/dist/utils/paths.js +24 -0
  42. package/dist/utils/paths.js.map +1 -0
  43. package/dist/worker/csv-worker.d.ts +2 -0
  44. package/dist/worker/csv-worker.d.ts.map +1 -0
  45. package/dist/worker/csv-worker.js +33 -0
  46. package/dist/worker/csv-worker.js.map +1 -0
  47. package/package.json +39 -9
  48. package/.env.example +0 -0
  49. package/.idea/bjd-code.iml +0 -12
  50. package/.idea/jsLibraryMappings.xml +0 -6
  51. package/.idea/misc.xml +0 -6
  52. package/.idea/modules.xml +0 -8
  53. package/.idea/vcs.xml +0 -6
  54. package/index.ts +0 -122
  55. package/tsconfig.json +0 -100
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 song
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,5 +1,119 @@
1
1
  # bjd-code (법정동 코드)
2
2
 
3
- [국토교통부_전국 법정동_20201119 API](https://www.data.go.kr/tcs/dss/selectFileDataDetailView.do?publicDataPk=15063424) 이용합니다.
3
+ [국토교통부_전국 법정동 CSV](https://www.data.go.kr/tcs/dss/selectFileDataDetailView.do?publicDataPk=15063424) 데이터를 파싱, 필터링, 트리 구조화하는 Node.js/TypeScript 라이브러리.
4
4
 
5
- 1. worker threads를 이용하여
5
+ ## 기능
6
+
7
+ - CSV 파싱 (EUC-KR/UTF-8 자동 처리, worker_threads 지원)
8
+ - 시도/시군구/읍면동/리 레벨별 필터링
9
+ - 이름 검색
10
+ - 트리 구조 변환 (시도 → 시군구 → 읍면동 → 리)
11
+ - CLI 도구 (다운로드, 파싱, 필터링, 트리, 검색)
12
+
13
+ ## 설치
14
+
15
+ ```bash
16
+ npm install bjd-code
17
+ ```
18
+
19
+ ## 빠른 시작
20
+
21
+ ```bash
22
+ # 1. 데이터 다운로드 (~/.bjd-code/data.csv에 저장)
23
+ bjd download
24
+
25
+ # 2. 바로 사용 (파일 경로 생략 가능)
26
+ bjd parse --pretty
27
+ bjd filter --level sido --pretty
28
+ bjd tree --pretty
29
+ bjd search 강남구
30
+ ```
31
+
32
+ ## 사용법
33
+
34
+ ### 라이브러리
35
+
36
+ ```typescript
37
+ import { parseCSV, filterSido, buildTree, loadTree, searchByName, downloadCSV } from 'bjd-code';
38
+
39
+ // 데이터 다운로드 (최초 1회)
40
+ await downloadCSV();
41
+
42
+ // CSV 파싱 (경로 생략 시 ~/.bjd-code/data.csv 사용)
43
+ const records = await parseCSV();
44
+
45
+ // 직접 파일 경로 지정도 가능
46
+ const records2 = await parseCSV({ filePath: './my-data.csv' });
47
+
48
+ // 시도만 추출
49
+ const sidos = filterSido(records);
50
+
51
+ // 트리 구조 빌드
52
+ const tree = buildTree(records.filter(r => r.isActive));
53
+
54
+ // 편의 함수 (파싱 + 트리 한번에)
55
+ const tree2 = await loadTree();
56
+
57
+ // 이름 검색
58
+ const results = searchByName(records, '종로구');
59
+ ```
60
+
61
+ ### CLI
62
+
63
+ ```bash
64
+ # 데이터 다운로드
65
+ bjd download # ~/.bjd-code/data.csv에 저장
66
+ bjd download -o ./data/bjd.csv # 경로 지정
67
+ bjd download --url <url> # URL 직접 지정
68
+
69
+ # 전체 데이터 파싱
70
+ bjd parse # 다운로드된 데이터 사용
71
+ bjd parse ./my-data.csv # 파일 경로 직접 지정
72
+
73
+ # 레벨별 필터링
74
+ bjd filter --level sido --pretty
75
+ bjd filter ./data.csv --level sigungu
76
+
77
+ # 트리 구조
78
+ bjd tree --pretty -o tree.json
79
+
80
+ # 이름 검색
81
+ bjd search 강남구
82
+
83
+ # 공통 옵션
84
+ # --include-inactive 폐지된 코드 포함
85
+ # --pretty JSON 포맷팅
86
+ # -o, --output <file> 파일로 저장
87
+ # --encoding <enc> CSV 인코딩 (기본: euc-kr)
88
+ ```
89
+
90
+ ## 코드 구조
91
+
92
+ 법정동 코드는 10자리 숫자로 구성:
93
+
94
+ | 위치 | 자릿수 | 레벨 | 예시 |
95
+ |------|--------|------|------|
96
+ | 1-2 | 시도 | `sido` | 11 (서울) |
97
+ | 3-5 | 시군구 | `sigungu` | 110 (종로구) |
98
+ | 6-8 | 읍면동 | `eupmyeondong` | 101 (청운동) |
99
+ | 9-10 | 리 | `ri` | 21 (송산리) |
100
+
101
+ 뒷자리가 0으로 채워진 패턴으로 레벨을 판단합니다:
102
+ - `1100000000` → 시도 (서울특별시)
103
+ - `1111000000` → 시군구 (종로구)
104
+ - `1111010100` → 읍면동 (청운동)
105
+ - `4372025021` → 리 (송산리)
106
+
107
+ ## 개발
108
+
109
+ ```bash
110
+ npm install
111
+ npm run build
112
+ npm test
113
+ ```
114
+
115
+ ## 기술 스택
116
+
117
+ - Node.js 24+, ESM
118
+ - TypeScript, Vitest
119
+ - csv-parse, iconv-lite, commander
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env node
2
+ import { writeFileSync } from 'node:fs';
3
+ import { Command, InvalidArgumentError } from 'commander';
4
+ import { parseCSV } from '../core/parser.js';
5
+ import { filterByLevel, searchByName } from '../core/filter.js';
6
+ import { buildTree } from '../core/tree.js';
7
+ import { downloadCSV, MANUAL_INSTRUCTIONS } from '../core/download.js';
8
+ const VALID_LEVELS = ['sido', 'sigungu', 'eupmyeondong', 'ri'];
9
+ function parseLevel(value) {
10
+ if (!VALID_LEVELS.includes(value)) {
11
+ throw new InvalidArgumentError(`유효하지 않은 레벨: "${value}" (가능한 값: ${VALID_LEVELS.join(', ')})`);
12
+ }
13
+ return value;
14
+ }
15
+ const program = new Command();
16
+ program
17
+ .name('bjd')
18
+ .description('법정동 코드 파싱/필터링/트리 구조화 CLI')
19
+ .version('0.1.0');
20
+ function output(data, opts) {
21
+ const json = opts.pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data);
22
+ if (opts.output) {
23
+ writeFileSync(opts.output, json, 'utf-8');
24
+ console.log(`결과가 ${opts.output}에 저장되었습니다.`);
25
+ }
26
+ else {
27
+ console.log(json);
28
+ }
29
+ }
30
+ function addCommonOptions(cmd) {
31
+ return cmd
32
+ .option('-f, --file <path>', 'CSV 파일 경로 (기본: ~/.bjd-code/data.csv)')
33
+ .option('--include-inactive', '폐지된 코드 포함', false)
34
+ .option('--pretty', 'JSON 예쁘게 출력', false)
35
+ .option('-o, --output <file>', '결과를 파일로 저장')
36
+ .option('--encoding <encoding>', 'CSV 인코딩', 'euc-kr');
37
+ }
38
+ function handleError(err) {
39
+ const message = err instanceof Error ? err.message : String(err);
40
+ console.error(`오류: ${message}`);
41
+ process.exit(1);
42
+ }
43
+ program
44
+ .command('download')
45
+ .description('법정동 코드 CSV 다운로드')
46
+ .option('--url <url>', '다운로드 URL 직접 지정')
47
+ .option('-o, --output <path>', '저장 경로 (기본: ~/.bjd-code/data.csv)')
48
+ .action(async (opts) => {
49
+ try {
50
+ console.log('다운로드 중...');
51
+ const savedPath = await downloadCSV({ url: opts.url, outputPath: opts.output });
52
+ console.log(`저장 완료: ${savedPath}`);
53
+ }
54
+ catch (err) {
55
+ const message = err instanceof Error ? err.message : String(err);
56
+ console.error(`다운로드 실패: ${message}`);
57
+ console.error(`\n수동 다운로드 방법:\n${MANUAL_INSTRUCTIONS}`);
58
+ process.exit(1);
59
+ }
60
+ });
61
+ addCommonOptions(program
62
+ .command('parse')
63
+ .description('CSV 파일을 파싱하여 전체 데이터 JSON 출력')).action(async (opts) => {
64
+ try {
65
+ const records = await parseCSV({ filePath: opts.file, encoding: opts.encoding, useWorker: false });
66
+ const result = opts.includeInactive ? records : records.filter((r) => r.isActive);
67
+ output(result, opts);
68
+ }
69
+ catch (err) {
70
+ handleError(err);
71
+ }
72
+ });
73
+ addCommonOptions(program
74
+ .command('filter')
75
+ .description('레벨별 필터링')
76
+ .requiredOption('--level <level>', '필터 레벨 (sido, sigungu, eupmyeondong, ri)', parseLevel)).action(async (opts) => {
77
+ try {
78
+ const records = await parseCSV({ filePath: opts.file, encoding: opts.encoding, useWorker: false });
79
+ const filterOpts = { includeInactive: opts.includeInactive };
80
+ const result = filterByLevel(records, opts.level, filterOpts);
81
+ output(result, opts);
82
+ }
83
+ catch (err) {
84
+ handleError(err);
85
+ }
86
+ });
87
+ addCommonOptions(program
88
+ .command('tree')
89
+ .description('트리 구조 JSON 출력')).action(async (opts) => {
90
+ try {
91
+ const records = await parseCSV({ filePath: opts.file, encoding: opts.encoding, useWorker: false });
92
+ const active = opts.includeInactive ? records : records.filter((r) => r.isActive);
93
+ const tree = buildTree(active);
94
+ output(tree, opts);
95
+ }
96
+ catch (err) {
97
+ handleError(err);
98
+ }
99
+ });
100
+ addCommonOptions(program
101
+ .command('search <query>')
102
+ .description('이름으로 검색')).action(async (query, opts) => {
103
+ try {
104
+ const records = await parseCSV({ filePath: opts.file, encoding: opts.encoding, useWorker: false });
105
+ const filterOpts = { includeInactive: opts.includeInactive };
106
+ const result = searchByName(records, query, filterOpts);
107
+ output(result, opts);
108
+ }
109
+ catch (err) {
110
+ handleError(err);
111
+ }
112
+ });
113
+ program.parse();
114
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import { DATA_DIR } from '../utils/paths.js';
2
+ export interface DownloadOptions {
3
+ /** 다운로드 URL (기본: data.go.kr) */
4
+ url?: string;
5
+ /** 저장 경로 (기본: ~/.bjd-code/data.csv) */
6
+ outputPath?: string;
7
+ }
8
+ /** 법정동 코드 CSV 다운로드 */
9
+ export declare function downloadCSV(options?: DownloadOptions): Promise<string>;
10
+ declare const MANUAL_INSTRUCTIONS: string;
11
+ export { MANUAL_INSTRUCTIONS, DATA_DIR };
12
+ //# sourceMappingURL=download.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../src/core/download.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,QAAQ,EAAoB,MAAM,mBAAmB,CAAC;AAK/D,MAAM,WAAW,eAAe;IAC9B,gCAAgC;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,sBAAsB;AACtB,wBAAsB,WAAW,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAyB5E;AAED,QAAA,MAAM,mBAAmB,QAIgC,CAAC;AAE1D,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,CAAC"}
@@ -0,0 +1,34 @@
1
+ import { createWriteStream, mkdirSync } from 'node:fs';
2
+ import { pipeline } from 'node:stream/promises';
3
+ import { Readable } from 'node:stream';
4
+ import { dirname } from 'node:path';
5
+ import { DATA_DIR, DEFAULT_CSV_PATH } from '../utils/paths.js';
6
+ const DOWNLOAD_URL = 'https://www.data.go.kr/cmm/cmm/fileDownload.do?atchFileId=FILE_000000002647058&fileDetailSn=1';
7
+ /** 법정동 코드 CSV 다운로드 */
8
+ export async function downloadCSV(options) {
9
+ const url = options?.url ?? DOWNLOAD_URL;
10
+ const outputPath = options?.outputPath ?? DEFAULT_CSV_PATH;
11
+ mkdirSync(dirname(outputPath), { recursive: true });
12
+ const response = await fetch(url, {
13
+ headers: {
14
+ 'User-Agent': 'bjd-code/0.1.0',
15
+ },
16
+ redirect: 'follow',
17
+ });
18
+ if (!response.ok) {
19
+ throw new Error(`다운로드 실패 (HTTP ${response.status}). 수동으로 다운로드하세요:\n${MANUAL_INSTRUCTIONS}`);
20
+ }
21
+ if (!response.body) {
22
+ throw new Error('응답 본문이 비어있습니다.');
23
+ }
24
+ const body = Readable.fromWeb(response.body);
25
+ await pipeline(body, createWriteStream(outputPath));
26
+ return outputPath;
27
+ }
28
+ const MANUAL_INSTRUCTIONS = `
29
+ 1. https://www.data.go.kr/tcs/dss/selectFileDataDetailView.do?publicDataPk=15063424 접속
30
+ 2. CSV 파일 다운로드
31
+ 3. 다운로드한 파일을 다음 경로에 저장: ${DEFAULT_CSV_PATH}
32
+ 또는 CLI 사용 시 파일 경로를 직접 지정: bjd parse ./파일경로.csv`.trim();
33
+ export { MANUAL_INSTRUCTIONS, DATA_DIR };
34
+ //# sourceMappingURL=download.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"download.js","sourceRoot":"","sources":["../../src/core/download.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE/D,MAAM,YAAY,GAChB,+FAA+F,CAAC;AASlG,sBAAsB;AACtB,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAyB;IACzD,MAAM,GAAG,GAAG,OAAO,EAAE,GAAG,IAAI,YAAY,CAAC;IACzC,MAAM,UAAU,GAAG,OAAO,EAAE,UAAU,IAAI,gBAAgB,CAAC;IAE3D,SAAS,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEpD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAChC,OAAO,EAAE;YACP,YAAY,EAAE,gBAAgB;SAC/B;QACD,QAAQ,EAAE,QAAQ;KACnB,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,iBAAiB,QAAQ,CAAC,MAAM,qBAAqB,mBAAmB,EAAE,CAAC,CAAC;IAC9F,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,gBAAgB,CAAC,CAAC;IACpC,CAAC;IAED,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAgD,CAAC,CAAC;IACzF,MAAM,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC,UAAU,CAAC,CAAC,CAAC;IAEpD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,MAAM,mBAAmB,GAAG;;;0BAGF,gBAAgB;kDACQ,CAAC,IAAI,EAAE,CAAC;AAE1D,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { BjdLevel, BjdRecord, FilterOptions } from '../types/index.js';
2
+ /** 시도 필터링 */
3
+ export declare function filterSido(records: BjdRecord[], options?: FilterOptions): BjdRecord[];
4
+ /** 시군구 필터링 */
5
+ export declare function filterSigungu(records: BjdRecord[], options?: FilterOptions): BjdRecord[];
6
+ /** 읍면동 필터링 */
7
+ export declare function filterEupmyeondong(records: BjdRecord[], options?: FilterOptions): BjdRecord[];
8
+ /** 리 필터링 */
9
+ export declare function filterRi(records: BjdRecord[], options?: FilterOptions): BjdRecord[];
10
+ /** 레벨별 필터링 (단일 순회) */
11
+ export declare function filterByLevel(records: BjdRecord[], level: BjdLevel, options?: FilterOptions): BjdRecord[];
12
+ /** 이름 부분 검색 (단일 순회) */
13
+ export declare function searchByName(records: BjdRecord[], query: string, options?: FilterOptions): BjdRecord[];
14
+ //# sourceMappingURL=filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter.d.ts","sourceRoot":"","sources":["../../src/core/filter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAE5E,aAAa;AACb,wBAAgB,UAAU,CAAC,OAAO,EAAE,SAAS,EAAE,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,SAAS,EAAE,CAErF;AAED,cAAc;AACd,wBAAgB,aAAa,CAAC,OAAO,EAAE,SAAS,EAAE,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,SAAS,EAAE,CAExF;AAED,cAAc;AACd,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,SAAS,EAAE,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,SAAS,EAAE,CAE7F;AAED,YAAY;AACZ,wBAAgB,QAAQ,CAAC,OAAO,EAAE,SAAS,EAAE,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,SAAS,EAAE,CAEnF;AAED,sBAAsB;AACtB,wBAAgB,aAAa,CAC3B,OAAO,EAAE,SAAS,EAAE,EACpB,KAAK,EAAE,QAAQ,EACf,OAAO,CAAC,EAAE,aAAa,GACtB,SAAS,EAAE,CAGb;AAED,uBAAuB;AACvB,wBAAgB,YAAY,CAC1B,OAAO,EAAE,SAAS,EAAE,EACpB,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,aAAa,GACtB,SAAS,EAAE,CAKb"}
@@ -0,0 +1,30 @@
1
+ /** 시도 필터링 */
2
+ export function filterSido(records, options) {
3
+ return filterByLevel(records, 'sido', options);
4
+ }
5
+ /** 시군구 필터링 */
6
+ export function filterSigungu(records, options) {
7
+ return filterByLevel(records, 'sigungu', options);
8
+ }
9
+ /** 읍면동 필터링 */
10
+ export function filterEupmyeondong(records, options) {
11
+ return filterByLevel(records, 'eupmyeondong', options);
12
+ }
13
+ /** 리 필터링 */
14
+ export function filterRi(records, options) {
15
+ return filterByLevel(records, 'ri', options);
16
+ }
17
+ /** 레벨별 필터링 (단일 순회) */
18
+ export function filterByLevel(records, level, options) {
19
+ const includeInactive = options?.includeInactive ?? false;
20
+ return records.filter((r) => r.level === level && (includeInactive || r.isActive));
21
+ }
22
+ /** 이름 부분 검색 (단일 순회) */
23
+ export function searchByName(records, query, options) {
24
+ const q = query.trim();
25
+ if (!q)
26
+ return [];
27
+ const includeInactive = options?.includeInactive ?? false;
28
+ return records.filter((r) => r.name.includes(q) && (includeInactive || r.isActive));
29
+ }
30
+ //# sourceMappingURL=filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter.js","sourceRoot":"","sources":["../../src/core/filter.ts"],"names":[],"mappings":"AAEA,aAAa;AACb,MAAM,UAAU,UAAU,CAAC,OAAoB,EAAE,OAAuB;IACtE,OAAO,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;AACjD,CAAC;AAED,cAAc;AACd,MAAM,UAAU,aAAa,CAAC,OAAoB,EAAE,OAAuB;IACzE,OAAO,aAAa,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;AACpD,CAAC;AAED,cAAc;AACd,MAAM,UAAU,kBAAkB,CAAC,OAAoB,EAAE,OAAuB;IAC9E,OAAO,aAAa,CAAC,OAAO,EAAE,cAAc,EAAE,OAAO,CAAC,CAAC;AACzD,CAAC;AAED,YAAY;AACZ,MAAM,UAAU,QAAQ,CAAC,OAAoB,EAAE,OAAuB;IACpE,OAAO,aAAa,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;AAC/C,CAAC;AAED,sBAAsB;AACtB,MAAM,UAAU,aAAa,CAC3B,OAAoB,EACpB,KAAe,EACf,OAAuB;IAEvB,MAAM,eAAe,GAAG,OAAO,EAAE,eAAe,IAAI,KAAK,CAAC;IAC1D,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,IAAI,CAAC,eAAe,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;AACrF,CAAC;AAED,uBAAuB;AACvB,MAAM,UAAU,YAAY,CAC1B,OAAoB,EACpB,KAAa,EACb,OAAuB;IAEvB,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IACvB,IAAI,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IAClB,MAAM,eAAe,GAAG,OAAO,EAAE,eAAe,IAAI,KAAK,CAAC;IAC1D,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;AACtF,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { BjdRecord, ParseOptions } from '../types/index.js';
2
+ export { normalizeRow } from '../utils/normalize.js';
3
+ /** CSV 파싱 → BjdRecord[] */
4
+ export declare function parseCSV(options?: ParseOptions): Promise<BjdRecord[]>;
5
+ //# sourceMappingURL=parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/core/parser.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,SAAS,EACT,YAAY,EAIb,MAAM,mBAAmB,CAAC;AAI3B,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAgErD,2BAA2B;AAC3B,wBAAsB,QAAQ,CAAC,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAO3E"}
@@ -0,0 +1,70 @@
1
+ import { Worker } from 'node:worker_threads';
2
+ import { createReadStream } from 'node:fs';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { parse } from 'csv-parse';
5
+ import iconv from 'iconv-lite';
6
+ import { normalizeRow } from '../utils/normalize.js';
7
+ import { resolveDataPath } from '../utils/paths.js';
8
+ export { normalizeRow } from '../utils/normalize.js';
9
+ // vitest에서는 .ts 소스를 실행하므로 dist/ 경로로 우회
10
+ const isSource = import.meta.url.endsWith('.ts');
11
+ const WORKER_PATH = isSource
12
+ ? new URL('../../dist/worker/csv-worker.js', import.meta.url)
13
+ : new URL('../worker/csv-worker.js', import.meta.url);
14
+ /** 메인 스레드에서 CSV 파싱 (폴백) */
15
+ async function parseInMainThread(filePath, encoding) {
16
+ return new Promise((resolve, reject) => {
17
+ const records = [];
18
+ const fileStream = createReadStream(filePath);
19
+ const decoder = encoding.toLowerCase() === 'utf-8' || encoding.toLowerCase() === 'utf8'
20
+ ? fileStream
21
+ : fileStream.pipe(iconv.decodeStream(encoding));
22
+ const csvParser = parse({
23
+ columns: true,
24
+ skip_empty_lines: true,
25
+ trim: true,
26
+ bom: true,
27
+ });
28
+ decoder.pipe(csvParser);
29
+ csvParser.on('data', (row) => {
30
+ const record = normalizeRow(row);
31
+ if (record) {
32
+ records.push(record);
33
+ }
34
+ });
35
+ csvParser.on('end', () => resolve(records));
36
+ csvParser.on('error', reject);
37
+ });
38
+ }
39
+ /** Worker를 사용하여 CSV 파싱 */
40
+ async function parseWithWorker(filePath, encoding) {
41
+ return new Promise((resolve, reject) => {
42
+ const workerDataPayload = { filePath, encoding };
43
+ const worker = new Worker(fileURLToPath(WORKER_PATH), {
44
+ workerData: workerDataPayload,
45
+ });
46
+ worker.on('message', (msg) => {
47
+ if (msg.type === 'result') {
48
+ resolve(msg.data);
49
+ }
50
+ else {
51
+ reject(new Error(msg.message));
52
+ }
53
+ });
54
+ worker.on('error', reject);
55
+ worker.on('exit', (exitCode) => {
56
+ if (exitCode !== 0) {
57
+ reject(new Error(`Worker exited with code ${exitCode}`));
58
+ }
59
+ });
60
+ });
61
+ }
62
+ /** CSV 파싱 → BjdRecord[] */
63
+ export async function parseCSV(options) {
64
+ const { filePath, encoding = 'euc-kr', useWorker = true } = options ?? {};
65
+ const resolved = resolveDataPath(filePath);
66
+ return useWorker
67
+ ? parseWithWorker(resolved, encoding)
68
+ : parseInMainThread(resolved, encoding);
69
+ }
70
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/core/parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { BjdRecord, BjdTreeNode } from '../types/index.js';
2
+ /**
3
+ * BjdRecord[] → 트리 구조 빌드 (O(n) 단일 패스)
4
+ * 시도 → 시군구 → 읍면동 → 리 계층
5
+ */
6
+ export declare function buildTree(records: BjdRecord[]): BjdTreeNode[];
7
+ /**
8
+ * 특정 코드 프리픽스 기준 서브트리 빌드
9
+ */
10
+ export declare function buildSubTree(records: BjdRecord[], prefix: string): BjdTreeNode[];
11
+ /**
12
+ * 트리 → 배열 평탄화 (깊이 우선)
13
+ */
14
+ export declare function flattenTree(nodes: BjdTreeNode[]): BjdTreeNode[];
15
+ //# sourceMappingURL=tree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree.d.ts","sourceRoot":"","sources":["../../src/core/tree.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhE;;;GAGG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,WAAW,EAAE,CAmC7D;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,EAAE,CAGhF;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,WAAW,EAAE,CAc/D"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * BjdRecord[] → 트리 구조 빌드 (O(n) 단일 패스)
3
+ * 시도 → 시군구 → 읍면동 → 리 계층
4
+ */
5
+ export function buildTree(records) {
6
+ const roots = [];
7
+ const nodeMap = new Map();
8
+ // 이미 정렬되어 있으면 복사/정렬 skip
9
+ const sorted = isSorted(records)
10
+ ? records
11
+ : [...records].sort((a, b) => a.code.localeCompare(b.code));
12
+ for (const record of sorted) {
13
+ const node = {
14
+ code: getCodeSegment(record),
15
+ fullCode: record.code,
16
+ name: record.names[record.names.length - 1],
17
+ level: record.level,
18
+ children: [],
19
+ };
20
+ nodeMap.set(record.code, node);
21
+ const parentKey = getParentKey(record);
22
+ if (parentKey) {
23
+ const parent = nodeMap.get(parentKey);
24
+ if (parent) {
25
+ parent.children.push(node);
26
+ }
27
+ else {
28
+ // 부모가 없으면 루트에 추가
29
+ roots.push(node);
30
+ }
31
+ }
32
+ else {
33
+ roots.push(node);
34
+ }
35
+ }
36
+ return roots;
37
+ }
38
+ /**
39
+ * 특정 코드 프리픽스 기준 서브트리 빌드
40
+ */
41
+ export function buildSubTree(records, prefix) {
42
+ const filtered = records.filter((r) => r.code.startsWith(prefix));
43
+ return buildTree(filtered);
44
+ }
45
+ /**
46
+ * 트리 → 배열 평탄화 (깊이 우선)
47
+ */
48
+ export function flattenTree(nodes) {
49
+ const result = [];
50
+ function walk(nodeList) {
51
+ for (const node of nodeList) {
52
+ result.push(node);
53
+ if (node.children.length > 0) {
54
+ walk(node.children);
55
+ }
56
+ }
57
+ }
58
+ walk(nodes);
59
+ return result;
60
+ }
61
+ /** 해당 레벨의 코드 조각 추출 */
62
+ function getCodeSegment(record) {
63
+ switch (record.level) {
64
+ case 'sido':
65
+ return record.sidoCode;
66
+ case 'sigungu':
67
+ return record.sigunguCode;
68
+ case 'eupmyeondong':
69
+ return record.eupmyeondongCode;
70
+ case 'ri':
71
+ return record.riCode;
72
+ }
73
+ }
74
+ /** 배열이 코드 기준 정렬 상태인지 확인 */
75
+ function isSorted(records) {
76
+ for (let i = 1; i < records.length; i++) {
77
+ if (records[i].code < records[i - 1].code)
78
+ return false;
79
+ }
80
+ return true;
81
+ }
82
+ /** 부모 노드의 전체 코드 계산 */
83
+ function getParentKey(record) {
84
+ switch (record.level) {
85
+ case 'sido':
86
+ return null;
87
+ case 'sigungu':
88
+ return record.sidoCode + '00000000';
89
+ case 'eupmyeondong':
90
+ return record.sidoCode + record.sigunguCode + '00000';
91
+ case 'ri':
92
+ return record.sidoCode + record.sigunguCode + record.eupmyeondongCode + '00';
93
+ }
94
+ }
95
+ //# sourceMappingURL=tree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree.js","sourceRoot":"","sources":["../../src/core/tree.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ export type { RawBjdRow, BjdRecord, BjdLevel, BjdTreeNode, FilterOptions, ParseOptions, } from './types/index.js';
2
+ export { extractSidoCode, extractSigunguCode, extractEupmyeondongCode, extractRiCode, determineLevel, isValidCode, getParentCode, } from './utils/code.js';
3
+ export { parseCSV, normalizeRow } from './core/parser.js';
4
+ export { filterSido, filterSigungu, filterEupmyeondong, filterRi, filterByLevel, searchByName, } from './core/filter.js';
5
+ export { buildTree, buildSubTree, flattenTree } from './core/tree.js';
6
+ export { downloadCSV } from './core/download.js';
7
+ export type { DownloadOptions } from './core/download.js';
8
+ export { DEFAULT_CSV_PATH, hasDefaultData, resolveDataPath } from './utils/paths.js';
9
+ import type { BjdRecord, BjdTreeNode, BjdLevel, ParseOptions, FilterOptions } from './types/index.js';
10
+ /** parse + buildTree 편의 함수 */
11
+ export declare function loadTree(options?: ParseOptions): Promise<BjdTreeNode[]>;
12
+ /** parse + filter 편의 함수 */
13
+ export declare function loadFiltered(options?: ParseOptions & {
14
+ level?: BjdLevel;
15
+ query?: string;
16
+ } & FilterOptions): Promise<BjdRecord[]>;
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,SAAS,EACT,SAAS,EACT,QAAQ,EACR,WAAW,EACX,aAAa,EACb,YAAY,GACb,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,uBAAuB,EACvB,aAAa,EACb,cAAc,EACd,WAAW,EACX,aAAa,GACd,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAG1D,OAAO,EACL,UAAU,EACV,aAAa,EACb,kBAAkB,EAClB,QAAQ,EACR,aAAa,EACb,YAAY,GACb,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGtE,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,YAAY,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAG1D,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGrF,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAKtG,8BAA8B;AAC9B,wBAAsB,QAAQ,CAAC,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAI7E;AAED,2BAA2B;AAC3B,wBAAsB,YAAY,CAChC,OAAO,CAAC,EAAE,YAAY,GAAG;IAAE,KAAK,CAAC,EAAE,QAAQ,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,aAAa,GAC5E,OAAO,CAAC,SAAS,EAAE,CAAC,CActB"}