@simplysm/core-node 13.0.100 → 14.0.4

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 (44) hide show
  1. package/README.md +93 -79
  2. package/dist/features/fs-watcher.d.ts +21 -21
  3. package/dist/features/fs-watcher.d.ts.map +1 -1
  4. package/dist/features/fs-watcher.js +176 -114
  5. package/dist/features/fs-watcher.js.map +1 -6
  6. package/dist/index.js +6 -7
  7. package/dist/index.js.map +1 -6
  8. package/dist/utils/fs.d.ts +96 -96
  9. package/dist/utils/fs.d.ts.map +1 -1
  10. package/dist/utils/fs.js +437 -272
  11. package/dist/utils/fs.js.map +1 -6
  12. package/dist/utils/path.d.ts +22 -22
  13. package/dist/utils/path.js +103 -45
  14. package/dist/utils/path.js.map +1 -6
  15. package/dist/worker/create-worker.d.ts +3 -3
  16. package/dist/worker/create-worker.js +106 -81
  17. package/dist/worker/create-worker.js.map +1 -6
  18. package/dist/worker/types.d.ts +14 -14
  19. package/dist/worker/types.js +4 -1
  20. package/dist/worker/types.js.map +1 -6
  21. package/dist/worker/worker.d.ts +5 -5
  22. package/dist/worker/worker.js +168 -132
  23. package/dist/worker/worker.js.map +1 -6
  24. package/docs/fs-watcher.md +107 -0
  25. package/docs/fsx.md +287 -0
  26. package/docs/pathx.md +115 -0
  27. package/docs/worker.md +117 -62
  28. package/lib/worker-dev-proxy.js +15 -0
  29. package/package.json +9 -6
  30. package/src/features/fs-watcher.ts +53 -42
  31. package/src/index.ts +3 -3
  32. package/src/utils/fs.ts +111 -120
  33. package/src/utils/path.ts +26 -26
  34. package/src/worker/create-worker.ts +10 -10
  35. package/src/worker/types.ts +14 -14
  36. package/src/worker/worker.ts +29 -29
  37. package/docs/features.md +0 -91
  38. package/docs/fs.md +0 -309
  39. package/docs/path.md +0 -120
  40. package/tests/utils/fs-watcher.spec.ts +0 -286
  41. package/tests/utils/fs.spec.ts +0 -705
  42. package/tests/utils/path.spec.ts +0 -179
  43. package/tests/worker/fixtures/test-worker.ts +0 -35
  44. 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,107 @@
1
+ # FsWatcher -- File System Watcher
2
+
3
+ Chokidar-based file system watcher with debounced event delivery and glob filtering.
4
+
5
+ ```ts
6
+ import { FsWatcher } from "@simplysm/core-node";
7
+ import type { FsWatcherEvent, FsWatcherChangeInfo } from "@simplysm/core-node";
8
+ ```
9
+
10
+ ## Types
11
+
12
+ ### FsWatcherEvent
13
+
14
+ ```ts
15
+ type FsWatcherEvent = "add" | "addDir" | "change" | "unlink" | "unlinkDir"
16
+ ```
17
+
18
+ Supported file change event types.
19
+
20
+ ### FsWatcherChangeInfo (interface)
21
+
22
+ ```ts
23
+ interface FsWatcherChangeInfo {
24
+ event: FsWatcherEvent;
25
+ path: NormPath;
26
+ }
27
+ ```
28
+
29
+ | Field | Type | Description |
30
+ |-------|------|-------------|
31
+ | `event` | `FsWatcherEvent` | The type of change event |
32
+ | `path` | `NormPath` | Normalized absolute path of the changed file/directory |
33
+
34
+ ## FsWatcher (class)
35
+
36
+ Wraps chokidar to provide debounced file change notifications. Multiple events occurring within a short window are merged and delivered as a single batch.
37
+
38
+ Event merging strategy:
39
+ - `add` + `change` --> `add` (modification right after creation is treated as creation)
40
+ - `add` + `unlink` --> removed (creation then deletion cancels out)
41
+ - `unlink` + `add` --> `add` (deletion then recreation is treated as creation)
42
+ - Other combinations --> latest event wins
43
+
44
+ **Note:** `ignoreInitial` is always set to `true` internally. Passing `ignoreInitial: false` triggers an initial callback with an empty array, but does not include the initial file listing.
45
+
46
+ ### Static Methods
47
+
48
+ #### FsWatcher.watch
49
+
50
+ ```ts
51
+ static async watch(
52
+ paths: string[],
53
+ options?: chokidar.ChokidarOptions,
54
+ ): Promise<FsWatcher>
55
+ ```
56
+
57
+ Start watching files/directories. Resolves when the watcher is ready.
58
+
59
+ | Parameter | Type | Description |
60
+ |-----------|------|-------------|
61
+ | `paths` | `string[]` | File paths, directory paths, or glob patterns to watch |
62
+ | `options` | `chokidar.ChokidarOptions?` | Chokidar configuration options |
63
+
64
+ Glob patterns in `paths` are automatically decomposed: the base directory is watched, and events are filtered by the glob pattern using minimatch.
65
+
66
+ ### Instance Methods
67
+
68
+ #### onChange
69
+
70
+ ```ts
71
+ onChange(
72
+ opt: { delay?: number },
73
+ cb: (changeInfos: FsWatcherChangeInfo[]) => void | Promise<void>,
74
+ ): this
75
+ ```
76
+
77
+ Register a change event handler. Events are collected for the specified delay duration and delivered as a single batch.
78
+
79
+ | Parameter | Type | Description |
80
+ |-----------|------|-------------|
81
+ | `opt.delay` | `number?` | Debounce delay in milliseconds |
82
+ | `cb` | `(changeInfos: FsWatcherChangeInfo[]) => void \| Promise<void>` | Callback receiving batched change events |
83
+
84
+ Returns `this` for chaining.
85
+
86
+ #### close
87
+
88
+ ```ts
89
+ async close(): Promise<void>
90
+ ```
91
+
92
+ Stop watching and clean up all debounce queues.
93
+
94
+ ## Usage
95
+
96
+ ```ts
97
+ const watcher = await FsWatcher.watch(["src/**/*.ts"], { depth: 3 });
98
+
99
+ watcher.onChange({ delay: 300 }, (changes) => {
100
+ for (const { path, event } of changes) {
101
+ console.log(`${event}: ${path}`);
102
+ }
103
+ });
104
+
105
+ // Later: stop watching
106
+ await watcher.close();
107
+ ```