@simplysm/core-node 14.0.1 → 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.
package/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # @simplysm/core-node
2
+
3
+ Core Node.js utilities -- file system operations, path utilities, file watcher, typed worker threads.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @simplysm/core-node
9
+ ```
10
+
11
+ ## API
12
+
13
+ ### fsx -- File System Utilities
14
+
15
+ Namespace with sync/async file system operations. See [docs/fsx.md](docs/fsx.md) for full API.
16
+
17
+ | Function | Signature | Description |
18
+ |----------|-----------|-------------|
19
+ | `existsSync` | `(targetPath: string): boolean` | Check if path exists (sync) |
20
+ | `exists` | `(targetPath: string): Promise<boolean>` | Check if path exists (async) |
21
+ | `mkdirSync` | `(targetPath: string): void` | Create directory recursively (sync) |
22
+ | `mkdir` | `(targetPath: string): Promise<void>` | Create directory recursively (async) |
23
+ | `rmSync` | `(targetPath: string): void` | Remove file/directory (sync) |
24
+ | `rm` | `(targetPath: string): Promise<void>` | Remove file/directory with retry (async) |
25
+ | `copySync` | `(sourcePath, targetPath, filter?): void` | Copy file/directory (sync) |
26
+ | `copy` | `(sourcePath, targetPath, filter?): Promise<void>` | Copy file/directory (async) |
27
+ | `readSync` | `(targetPath: string): string` | Read file as UTF-8 string (sync) |
28
+ | `read` | `(targetPath: string): Promise<string>` | Read file as UTF-8 string (async) |
29
+ | `readBufferSync` | `(targetPath: string): Buffer` | Read file as Buffer (sync) |
30
+ | `readBuffer` | `(targetPath: string): Promise<Buffer>` | Read file as Buffer (async) |
31
+ | `readJsonSync` | `<T>(targetPath: string): T` | Read and parse JSON file (sync) |
32
+ | `readJson` | `<T>(targetPath: string): Promise<T>` | Read and parse JSON file (async) |
33
+ | `writeSync` | `(targetPath, data): void` | Write file with auto-mkdir (sync) |
34
+ | `write` | `(targetPath, data): Promise<void>` | Write file with auto-mkdir (async) |
35
+ | `writeJsonSync` | `(targetPath, data, options?): void` | Write JSON file (sync) |
36
+ | `writeJson` | `(targetPath, data, options?): Promise<void>` | Write JSON file (async) |
37
+ | `readdirSync` | `(targetPath: string): string[]` | List directory contents (sync) |
38
+ | `readdir` | `(targetPath: string): Promise<string[]>` | List directory contents (async) |
39
+ | `statSync` | `(targetPath: string): fs.Stats` | Get file info, follows symlinks (sync) |
40
+ | `stat` | `(targetPath: string): Promise<fs.Stats>` | Get file info, follows symlinks (async) |
41
+ | `lstatSync` | `(targetPath: string): fs.Stats` | Get file info, no symlink follow (sync) |
42
+ | `lstat` | `(targetPath: string): Promise<fs.Stats>` | Get file info, no symlink follow (async) |
43
+ | `globSync` | `(pattern, options?): string[]` | Glob file search (sync) |
44
+ | `glob` | `(pattern, options?): Promise<string[]>` | Glob file search (async) |
45
+ | `clearEmptyDirectory` | `(dirPath: string): Promise<void>` | Recursively remove empty directories |
46
+ | `findAllParentChildPathsSync` | `(childGlob, fromPath, rootPath?): string[]` | Search parent dirs for glob matches (sync) |
47
+ | `findAllParentChildPaths` | `(childGlob, fromPath, rootPath?): Promise<string[]>` | Search parent dirs for glob matches (async) |
48
+
49
+ ### pathx -- Path Utilities
50
+
51
+ Namespace with path manipulation utilities. See [docs/pathx.md](docs/pathx.md) for full API.
52
+
53
+ | Export | Kind | Description |
54
+ |--------|------|-------------|
55
+ | `NormPath` | type | Branded type for normalized absolute paths |
56
+ | `posix` | function | Convert path to POSIX style (backslash to slash) |
57
+ | `changeFileDirectory` | function | Change the directory portion of a file path |
58
+ | `basenameWithoutExt` | function | Get filename without extension |
59
+ | `isChildPath` | function | Check if a path is a child of another |
60
+ | `norm` | function | Normalize and resolve path to `NormPath` |
61
+ | `filterByTargets` | function | Filter file paths by target directory prefixes |
62
+
63
+ ### FsWatcher -- File System Watcher
64
+
65
+ Chokidar-based file watcher with debounced change events. See [docs/fs-watcher.md](docs/fs-watcher.md) for full API.
66
+
67
+ | Export | Kind | Description |
68
+ |--------|------|-------------|
69
+ | `FsWatcherEvent` | type | `"add" \| "addDir" \| "change" \| "unlink" \| "unlinkDir"` |
70
+ | `FsWatcherChangeInfo` | interface | Change event info with path and event type |
71
+ | `FsWatcher` | class | Debounced file system watcher |
72
+
73
+ ### Worker -- Typed Worker Threads
74
+
75
+ Type-safe worker thread wrapper with Proxy-based RPC. See [docs/worker.md](docs/worker.md) for full API.
76
+
77
+ | Export | Kind | Description |
78
+ |--------|------|-------------|
79
+ | `WorkerModule` | interface | Type structure for worker modules |
80
+ | `PromisifyMethods` | type | Maps sync method signatures to async |
81
+ | `WorkerProxy` | type | Proxy type returned by `Worker.create()` |
82
+ | `WorkerRequest` | interface | Internal worker request message |
83
+ | `WorkerResponse` | type | Internal worker response message (union) |
84
+ | `Worker` | const | Factory object with `create()` method |
85
+ | `createWorker` | function | Define a worker module in a worker thread |
86
+
87
+ ## Usage
88
+
89
+ ```ts
90
+ import { fsx, pathx } from "@simplysm/core-node";
91
+
92
+ // File system
93
+ await fsx.mkdir("/tmp/mydir");
94
+ await fsx.write("/tmp/mydir/hello.txt", "Hello, world!");
95
+ const content = await fsx.read("/tmp/mydir/hello.txt");
96
+ const config = await fsx.readJson<{ port: number }>("config.json");
97
+
98
+ // Path utilities
99
+ const posixPath = pathx.posix("C:\\Users\\test"); // "C:/Users/test"
100
+ const normalized = pathx.norm("/some/path"); // NormPath
101
+ const isChild = pathx.isChildPath("/a/b/c", "/a/b"); // true
102
+
103
+ // File watcher
104
+ import { FsWatcher } from "@simplysm/core-node";
105
+
106
+ const watcher = await FsWatcher.watch(["src/**/*.ts"]);
107
+ watcher.onChange({ delay: 300 }, (changes) => {
108
+ for (const { path, event } of changes) {
109
+ console.log(`${event}: ${path}`);
110
+ }
111
+ });
112
+ await watcher.close();
113
+
114
+ // Worker threads
115
+ import { Worker, createWorker } from "@simplysm/core-node";
116
+
117
+ // worker.ts
118
+ export default createWorker({
119
+ add: (a: number, b: number) => a + b,
120
+ });
121
+
122
+ // main.ts
123
+ const worker = Worker.create<typeof import("./worker")>("./worker.ts");
124
+ const result = await worker.add(10, 20); // 30
125
+ await worker.terminate();
126
+ ```
@@ -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
+ ```
package/docs/fsx.md ADDED
@@ -0,0 +1,287 @@
1
+ # fsx -- File System Utilities
2
+
3
+ Namespace providing sync and async file system operations with automatic error wrapping, recursive directory creation, and glob support.
4
+
5
+ ```ts
6
+ import { fsx } from "@simplysm/core-node";
7
+ ```
8
+
9
+ ## Functions
10
+
11
+ ### existsSync
12
+
13
+ ```ts
14
+ function existsSync(targetPath: string): boolean
15
+ ```
16
+
17
+ Check if a file or directory exists (synchronous).
18
+
19
+ ### exists
20
+
21
+ ```ts
22
+ async function exists(targetPath: string): Promise<boolean>
23
+ ```
24
+
25
+ Check if a file or directory exists (asynchronous).
26
+
27
+ ### mkdirSync
28
+
29
+ ```ts
30
+ function mkdirSync(targetPath: string): void
31
+ ```
32
+
33
+ Create a directory recursively (synchronous). Equivalent to `mkdir -p`.
34
+
35
+ ### mkdir
36
+
37
+ ```ts
38
+ async function mkdir(targetPath: string): Promise<void>
39
+ ```
40
+
41
+ Create a directory recursively (asynchronous). Equivalent to `mkdir -p`.
42
+
43
+ ### rmSync
44
+
45
+ ```ts
46
+ function rmSync(targetPath: string): void
47
+ ```
48
+
49
+ Remove a file or directory recursively (synchronous). Does not retry on failure -- use `rm` if transient errors (e.g., file locks) are expected.
50
+
51
+ ### rm
52
+
53
+ ```ts
54
+ async function rm(targetPath: string): Promise<void>
55
+ ```
56
+
57
+ Remove a file or directory recursively (asynchronous). Retries up to 6 times with 500ms delay on transient errors.
58
+
59
+ ### copySync
60
+
61
+ ```ts
62
+ function copySync(
63
+ sourcePath: string,
64
+ targetPath: string,
65
+ filter?: (absolutePath: string) => boolean,
66
+ ): void
67
+ ```
68
+
69
+ Copy a file or directory recursively (synchronous).
70
+
71
+ - If `sourcePath` does not exist, returns silently.
72
+ - `filter` receives the absolute path of each child entry. Return `true` to include, `false` to exclude.
73
+ - The top-level `sourcePath` is not subject to filtering -- only its descendants are.
74
+ - Returning `false` for a directory skips it and all its contents.
75
+
76
+ ### copy
77
+
78
+ ```ts
79
+ async function copy(
80
+ sourcePath: string,
81
+ targetPath: string,
82
+ filter?: (absolutePath: string) => boolean,
83
+ ): Promise<void>
84
+ ```
85
+
86
+ Copy a file or directory recursively (asynchronous). Same semantics as `copySync`.
87
+
88
+ ### readSync
89
+
90
+ ```ts
91
+ function readSync(targetPath: string): string
92
+ ```
93
+
94
+ Read a file as a UTF-8 string (synchronous).
95
+
96
+ ### read
97
+
98
+ ```ts
99
+ async function read(targetPath: string): Promise<string>
100
+ ```
101
+
102
+ Read a file as a UTF-8 string (asynchronous).
103
+
104
+ ### readBufferSync
105
+
106
+ ```ts
107
+ function readBufferSync(targetPath: string): Buffer
108
+ ```
109
+
110
+ Read a file as a `Buffer` (synchronous).
111
+
112
+ ### readBuffer
113
+
114
+ ```ts
115
+ async function readBuffer(targetPath: string): Promise<Buffer>
116
+ ```
117
+
118
+ Read a file as a `Buffer` (asynchronous).
119
+
120
+ ### readJsonSync
121
+
122
+ ```ts
123
+ function readJsonSync<TData = unknown>(targetPath: string): TData
124
+ ```
125
+
126
+ Read and parse a JSON file using `json.parse` from `@simplysm/core-common` (synchronous). On parse failure, the error message includes a preview of the file contents.
127
+
128
+ ### readJson
129
+
130
+ ```ts
131
+ async function readJson<TData = unknown>(targetPath: string): Promise<TData>
132
+ ```
133
+
134
+ Read and parse a JSON file using `json.parse` from `@simplysm/core-common` (asynchronous). On parse failure, the error message includes a preview of the file contents.
135
+
136
+ ### writeSync
137
+
138
+ ```ts
139
+ function writeSync(targetPath: string, data: string | Uint8Array): void
140
+ ```
141
+
142
+ Write data to a file (synchronous). Parent directories are created automatically. The file is flushed to disk.
143
+
144
+ ### write
145
+
146
+ ```ts
147
+ async function write(targetPath: string, data: string | Uint8Array): Promise<void>
148
+ ```
149
+
150
+ Write data to a file (asynchronous). Parent directories are created automatically. The file is flushed to disk.
151
+
152
+ ### writeJsonSync
153
+
154
+ ```ts
155
+ function writeJsonSync(
156
+ targetPath: string,
157
+ data: unknown,
158
+ options?: {
159
+ replacer?: (this: unknown, key: string | undefined, value: unknown) => unknown;
160
+ space?: string | number;
161
+ },
162
+ ): void
163
+ ```
164
+
165
+ Serialize data to JSON and write to a file (synchronous). Uses `json.stringify` from `@simplysm/core-common`.
166
+
167
+ | Option | Type | Description |
168
+ |--------|------|-------------|
169
+ | `replacer` | `(this: unknown, key: string \| undefined, value: unknown) => unknown` | Custom replacer function |
170
+ | `space` | `string \| number` | Indentation (spaces or string) |
171
+
172
+ ### writeJson
173
+
174
+ ```ts
175
+ async function writeJson(
176
+ targetPath: string,
177
+ data: unknown,
178
+ options?: {
179
+ replacer?: (this: unknown, key: string | undefined, value: unknown) => unknown;
180
+ space?: string | number;
181
+ },
182
+ ): Promise<void>
183
+ ```
184
+
185
+ Serialize data to JSON and write to a file (asynchronous). Uses `json.stringify` from `@simplysm/core-common`. Same options as `writeJsonSync`.
186
+
187
+ ### readdirSync
188
+
189
+ ```ts
190
+ function readdirSync(targetPath: string): string[]
191
+ ```
192
+
193
+ Read directory contents (synchronous). Returns an array of entry names.
194
+
195
+ ### readdir
196
+
197
+ ```ts
198
+ async function readdir(targetPath: string): Promise<string[]>
199
+ ```
200
+
201
+ Read directory contents (asynchronous). Returns an array of entry names.
202
+
203
+ ### statSync
204
+
205
+ ```ts
206
+ function statSync(targetPath: string): fs.Stats
207
+ ```
208
+
209
+ Get file/directory info (synchronous). Follows symbolic links.
210
+
211
+ ### stat
212
+
213
+ ```ts
214
+ async function stat(targetPath: string): Promise<fs.Stats>
215
+ ```
216
+
217
+ Get file/directory info (asynchronous). Follows symbolic links.
218
+
219
+ ### lstatSync
220
+
221
+ ```ts
222
+ function lstatSync(targetPath: string): fs.Stats
223
+ ```
224
+
225
+ Get file/directory info (synchronous). Does **not** follow symbolic links.
226
+
227
+ ### lstat
228
+
229
+ ```ts
230
+ async function lstat(targetPath: string): Promise<fs.Stats>
231
+ ```
232
+
233
+ Get file/directory info (asynchronous). Does **not** follow symbolic links.
234
+
235
+ ### globSync
236
+
237
+ ```ts
238
+ function globSync(pattern: string, options?: GlobOptions): string[]
239
+ ```
240
+
241
+ Search for files matching a glob pattern (synchronous). Returns absolute paths. Backslashes in the pattern are converted to forward slashes.
242
+
243
+ ### glob
244
+
245
+ ```ts
246
+ async function glob(pattern: string, options?: GlobOptions): Promise<string[]>
247
+ ```
248
+
249
+ Search for files matching a glob pattern (asynchronous). Returns absolute paths. Backslashes in the pattern are converted to forward slashes.
250
+
251
+ ### clearEmptyDirectory
252
+
253
+ ```ts
254
+ async function clearEmptyDirectory(dirPath: string): Promise<void>
255
+ ```
256
+
257
+ Recursively find and remove empty directories under `dirPath`. If removing children causes a parent directory to become empty, it is also removed.
258
+
259
+ ### findAllParentChildPathsSync
260
+
261
+ ```ts
262
+ function findAllParentChildPathsSync(
263
+ childGlob: string,
264
+ fromPath: string,
265
+ rootPath?: string,
266
+ ): string[]
267
+ ```
268
+
269
+ Walk from `fromPath` toward the filesystem root, collecting all files matching `childGlob` in each directory (synchronous).
270
+
271
+ | Parameter | Type | Description |
272
+ |-----------|------|-------------|
273
+ | `childGlob` | `string` | Glob pattern to match in each directory |
274
+ | `fromPath` | `string` | Starting path for the upward walk |
275
+ | `rootPath` | `string?` | Stop path (defaults to filesystem root). `fromPath` must be a descendant of `rootPath`. |
276
+
277
+ ### findAllParentChildPaths
278
+
279
+ ```ts
280
+ async function findAllParentChildPaths(
281
+ childGlob: string,
282
+ fromPath: string,
283
+ rootPath?: string,
284
+ ): Promise<string[]>
285
+ ```
286
+
287
+ Walk from `fromPath` toward the filesystem root, collecting all files matching `childGlob` in each directory (asynchronous). Same parameters as `findAllParentChildPathsSync`.
package/docs/pathx.md ADDED
@@ -0,0 +1,115 @@
1
+ # pathx -- Path Utilities
2
+
3
+ Namespace providing path manipulation utilities with normalization, POSIX conversion, and filtering.
4
+
5
+ ```ts
6
+ import { pathx } from "@simplysm/core-node";
7
+ ```
8
+
9
+ ## Types
10
+
11
+ ### NormPath
12
+
13
+ ```ts
14
+ type NormPath = string & { [NORM]: never }
15
+ ```
16
+
17
+ Branded type representing a normalized absolute path. Can only be created via `norm()`. The brand prevents accidental use of raw strings where a normalized path is expected.
18
+
19
+ ## Functions
20
+
21
+ ### posix
22
+
23
+ ```ts
24
+ function posix(...args: string[]): string
25
+ ```
26
+
27
+ Convert a path to POSIX style by joining the arguments and replacing backslashes with forward slashes.
28
+
29
+ ```ts
30
+ pathx.posix("C:\\Users\\test"); // "C:/Users/test"
31
+ pathx.posix("src", "index.ts"); // "src/index.ts"
32
+ ```
33
+
34
+ ### changeFileDirectory
35
+
36
+ ```ts
37
+ function changeFileDirectory(
38
+ filePath: string,
39
+ fromDirectory: string,
40
+ toDirectory: string,
41
+ ): string
42
+ ```
43
+
44
+ Change the directory portion of a file path. The file must be inside `fromDirectory`; throws `ArgumentError` otherwise.
45
+
46
+ ```ts
47
+ pathx.changeFileDirectory("/a/b/c.txt", "/a", "/x");
48
+ // "/x/b/c.txt"
49
+ ```
50
+
51
+ ### basenameWithoutExt
52
+
53
+ ```ts
54
+ function basenameWithoutExt(filePath: string): string
55
+ ```
56
+
57
+ Return the filename without its extension.
58
+
59
+ ```ts
60
+ pathx.basenameWithoutExt("file.txt"); // "file"
61
+ pathx.basenameWithoutExt("/path/to/file.spec.ts"); // "file.spec"
62
+ ```
63
+
64
+ ### isChildPath
65
+
66
+ ```ts
67
+ function isChildPath(childPath: string, parentPath: string): boolean
68
+ ```
69
+
70
+ Check if `childPath` is a descendant of `parentPath`. Returns `false` for identical paths. Paths are normalized internally via `norm()`.
71
+
72
+ ```ts
73
+ pathx.isChildPath("/a/b/c", "/a/b"); // true
74
+ pathx.isChildPath("/a/b", "/a/b/c"); // false
75
+ pathx.isChildPath("/a/b", "/a/b"); // false (same path)
76
+ ```
77
+
78
+ ### norm
79
+
80
+ ```ts
81
+ function norm(...paths: string[]): NormPath
82
+ ```
83
+
84
+ Normalize and resolve path segments into an absolute `NormPath`. Uses the platform-specific path separator.
85
+
86
+ ```ts
87
+ pathx.norm("/some/path"); // NormPath
88
+ pathx.norm("relative", "path"); // NormPath (resolved to absolute)
89
+ ```
90
+
91
+ ### filterByTargets
92
+
93
+ ```ts
94
+ function filterByTargets(
95
+ files: string[],
96
+ targets: string[],
97
+ cwd: string,
98
+ ): string[]
99
+ ```
100
+
101
+ Filter file paths by target directory prefixes.
102
+
103
+ | Parameter | Type | Description |
104
+ |-----------|------|-------------|
105
+ | `files` | `string[]` | Absolute file paths to filter (must be under `cwd`) |
106
+ | `targets` | `string[]` | Target directory prefixes (relative to `cwd`, POSIX style recommended) |
107
+ | `cwd` | `string` | Current working directory (absolute path) |
108
+
109
+ Returns `files` unchanged if `targets` is empty. Otherwise returns only files whose relative path matches or is a child of a target.
110
+
111
+ ```ts
112
+ const files = ["/proj/src/a.ts", "/proj/src/b.ts", "/proj/tests/c.ts"];
113
+ pathx.filterByTargets(files, ["src"], "/proj");
114
+ // ["/proj/src/a.ts", "/proj/src/b.ts"]
115
+ ```
package/docs/worker.md ADDED
@@ -0,0 +1,223 @@
1
+ # Worker -- Typed Worker Threads
2
+
3
+ Type-safe worker thread wrapper providing Proxy-based RPC and event communication between the main thread and worker threads.
4
+
5
+ ```ts
6
+ import { Worker, createWorker } from "@simplysm/core-node";
7
+ import type {
8
+ WorkerModule,
9
+ PromisifyMethods,
10
+ WorkerProxy,
11
+ WorkerRequest,
12
+ WorkerResponse,
13
+ } from "@simplysm/core-node";
14
+ ```
15
+
16
+ ## Types
17
+
18
+ ### WorkerModule (interface)
19
+
20
+ ```ts
21
+ interface WorkerModule {
22
+ default: {
23
+ __methods: Record<string, (...args: any[]) => unknown>;
24
+ __events: Record<string, unknown>;
25
+ };
26
+ }
27
+ ```
28
+
29
+ Type structure representing a worker module. Used with `Worker.create<typeof import("./worker")>()` for type inference.
30
+
31
+ | Field | Type | Description |
32
+ |-------|------|-------------|
33
+ | `default.__methods` | `Record<string, (...args: any[]) => unknown>` | Map of callable worker methods |
34
+ | `default.__events` | `Record<string, unknown>` | Map of event names to event data types |
35
+
36
+ ### PromisifyMethods
37
+
38
+ ```ts
39
+ type PromisifyMethods<TMethods> = {
40
+ [K in keyof TMethods]: TMethods[K] extends (...args: infer P) => infer R
41
+ ? (...args: P) => Promise<Awaited<R>>
42
+ : never;
43
+ }
44
+ ```
45
+
46
+ Utility type that wraps all method return types in `Promise`. Worker methods communicate via `postMessage` and are inherently asynchronous, so even synchronous method signatures are converted to `Promise<Awaited<R>>`.
47
+
48
+ ### WorkerProxy
49
+
50
+ ```ts
51
+ type WorkerProxy<TModule extends WorkerModule> = PromisifyMethods<
52
+ TModule["default"]["__methods"]
53
+ > & {
54
+ on<TEventName extends keyof TModule["default"]["__events"] & string>(
55
+ event: TEventName,
56
+ listener: (data: TModule["default"]["__events"][TEventName]) => void,
57
+ ): void;
58
+
59
+ off<TEventName extends keyof TModule["default"]["__events"] & string>(
60
+ event: TEventName,
61
+ listener: (data: TModule["default"]["__events"][TEventName]) => void,
62
+ ): void;
63
+
64
+ terminate(): Promise<void>;
65
+ }
66
+ ```
67
+
68
+ The proxy type returned by `Worker.create()`. Combines promisified worker methods with event handling and termination.
69
+
70
+ | Method | Description |
71
+ |--------|-------------|
72
+ | `on(event, listener)` | Register an event listener |
73
+ | `off(event, listener)` | Remove an event listener |
74
+ | `terminate()` | Terminate the worker thread |
75
+
76
+ ### WorkerRequest (interface)
77
+
78
+ ```ts
79
+ interface WorkerRequest {
80
+ id: string;
81
+ method: string;
82
+ params: unknown[];
83
+ }
84
+ ```
85
+
86
+ Internal message format sent from the main thread to the worker.
87
+
88
+ | Field | Type | Description |
89
+ |-------|------|-------------|
90
+ | `id` | `string` | Unique request identifier (UUID) |
91
+ | `method` | `string` | Name of the method to invoke |
92
+ | `params` | `unknown[]` | Method arguments |
93
+
94
+ ### WorkerResponse (type)
95
+
96
+ ```ts
97
+ type WorkerResponse =
98
+ | { request: WorkerRequest; type: "return"; body?: unknown }
99
+ | { request: WorkerRequest; type: "error"; body: Error }
100
+ | { type: "event"; event: string; body?: unknown }
101
+ | { type: "log"; body: string }
102
+ ```
103
+
104
+ Internal message format sent from the worker to the main thread. A discriminated union with four variants:
105
+
106
+ | Variant | Fields | Description |
107
+ |---------|--------|-------------|
108
+ | `return` | `request`, `body?` | Successful method return value |
109
+ | `error` | `request`, `body` | Method threw an error |
110
+ | `event` | `event`, `body?` | Worker-emitted event |
111
+ | `log` | `body` | Redirected stdout output |
112
+
113
+ ## Worker (const)
114
+
115
+ Factory object for creating type-safe worker proxies.
116
+
117
+ ### Worker.create
118
+
119
+ ```ts
120
+ Worker.create<TModule extends WorkerModule>(
121
+ filePath: string,
122
+ opt?: Omit<WorkerRawOptions, "stdout" | "stderr">,
123
+ ): WorkerProxy<TModule>
124
+ ```
125
+
126
+ Create a type-safe worker proxy.
127
+
128
+ | Parameter | Type | Description |
129
+ |-----------|------|-------------|
130
+ | `filePath` | `string` | Worker file path (`file://` URL or absolute path) |
131
+ | `opt` | `Omit<WorkerRawOptions, "stdout" \| "stderr">?` | Node.js `WorkerOptions` (excluding stdout/stderr which are managed internally) |
132
+
133
+ In development (`.ts` files), the worker is loaded via `tsx`. In production (`.js` files), the worker is loaded directly.
134
+
135
+ Worker stdout/stderr is piped to the main process. On abnormal exit, all pending requests are rejected.
136
+
137
+ ## createWorker (function)
138
+
139
+ ```ts
140
+ function createWorker<
141
+ TMethods extends Record<string, (...args: any[]) => unknown>,
142
+ TEvents extends Record<string, unknown> = Record<string, never>,
143
+ >(
144
+ methods: TMethods,
145
+ ): {
146
+ send<TEventName extends keyof TEvents & string>(
147
+ event: TEventName,
148
+ data?: TEvents[TEventName],
149
+ ): void;
150
+ __methods: TMethods;
151
+ __events: TEvents;
152
+ }
153
+ ```
154
+
155
+ Define a worker module inside a worker thread file. Returns a sender object that can emit events to the main thread.
156
+
157
+ | Parameter | Type | Description |
158
+ |-----------|------|-------------|
159
+ | `methods` | `TMethods` | Object mapping method names to handler functions |
160
+
161
+ The returned object exposes:
162
+
163
+ | Property/Method | Description |
164
+ |-----------------|-------------|
165
+ | `send(event, data?)` | Emit a typed event to the main thread |
166
+ | `__methods` | Type-level reference to the methods map (used for type inference) |
167
+ | `__events` | Type-level reference to the events map (used for type inference) |
168
+
169
+ Throws if not running in a worker thread (`parentPort` is null).
170
+
171
+ ## Usage
172
+
173
+ ### Basic worker (no events)
174
+
175
+ ```ts
176
+ // math-worker.ts
177
+ import { createWorker } from "@simplysm/core-node";
178
+
179
+ export default createWorker({
180
+ add: (a: number, b: number) => a + b,
181
+ multiply: (a: number, b: number) => a * b,
182
+ });
183
+
184
+ // main.ts
185
+ import { Worker } from "@simplysm/core-node";
186
+
187
+ const worker = Worker.create<typeof import("./math-worker")>("./math-worker.ts");
188
+ const sum = await worker.add(10, 20); // 30
189
+ const product = await worker.multiply(3, 7); // 21
190
+ await worker.terminate();
191
+ ```
192
+
193
+ ### Worker with events
194
+
195
+ ```ts
196
+ // process-worker.ts
197
+ import { createWorker } from "@simplysm/core-node";
198
+
199
+ interface Events {
200
+ progress: number;
201
+ }
202
+
203
+ const methods = {
204
+ processData: (items: string[]) => {
205
+ for (let i = 0; i < items.length; i++) {
206
+ // ... process item ...
207
+ sender.send("progress", ((i + 1) / items.length) * 100);
208
+ }
209
+ return items.length;
210
+ },
211
+ };
212
+
213
+ const sender = createWorker<typeof methods, Events>(methods);
214
+ export default sender;
215
+
216
+ // main.ts
217
+ import { Worker } from "@simplysm/core-node";
218
+
219
+ const worker = Worker.create<typeof import("./process-worker")>("./process-worker.ts");
220
+ worker.on("progress", (pct) => console.log(`${pct}% done`));
221
+ const count = await worker.processData(["a", "b", "c"]);
222
+ await worker.terminate();
223
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplysm/core-node",
3
- "version": "14.0.1",
3
+ "version": "14.0.4",
4
4
  "description": "심플리즘 패키지 - 코어 (node)",
5
5
  "author": "심플리즘",
6
6
  "license": "Apache-2.0",
@@ -15,7 +15,8 @@
15
15
  "files": [
16
16
  "dist",
17
17
  "src",
18
- "lib"
18
+ "lib",
19
+ "docs"
19
20
  ],
20
21
  "sideEffects": false,
21
22
  "dependencies": {
@@ -24,9 +25,9 @@
24
25
  "glob": "^13.0.6",
25
26
  "minimatch": "^10.2.4",
26
27
  "tsx": "^4.21.0",
27
- "@simplysm/core-common": "14.0.1"
28
+ "@simplysm/core-common": "14.0.4"
28
29
  },
29
30
  "devDependencies": {
30
- "@types/node": "^20.14.8"
31
+ "@types/node": "^20.19.37"
31
32
  }
32
33
  }