@simplysm/core-node 13.0.100 → 14.0.1

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 (41) hide show
  1. package/dist/features/fs-watcher.d.ts +21 -21
  2. package/dist/features/fs-watcher.d.ts.map +1 -1
  3. package/dist/features/fs-watcher.js +176 -114
  4. package/dist/features/fs-watcher.js.map +1 -6
  5. package/dist/index.js +6 -7
  6. package/dist/index.js.map +1 -6
  7. package/dist/utils/fs.d.ts +96 -96
  8. package/dist/utils/fs.d.ts.map +1 -1
  9. package/dist/utils/fs.js +437 -272
  10. package/dist/utils/fs.js.map +1 -6
  11. package/dist/utils/path.d.ts +22 -22
  12. package/dist/utils/path.js +103 -45
  13. package/dist/utils/path.js.map +1 -6
  14. package/dist/worker/create-worker.d.ts +3 -3
  15. package/dist/worker/create-worker.js +106 -81
  16. package/dist/worker/create-worker.js.map +1 -6
  17. package/dist/worker/types.d.ts +14 -14
  18. package/dist/worker/types.js +4 -1
  19. package/dist/worker/types.js.map +1 -6
  20. package/dist/worker/worker.d.ts +5 -5
  21. package/dist/worker/worker.js +168 -132
  22. package/dist/worker/worker.js.map +1 -6
  23. package/lib/worker-dev-proxy.js +15 -0
  24. package/package.json +8 -6
  25. package/src/features/fs-watcher.ts +53 -42
  26. package/src/index.ts +3 -3
  27. package/src/utils/fs.ts +111 -120
  28. package/src/utils/path.ts +26 -26
  29. package/src/worker/create-worker.ts +10 -10
  30. package/src/worker/types.ts +14 -14
  31. package/src/worker/worker.ts +29 -29
  32. package/README.md +0 -112
  33. package/docs/features.md +0 -91
  34. package/docs/fs.md +0 -309
  35. package/docs/path.md +0 -120
  36. package/docs/worker.md +0 -168
  37. package/tests/utils/fs-watcher.spec.ts +0 -286
  38. package/tests/utils/fs.spec.ts +0 -705
  39. package/tests/utils/path.spec.ts +0 -179
  40. package/tests/worker/fixtures/test-worker.ts +0 -35
  41. package/tests/worker/sd-worker.spec.ts +0 -174
@@ -1,7 +1,7 @@
1
1
  import type { WorkerOptions as WorkerRawOptions } from "worker_threads";
2
2
  import type { WorkerModule, WorkerProxy } from "./types";
3
3
  /**
4
- * Type-safe Worker wrapper.
4
+ * 타입 안전한 Worker 래퍼.
5
5
  *
6
6
  * @example
7
7
  * // worker.ts
@@ -16,11 +16,11 @@ import type { WorkerModule, WorkerProxy } from "./types";
16
16
  */
17
17
  export declare const Worker: {
18
18
  /**
19
- * Creates a type-safe Worker Proxy.
19
+ * 타입 안전한 Worker Proxy를 생성한다.
20
20
  *
21
- * @param filePath - Worker file path (file:// URL or absolute path)
22
- * @param opt - Worker options
23
- * @returns Proxy object (supports direct method calls, on(), and terminate())
21
+ * @param filePath - 워커 파일 경로 (file:// URL 또는 절대 경로)
22
+ * @param opt - 워커 옵션
23
+ * @returns Proxy 객체 (메서드 직접 호출, on(), terminate() 지원)
24
24
  */
25
25
  create<TModule extends WorkerModule>(filePath: string, opt?: Omit<WorkerRawOptions, "stdout" | "stderr">): WorkerProxy<TModule>;
26
26
  };
@@ -4,143 +4,179 @@ import path from "path";
4
4
  import { fileURLToPath } from "url";
5
5
  import { Worker as WorkerRaw } from "worker_threads";
6
6
  const logger = consola.withTag("sd-worker");
7
+ //#region WorkerInternal
8
+ /**
9
+ * Worker의 내부 구현 클래스.
10
+ * Proxy를 통해 외부에 노출된다.
11
+ *
12
+ * 개발 환경(.ts 파일)에서는 TypeScript 워커 파일을 tsx를 통해 실행한다.
13
+ * 프로덕션 환경(.js 파일)에서는 Worker를 직접 생성한다.
14
+ */
7
15
  class WorkerInternal extends EventEmitter {
8
- _worker;
9
- _isTerminated = false;
10
- _pendingRequests = /* @__PURE__ */ new Map();
11
- constructor(filePath, opt) {
12
- super();
13
- const ext = path.extname(import.meta.filename);
14
- const envObj = opt?.env != null && typeof opt.env === "object" ? opt.env : {};
15
- if (ext === ".ts") {
16
- const workerPath = filePath.startsWith("file://") ? fileURLToPath(filePath) : filePath;
17
- this._worker = new WorkerRaw(
18
- path.resolve(import.meta.dirname, "../../lib/worker-dev-proxy.js"),
19
- {
20
- stdout: true,
21
- stderr: true,
22
- ...opt,
23
- env: {
24
- ...process.env,
25
- ...envObj
26
- },
27
- argv: [workerPath, ...opt?.argv ?? []]
16
+ _worker;
17
+ _isTerminated = false;
18
+ _pendingRequests = new Map();
19
+ constructor(filePath, opt) {
20
+ super();
21
+ const ext = path.extname(import.meta.filename);
22
+ // 타입 가드를 통해 env 객체 추출
23
+ const envObj = opt?.env != null && typeof opt.env === "object" ? opt.env : {};
24
+ // 개발 환경(.ts 파일)에서는 tsx를 통해 실행
25
+ // worker-dev-proxy.js: tsx를 통해 TypeScript 워커 파일을 동적으로 로드하는 프록시
26
+ if (ext === ".ts") {
27
+ // file:// URL이면 절대 경로로 변환 (worker-dev-proxy.js가 다시 pathToFileURL을 적용)
28
+ const workerPath = filePath.startsWith("file://") ? fileURLToPath(filePath) : filePath;
29
+ this._worker = new WorkerRaw(path.resolve(import.meta.dirname, "../../lib/worker-dev-proxy.js"), {
30
+ stdout: true,
31
+ stderr: true,
32
+ ...opt,
33
+ env: {
34
+ ...process.env,
35
+ ...envObj,
36
+ },
37
+ argv: [workerPath, ...(opt?.argv ?? [])],
38
+ });
28
39
  }
29
- );
30
- } else {
31
- const workerPath = filePath.startsWith("file://") ? fileURLToPath(filePath) : filePath;
32
- this._worker = new WorkerRaw(workerPath, {
33
- stdout: true,
34
- stderr: true,
35
- ...opt,
36
- env: {
37
- ...process.env,
38
- ...envObj
40
+ else {
41
+ // 프로덕션 환경 (.js 파일)
42
+ // file:// URL이면 변환; 그렇지 않으면 절대 경로를 그대로 사용
43
+ const workerPath = filePath.startsWith("file://") ? fileURLToPath(filePath) : filePath;
44
+ this._worker = new WorkerRaw(workerPath, {
45
+ stdout: true,
46
+ stderr: true,
47
+ ...opt,
48
+ env: {
49
+ ...process.env,
50
+ ...envObj,
51
+ },
52
+ });
39
53
  }
40
- });
54
+ // 워커의 stdout/stderr를 메인 프로세스로 파이프
55
+ this._worker.stdout.pipe(process.stdout);
56
+ this._worker.stderr.pipe(process.stderr);
57
+ this._worker.on("exit", (code) => {
58
+ if (!this._isTerminated && code !== 0) {
59
+ logger.error(`워커가 비정상 종료되었습니다 (코드: ${code})`);
60
+ // 비정상 종료 시 대기 중인 모든 요청을 거부
61
+ this._rejectAllPending(new Error(`워커가 비정상 종료되었습니다 (코드: ${code})`));
62
+ }
63
+ });
64
+ this._worker.on("error", (err) => {
65
+ logger.error("워커 오류:", err);
66
+ // 워커 오류 시 대기 중인 모든 요청을 거부
67
+ this._rejectAllPending(err);
68
+ });
69
+ this._worker.on("message", (serializedResponse) => {
70
+ const decoded = transfer.decode(serializedResponse);
71
+ // 응답 구조 검증
72
+ if (decoded == null || typeof decoded !== "object" || !("type" in decoded)) {
73
+ logger.warn("워커로부터 잘못된 응답 형식:", decoded);
74
+ return;
75
+ }
76
+ const response = decoded;
77
+ if (response.type === "event") {
78
+ this.emit(response.event, response.body);
79
+ }
80
+ else if (response.type === "log") {
81
+ process.stdout.write(response.body);
82
+ }
83
+ else if (response.type === "return") {
84
+ const pending = this._pendingRequests.get(response.request.id);
85
+ if (pending) {
86
+ this._pendingRequests.delete(response.request.id);
87
+ pending.resolve(response.body);
88
+ }
89
+ }
90
+ else {
91
+ // response.type === "error"
92
+ const pending = this._pendingRequests.get(response.request.id);
93
+ if (pending) {
94
+ this._pendingRequests.delete(response.request.id);
95
+ pending.reject(response.body);
96
+ }
97
+ }
98
+ });
41
99
  }
42
- this._worker.stdout.pipe(process.stdout);
43
- this._worker.stderr.pipe(process.stderr);
44
- this._worker.on("exit", (code) => {
45
- if (!this._isTerminated && code !== 0) {
46
- logger.error(`Worker crashed (code: ${code})`);
47
- this._rejectAllPending(new Error(`Worker crashed (code: ${code})`));
48
- }
49
- });
50
- this._worker.on("error", (err) => {
51
- logger.error("Worker error:", err);
52
- this._rejectAllPending(err);
53
- });
54
- this._worker.on("message", (serializedResponse) => {
55
- const decoded = transfer.decode(serializedResponse);
56
- if (decoded == null || typeof decoded !== "object" || !("type" in decoded)) {
57
- logger.warn("Invalid response format from worker:", decoded);
58
- return;
59
- }
60
- const response = decoded;
61
- if (response.type === "event") {
62
- this.emit(response.event, response.body);
63
- } else if (response.type === "log") {
64
- process.stdout.write(response.body);
65
- } else if (response.type === "return") {
66
- const pending = this._pendingRequests.get(response.request.id);
67
- if (pending) {
68
- this._pendingRequests.delete(response.request.id);
69
- pending.resolve(response.body);
100
+ /**
101
+ * 대기 중인 모든 요청을 거부한다.
102
+ */
103
+ _rejectAllPending(err) {
104
+ for (const [_id, { method, reject }] of this._pendingRequests) {
105
+ reject(new Error(`${err.message} (method: ${method})`));
70
106
  }
71
- } else {
72
- const pending = this._pendingRequests.get(response.request.id);
73
- if (pending) {
74
- this._pendingRequests.delete(response.request.id);
75
- pending.reject(response.body);
76
- }
77
- }
78
- });
79
- }
80
- /**
81
- * Rejects all pending requests.
82
- */
83
- _rejectAllPending(err) {
84
- for (const [_id, { method, reject }] of this._pendingRequests) {
85
- reject(new Error(`${err.message} (method: ${method})`));
107
+ this._pendingRequests.clear();
108
+ }
109
+ /**
110
+ * 워커 메서드를 호출한다.
111
+ */
112
+ call(method, params) {
113
+ return new Promise((resolve, reject) => {
114
+ const request = {
115
+ id: Uuid.generate().toString(),
116
+ method,
117
+ params,
118
+ };
119
+ this._pendingRequests.set(request.id, { method, resolve, reject });
120
+ const serialized = transfer.encode(request);
121
+ this._worker.postMessage(serialized.result, serialized.transferList);
122
+ });
123
+ }
124
+ /**
125
+ * 워커를 종료한다.
126
+ */
127
+ async terminate() {
128
+ this._isTerminated = true;
129
+ this._rejectAllPending(new Error("워커가 종료되었습니다"));
130
+ await this._worker.terminate();
86
131
  }
87
- this._pendingRequests.clear();
88
- }
89
- /**
90
- * Calls a worker method.
91
- */
92
- call(method, params) {
93
- return new Promise((resolve, reject) => {
94
- const request = {
95
- id: Uuid.generate().toString(),
96
- method,
97
- params
98
- };
99
- this._pendingRequests.set(request.id, { method, resolve, reject });
100
- const serialized = transfer.encode(request);
101
- this._worker.postMessage(serialized.result, serialized.transferList);
102
- });
103
- }
104
- /**
105
- * Terminates the worker.
106
- */
107
- async terminate() {
108
- this._isTerminated = true;
109
- this._rejectAllPending(new Error("Worker terminated"));
110
- await this._worker.terminate();
111
- }
112
132
  }
113
- const Worker = {
114
- /**
115
- * Creates a type-safe Worker Proxy.
116
- *
117
- * @param filePath - Worker file path (file:// URL or absolute path)
118
- * @param opt - Worker options
119
- * @returns Proxy object (supports direct method calls, on(), and terminate())
120
- */
121
- create(filePath, opt) {
122
- const internal = new WorkerInternal(filePath, opt);
123
- return new Proxy({}, {
124
- get(_target, prop) {
125
- if (prop === "on") {
126
- return (event, listener) => {
127
- internal.on(event, listener);
128
- };
129
- }
130
- if (prop === "off") {
131
- return (event, listener) => {
132
- internal.off(event, listener);
133
- };
134
- }
135
- if (prop === "terminate") {
136
- return () => internal.terminate();
137
- }
138
- return (...args) => internal.call(prop, args);
139
- }
140
- });
141
- }
142
- };
143
- export {
144
- Worker
133
+ //#endregion
134
+ //#region Worker
135
+ /**
136
+ * 타입 안전한 Worker 래퍼.
137
+ *
138
+ * @example
139
+ * // worker.ts
140
+ * export default createWorker({
141
+ * add: (a: number, b: number) => a + b,
142
+ * });
143
+ *
144
+ * // main.ts
145
+ * const worker = Worker.create<typeof import("./worker.js")>("./worker.ts");
146
+ * const result = await worker.add(10, 20); // 30
147
+ * await worker.terminate();
148
+ */
149
+ export const Worker = {
150
+ /**
151
+ * 타입 안전한 Worker Proxy를 생성한다.
152
+ *
153
+ * @param filePath - 워커 파일 경로 (file:// URL 또는 절대 경로)
154
+ * @param opt - 워커 옵션
155
+ * @returns Proxy 객체 (메서드 직접 호출, on(), terminate() 지원)
156
+ */
157
+ create(filePath, opt) {
158
+ const internal = new WorkerInternal(filePath, opt);
159
+ return new Proxy({}, {
160
+ get(_target, prop) {
161
+ // 예약된 메서드: on, off, terminate
162
+ if (prop === "on") {
163
+ return (event, listener) => {
164
+ internal.on(event, listener);
165
+ };
166
+ }
167
+ if (prop === "off") {
168
+ return (event, listener) => {
169
+ internal.off(event, listener);
170
+ };
171
+ }
172
+ if (prop === "terminate") {
173
+ return () => internal.terminate();
174
+ }
175
+ // 그 외의 경우 워커 메서드로 처리
176
+ return (...args) => internal.call(prop, args);
177
+ },
178
+ });
179
+ },
145
180
  };
146
- //# sourceMappingURL=worker.js.map
181
+ //#endregion
182
+ //# sourceMappingURL=worker.js.map
@@ -1,6 +1 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/worker/worker.ts"],
4
- "mappings": "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",
5
- "names": []
6
- }
1
+ {"version":3,"file":"worker.js","sourceRoot":"","sources":["..\\..\\src\\worker\\worker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import process from "node:process";
2
+ import { tsImport } from "tsx/esm/api";
3
+ import { pathToFileURL } from "node:url";
4
+
5
+ // Assume argv[2] contains the actual worker file path
6
+ const workerFile = process.argv[2];
7
+ if (!workerFile) {
8
+ throw new Error("Worker file path is required as argument!");
9
+ }
10
+
11
+ // If file:// URL is already passed, use it as-is; otherwise convert
12
+ const workerFileUrl = workerFile.startsWith("file://")
13
+ ? workerFile
14
+ : pathToFileURL(workerFile).href;
15
+ await tsImport(workerFileUrl, import.meta.url);
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@simplysm/core-node",
3
- "version": "13.0.100",
4
- "description": "Simplysm package - Core module (node)",
5
- "author": "simplysm",
3
+ "version": "14.0.1",
4
+ "description": "심플리즘 패키지 - 코어 (node)",
5
+ "author": "심플리즘",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
8
8
  "type": "git",
@@ -14,9 +14,8 @@
14
14
  "types": "./dist/index.d.ts",
15
15
  "files": [
16
16
  "dist",
17
- "docs",
18
17
  "src",
19
- "tests"
18
+ "lib"
20
19
  ],
21
20
  "sideEffects": false,
22
21
  "dependencies": {
@@ -25,6 +24,9 @@
25
24
  "glob": "^13.0.6",
26
25
  "minimatch": "^10.2.4",
27
26
  "tsx": "^4.21.0",
28
- "@simplysm/core-common": "13.0.100"
27
+ "@simplysm/core-common": "14.0.1"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^20.14.8"
29
31
  }
30
32
  }
@@ -8,11 +8,11 @@ import { type NormPath, norm } from "../utils/path";
8
8
 
9
9
  //#region Helpers
10
10
 
11
- /** Glob metacharacter pattern */
11
+ /** Glob 메타문자 패턴 */
12
12
  const GLOB_CHARS_RE = /[*?{[\]]/;
13
13
 
14
14
  /**
15
- * Extracts the base directory from a glob pattern.
15
+ * Glob 패턴에서 기본 디렉토리를 추출한다.
16
16
  * @example extractGlobBase("/home/user/src/**\/*.ts") → "/home/user/src"
17
17
  */
18
18
  function extractGlobBase(globPath: string): string {
@@ -30,22 +30,22 @@ function extractGlobBase(globPath: string): string {
30
30
  //#region Types
31
31
 
32
32
  /**
33
- * List of supported file change event types.
33
+ * 지원되는 파일 변경 이벤트 타입 목록.
34
34
  */
35
35
  const FS_WATCHER_EVENTS = ["add", "addDir", "change", "unlink", "unlinkDir"] as const;
36
36
 
37
37
  /**
38
- * File change event type.
38
+ * 파일 변경 이벤트 타입.
39
39
  */
40
40
  export type FsWatcherEvent = (typeof FS_WATCHER_EVENTS)[number];
41
41
 
42
42
  /**
43
- * File change information.
43
+ * 파일 변경 정보.
44
44
  */
45
45
  export interface FsWatcherChangeInfo {
46
- /** Change event type */
46
+ /** 변경 이벤트 타입 */
47
47
  event: FsWatcherEvent;
48
- /** Changed file/directory path (normalized) */
48
+ /** 변경된 파일/디렉토리 경로 (정규화됨) */
49
49
  path: NormPath;
50
50
  }
51
51
 
@@ -54,13 +54,13 @@ export interface FsWatcherChangeInfo {
54
54
  //#region FsWatcher
55
55
 
56
56
  /**
57
- * Chokidar-based file system watcher wrapper.
58
- * Merges events that occur within a short time and calls the callback once.
57
+ * Chokidar 기반 파일 시스템 감시 래퍼.
58
+ * 짧은 시간 내에 발생하는 이벤트를 병합하여 콜백을 번만 호출한다.
59
59
  *
60
- * **Note**: The `ignoreInitial` option of chokidar is internally always set to `true`.
61
- * If you pass `options.ignoreInitial: false`, the callback will be called with an empty array on the first `onChange` call,
62
- * but the actual initial file list is not included.
63
- * This is intentional behavior to prevent conflicts with the event merging logic.
60
+ * **주의**: chokidar의 `ignoreInitial` 옵션은 내부적으로 항상 `true`로 설정된다.
61
+ * `options.ignoreInitial: false`를 전달하면 번째 `onChange` 호출 배열로 콜백이 호출되지만,
62
+ * 실제 초기 파일 목록은 포함되지 않는다.
63
+ * 이는 이벤트 병합 로직과의 충돌을 방지하기 위한 의도적인 동작이다.
64
64
  *
65
65
  * @example
66
66
  * const watcher = await FsWatcher.watch(["src/**\/*.ts"]);
@@ -70,24 +70,35 @@ export interface FsWatcherChangeInfo {
70
70
  * }
71
71
  * });
72
72
  *
73
- * // Close
73
+ * // 종료
74
74
  * await watcher.close();
75
75
  */
76
76
  export class FsWatcher {
77
77
  /**
78
- * Starts watching files (asynchronous).
79
- * Waits until the ready event is emitted.
78
+ * 파일 감시를 시작한다 (비동기).
79
+ * ready 이벤트가 발생할 때까지 대기한다.
80
80
  *
81
- * @param paths - Array of file/directory paths or glob patterns to watch
82
- * @param options - chokidar options
81
+ * @param paths - 감시할 파일/디렉토리 경로 또는 glob 패턴 배열
82
+ * @param options - chokidar 옵션
83
83
  */
84
84
  static async watch(paths: string[], options?: chokidar.ChokidarOptions): Promise<FsWatcher> {
85
85
  return new Promise<FsWatcher>((resolve, reject) => {
86
86
  const watcher = new FsWatcher(paths, options);
87
- watcher._watcher.on("ready", () => {
87
+
88
+ const onReady = () => {
89
+ watcher._watcher.removeListener("error", onError);
88
90
  resolve(watcher);
89
- });
90
- watcher._watcher.on("error", reject);
91
+ };
92
+ const onError = (err: unknown) => {
93
+ watcher._watcher.removeListener("ready", onReady);
94
+ watcher.close().then(
95
+ () => reject(err),
96
+ () => reject(err),
97
+ );
98
+ };
99
+
100
+ watcher._watcher.once("ready", onReady);
101
+ watcher._watcher.once("error", onError);
91
102
  });
92
103
  }
93
104
 
@@ -111,7 +122,7 @@ export class FsWatcher {
111
122
  }
112
123
  }
113
124
 
114
- // Remove duplicate paths
125
+ // 중복 경로 제거
115
126
  const uniquePaths = [...new Set(watchPaths)];
116
127
 
117
128
  this._watcher = chokidar.watch(uniquePaths, {
@@ -121,18 +132,18 @@ export class FsWatcher {
121
132
  });
122
133
  this._ignoreInitial = options?.ignoreInitial ?? this._ignoreInitial;
123
134
 
124
- // Log errors that occur during watching
135
+ // 감시 발생하는 오류를 로깅
125
136
  this._watcher.on("error", (err) => {
126
- this._logger.error("FsWatcher error:", err);
137
+ this._logger.error("FsWatcher 오류:", err);
127
138
  });
128
139
  }
129
140
 
130
141
  /**
131
- * Registers a file change event handler.
132
- * Collects events for the specified delay time and calls the callback once.
142
+ * 파일 변경 이벤트 핸들러를 등록한다.
143
+ * 지정된 지연 시간 동안 이벤트를 수집하여 콜백을 호출한다.
133
144
  *
134
- * @param opt.delay - Event merge wait time (ms)
135
- * @param cb - Change event callback
145
+ * @param opt.delay - 이벤트 병합 대기 시간 (ms)
146
+ * @param cb - 변경 이벤트 콜백
136
147
  */
137
148
  onChange(
138
149
  opt: { delay?: number },
@@ -143,7 +154,7 @@ export class FsWatcher {
143
154
 
144
155
  let changeInfoMap = new Map<string, EventName>();
145
156
 
146
- // If ignoreInitial is false, call callback with empty array initially
157
+ // ignoreInitial false이면 초기에 배열로 콜백 호출
147
158
  if (!this._ignoreInitial) {
148
159
  fnQ.run(async () => {
149
160
  await cb([]);
@@ -151,22 +162,22 @@ export class FsWatcher {
151
162
  }
152
163
 
153
164
  this._watcher.on("all", (event, filePath) => {
154
- // Only process supported events
165
+ // 지원되는 이벤트만 처리
155
166
  if (!FS_WATCHER_EVENTS.includes(event as FsWatcherEvent)) return;
156
167
 
157
- // If glob matchers exist, apply pattern filtering
168
+ // glob matcher가 존재하면 패턴 필터링 적용
158
169
  if (this._globMatchers.length > 0) {
159
170
  const posixFilePath = filePath.replace(/\\/g, "/");
160
171
  if (!this._globMatchers.some((m) => m.match(posixFilePath))) return;
161
172
  }
162
173
 
163
174
  /*
164
- * Event merging strategy:
165
- * If multiple events occur for the same file within a short time, only the final state is passed.
166
- * - add + change → add (modification immediately after creation is considered as creation)
167
- * - add + unlink → no change (immediate deletion after creation is considered as no change)
168
- * - unlink + add → add (recreation after deletion is considered as creation)
169
- * - otherwiseoverwrite with latest event
175
+ * 이벤트 병합 전략:
176
+ * 같은 파일에 대해 짧은 시간 내에 여러 이벤트가 발생하면, 최종 상태만 전달한다.
177
+ * - add + change → add (생성 직후 수정은 생성으로 간주)
178
+ * - add + unlink → 변경 없음 (생성 직후 삭제는 변경 없음으로 간주)
179
+ * - unlink + add → add (삭제 재생성은 생성으로 간주)
180
+ * - 최신 이벤트로 덮어쓰기
170
181
  */
171
182
  if (!changeInfoMap.has(filePath)) {
172
183
  changeInfoMap.set(filePath, event);
@@ -174,19 +185,19 @@ export class FsWatcher {
174
185
  const prevEvent = changeInfoMap.get(filePath)!;
175
186
 
176
187
  if (prevEvent === "add" && event === "change") {
177
- // add followed by change → keep add
188
+ // add change → add 유지
178
189
  changeInfoMap.set(filePath, "add");
179
190
  } else if (
180
191
  (prevEvent === "add" && event === "unlink") ||
181
192
  (prevEvent === "addDir" && event === "unlinkDir")
182
193
  ) {
183
- // add followed by unlink → no change (deletion)
194
+ // add unlink → 변경 없음 (삭제)
184
195
  changeInfoMap.delete(filePath);
185
196
  } else if (prevEvent === "unlink" && (event === "add" || event === "change")) {
186
- // unlink followed by add/change → add (file recreation)
197
+ // unlink add/change → add (파일 재생성)
187
198
  changeInfoMap.set(filePath, "add");
188
199
  } else if (prevEvent === "unlinkDir" && event === "addDir") {
189
- // unlinkDir followed by addDir → addDir (directory recreation)
200
+ // unlinkDir addDir → addDir (디렉토리 재생성)
190
201
  changeInfoMap.set(filePath, "addDir");
191
202
  } else {
192
203
  changeInfoMap.set(filePath, event);
@@ -213,7 +224,7 @@ export class FsWatcher {
213
224
  }
214
225
 
215
226
  /**
216
- * Closes the file watcher.
227
+ * 파일 감시자를 종료한다.
217
228
  */
218
229
  async close(): Promise<void> {
219
230
  for (const q of this._debounceQueues) {
package/src/index.ts CHANGED
@@ -1,11 +1,11 @@
1
- // Utils
1
+ // 유틸리티
2
2
  export * as fsx from "./utils/fs";
3
3
  export * as pathx from "./utils/path";
4
4
 
5
- // Features
5
+ // 기능
6
6
  export * from "./features/fs-watcher";
7
7
 
8
- // Worker
8
+ // 워커
9
9
  export * from "./worker/types";
10
10
  export * from "./worker/worker";
11
11
  export * from "./worker/create-worker";