@simplysm/core-node 14.0.1 → 14.0.5

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,155 @@
1
+ # @simplysm/core-node
2
+
3
+ Node.js-specific core utilities for the Simplysm framework. Provides enhanced file system operations, path utilities, file watching, and a type-safe worker thread abstraction.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @simplysm/core-node
9
+ ```
10
+
11
+ ## API Overview
12
+
13
+ ### Utilities / fsx
14
+
15
+ Namespace `fsx` -- Enhanced file system functions (sync and async pairs).
16
+
17
+ | API | Type | Description |
18
+ |-----|------|-------------|
19
+ | `exists` | function | Check if a path exists (async) |
20
+ | `existsSync` | function | Check if a path exists (sync) |
21
+ | `mkdir` | function | Create directory recursively (async) |
22
+ | `mkdirSync` | function | Create directory recursively (sync) |
23
+ | `rm` | function | Remove file/directory with retry (async) |
24
+ | `rmSync` | function | Remove file/directory (sync) |
25
+ | `copy` | function | Copy file/directory with filter (async) |
26
+ | `copySync` | function | Copy file/directory with filter (sync) |
27
+ | `read` | function | Read file as UTF-8 string (async) |
28
+ | `readSync` | function | Read file as UTF-8 string (sync) |
29
+ | `readBuffer` | function | Read file as Buffer (async) |
30
+ | `readBufferSync` | function | Read file as Buffer (sync) |
31
+ | `readJson` | function | Read and parse JSON file (async) |
32
+ | `readJsonSync` | function | Read and parse JSON file (sync) |
33
+ | `write` | function | Write data to file (async) |
34
+ | `writeSync` | function | Write data to file (sync) |
35
+ | `writeJson` | function | Write data as JSON file (async) |
36
+ | `writeJsonSync` | function | Write data as JSON file (sync) |
37
+ | `readdir` | function | List directory contents (async) |
38
+ | `readdirSync` | function | List directory contents (sync) |
39
+ | `stat` | function | Get file stats, follows symlinks (async) |
40
+ | `statSync` | function | Get file stats, follows symlinks (sync) |
41
+ | `lstat` | function | Get file stats, no symlink follow (async) |
42
+ | `lstatSync` | function | Get file stats, no symlink follow (sync) |
43
+ | `glob` | function | Search files by glob pattern (async) |
44
+ | `globSync` | function | Search files by glob pattern (sync) |
45
+ | `clearEmptyDirectory` | function | Recursively remove empty directories (async) |
46
+ | `findAllParentChildPaths` | function | Search parent dirs for glob matches (async) |
47
+ | `findAllParentChildPathsSync` | function | Search parent dirs for glob matches (sync) |
48
+
49
+ > See [docs/fsx.md](./docs/fsx.md) for details.
50
+
51
+ ### Utilities / pathx
52
+
53
+ Namespace `pathx` -- Path manipulation utilities.
54
+
55
+ | API | Type | Description |
56
+ |-----|------|-------------|
57
+ | `NormPath` | type | Branded string type for normalized paths |
58
+ | `posix` | function | Convert path to POSIX style (backslash to slash) |
59
+ | `changeFileDirectory` | function | Change a file's parent directory |
60
+ | `basenameWithoutExt` | function | Get filename without extension |
61
+ | `isChildPath` | function | Check if a path is a child of another |
62
+ | `norm` | function | Normalize and resolve path to `NormPath` |
63
+ | `filterByTargets` | function | Filter file paths by target directory prefixes |
64
+
65
+ > See [docs/pathx.md](./docs/pathx.md) for details.
66
+
67
+ ### Features
68
+
69
+ | API | Type | Description |
70
+ |-----|------|-------------|
71
+ | `FsWatcherEvent` | type | File change event type union |
72
+ | `FsWatcherChangeInfo` | interface | File change event info |
73
+ | `FsWatcher` | class | Debounced file system watcher (chokidar-based) |
74
+
75
+ > See [docs/fs-watcher.md](./docs/fs-watcher.md) for details.
76
+
77
+ ### Worker
78
+
79
+ | API | Type | Description |
80
+ |-----|------|-------------|
81
+ | `WorkerModule` | interface | Type structure for worker modules |
82
+ | `PromisifyMethods` | type | Maps sync methods to async (Promise) |
83
+ | `WorkerProxy` | type | Proxy type returned by `Worker.create()` |
84
+ | `WorkerRequest` | interface | Internal worker request message |
85
+ | `WorkerResponse` | type | Internal worker response message |
86
+ | `Worker` | object | Type-safe worker thread factory |
87
+ | `createWorker` | function | Create a worker module in the worker thread |
88
+
89
+ > See [docs/worker.md](./docs/worker.md) for details.
90
+
91
+ ## Usage Examples
92
+
93
+ ### File system operations
94
+
95
+ ```typescript
96
+ import { fsx } from "@simplysm/core-node";
97
+
98
+ // Read/write files
99
+ const content = await fsx.read("/path/to/file.txt");
100
+ await fsx.write("/path/to/output.txt", "hello");
101
+
102
+ // JSON
103
+ const data = await fsx.readJson<{ name: string }>("/path/to/config.json");
104
+ await fsx.writeJson("/path/to/out.json", data, { space: 2 });
105
+
106
+ // Copy with filter
107
+ await fsx.copy("/src", "/dest", (p) => !p.endsWith(".tmp"));
108
+
109
+ // Glob
110
+ const tsFiles = await fsx.glob("/project/src/**/*.ts");
111
+ ```
112
+
113
+ ### Path utilities
114
+
115
+ ```typescript
116
+ import { pathx } from "@simplysm/core-node";
117
+
118
+ const p = pathx.posix("C:\\Users\\test"); // "C:/Users/test"
119
+ const name = pathx.basenameWithoutExt("file.spec.ts"); // "file.spec"
120
+ const isChild = pathx.isChildPath("/a/b/c", "/a/b"); // true
121
+ const norm = pathx.norm("/some/path"); // NormPath
122
+ ```
123
+
124
+ ### File watcher
125
+
126
+ ```typescript
127
+ import { FsWatcher } from "@simplysm/core-node";
128
+
129
+ const watcher = await FsWatcher.watch(["src/**/*.ts"]);
130
+ watcher.onChange({ delay: 300 }, (changes) => {
131
+ for (const { path, event } of changes) {
132
+ console.log(`${event}: ${path}`);
133
+ }
134
+ });
135
+
136
+ await watcher.close();
137
+ ```
138
+
139
+ ### Worker threads
140
+
141
+ ```typescript
142
+ // worker.ts
143
+ import { createWorker } from "@simplysm/core-node";
144
+
145
+ export default createWorker({
146
+ add: (a: number, b: number) => a + b,
147
+ });
148
+
149
+ // main.ts
150
+ import { Worker } from "@simplysm/core-node";
151
+
152
+ const worker = Worker.create<typeof import("./worker")>("./worker.ts");
153
+ const result = await worker.add(10, 20); // 30
154
+ await worker.terminate();
155
+ ```
@@ -0,0 +1,105 @@
1
+ # FsWatcher
2
+
3
+ Chokidar-based file system watcher with debounced event merging. Short-lived events on the same file are consolidated into a single callback invocation.
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
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
35
+
36
+ Debounced file system watcher. Events occurring within the debounce window are merged using the following strategy:
37
+ - `add` + `change` --> `add` (modification right after creation is treated as creation)
38
+ - `add` + `unlink` --> removed (creation then deletion cancels out)
39
+ - `unlink` + `add` --> `add` (deletion then recreation is treated as creation)
40
+ - Other combinations --> latest event wins
41
+
42
+ The constructor is private; use the static `watch` method.
43
+
44
+ **Note:** `ignoreInitial` is always forced to `true` internally. If you pass `ignoreInitial: false`, the first `onChange` callback fires with an empty array (initial file listing is not included).
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 (async). Resolves when chokidar emits the `ready` event.
58
+
59
+ | Parameter | Type | Description |
60
+ |-----------|------|-------------|
61
+ | `paths` | `string[]` | File/directory paths or glob patterns to watch |
62
+ | `options` | `chokidar.ChokidarOptions` | Chokidar options (except `ignoreInitial`, which is forced `true`) |
63
+
64
+ ### Instance Methods
65
+
66
+ #### onChange
67
+
68
+ ```ts
69
+ onChange(
70
+ opt: { delay?: number },
71
+ cb: (changeInfos: FsWatcherChangeInfo[]) => void | Promise<void>,
72
+ ): this
73
+ ```
74
+
75
+ Register a file change event handler. Events are collected for the specified delay, then delivered as a batch.
76
+
77
+ | Parameter | Type | Description |
78
+ |-----------|------|-------------|
79
+ | `opt.delay` | `number` | Debounce delay in milliseconds |
80
+ | `cb` | `(changeInfos: FsWatcherChangeInfo[]) => void \| Promise<void>` | Callback receiving batched change events |
81
+
82
+ **Returns:** `this` for chaining.
83
+
84
+ #### close
85
+
86
+ ```ts
87
+ async close(): Promise<void>
88
+ ```
89
+
90
+ Stop the watcher and dispose all debounce queues.
91
+
92
+ ## Usage
93
+
94
+ ```ts
95
+ const watcher = await FsWatcher.watch(["src/**/*.ts"], { depth: 3 });
96
+
97
+ watcher.onChange({ delay: 300 }, (changes) => {
98
+ for (const { path, event } of changes) {
99
+ console.log(`${event}: ${path}`);
100
+ }
101
+ });
102
+
103
+ // Later: stop watching
104
+ await watcher.close();
105
+ ```
package/docs/fsx.md ADDED
@@ -0,0 +1,314 @@
1
+ # fsx
2
+
3
+ Namespace of enhanced file system functions. All functions wrap Node.js `fs` with error handling (wrapping in `SdError`) and convenience features like recursive directory creation. Most functions come in sync/async pairs.
4
+
5
+ Imported as:
6
+ ```typescript
7
+ import { fsx } from "@simplysm/core-node";
8
+ ```
9
+
10
+ ## Existence
11
+
12
+ ### existsSync
13
+
14
+ ```ts
15
+ function existsSync(targetPath: string): boolean
16
+ ```
17
+
18
+ Check if a file or directory exists (sync).
19
+
20
+ ### exists
21
+
22
+ ```ts
23
+ async function exists(targetPath: string): Promise<boolean>
24
+ ```
25
+
26
+ Check if a file or directory exists (async).
27
+
28
+ ## Directory Creation
29
+
30
+ ### mkdirSync
31
+
32
+ ```ts
33
+ function mkdirSync(targetPath: string): void
34
+ ```
35
+
36
+ Create a directory recursively (sync).
37
+
38
+ ### mkdir
39
+
40
+ ```ts
41
+ async function mkdir(targetPath: string): Promise<void>
42
+ ```
43
+
44
+ Create a directory recursively (async).
45
+
46
+ ## Removal
47
+
48
+ ### rmSync
49
+
50
+ ```ts
51
+ function rmSync(targetPath: string): void
52
+ ```
53
+
54
+ Remove a file or directory (sync). No retry -- fails immediately on error.
55
+
56
+ ### rm
57
+
58
+ ```ts
59
+ async function rm(targetPath: string): Promise<void>
60
+ ```
61
+
62
+ Remove a file or directory (async). Retries up to 6 times with 500ms delay for transient errors (e.g., file locks).
63
+
64
+ ## Copy
65
+
66
+ ### copySync
67
+
68
+ ```ts
69
+ function copySync(
70
+ sourcePath: string,
71
+ targetPath: string,
72
+ filter?: (absolutePath: string) => boolean,
73
+ ): void
74
+ ```
75
+
76
+ Copy a file or directory (sync). If `sourcePath` does not exist, returns silently. Directories are copied recursively. An optional filter function controls which children are included.
77
+
78
+ | Parameter | Type | Description |
79
+ |-----------|------|-------------|
80
+ | `sourcePath` | `string` | Source path to copy |
81
+ | `targetPath` | `string` | Destination path |
82
+ | `filter` | `(absolutePath: string) => boolean` | Optional filter applied to all children (not the root). Return `true` to include. If a directory returns `false`, it and all contents are skipped. |
83
+
84
+ ### copy
85
+
86
+ ```ts
87
+ async function copy(
88
+ sourcePath: string,
89
+ targetPath: string,
90
+ filter?: (absolutePath: string) => boolean,
91
+ ): Promise<void>
92
+ ```
93
+
94
+ Copy a file or directory (async). Same behavior as `copySync`.
95
+
96
+ ## File Reading
97
+
98
+ ### readSync
99
+
100
+ ```ts
101
+ function readSync(targetPath: string): string
102
+ ```
103
+
104
+ Read a file as a UTF-8 string (sync).
105
+
106
+ ### read
107
+
108
+ ```ts
109
+ async function read(targetPath: string): Promise<string>
110
+ ```
111
+
112
+ Read a file as a UTF-8 string (async).
113
+
114
+ ### readBufferSync
115
+
116
+ ```ts
117
+ function readBufferSync(targetPath: string): Buffer
118
+ ```
119
+
120
+ Read a file as a Buffer (sync).
121
+
122
+ ### readBuffer
123
+
124
+ ```ts
125
+ async function readBuffer(targetPath: string): Promise<Buffer>
126
+ ```
127
+
128
+ Read a file as a Buffer (async).
129
+
130
+ ### readJsonSync
131
+
132
+ ```ts
133
+ function readJsonSync<TData = unknown>(targetPath: string): TData
134
+ ```
135
+
136
+ Read and parse a JSON file using `JsonConvert` (sync). On parse failure, the error message includes a preview of the file contents.
137
+
138
+ ### readJson
139
+
140
+ ```ts
141
+ async function readJson<TData = unknown>(targetPath: string): Promise<TData>
142
+ ```
143
+
144
+ Read and parse a JSON file using `JsonConvert` (async). On parse failure, the error message includes a preview of the file contents.
145
+
146
+ ## File Writing
147
+
148
+ ### writeSync
149
+
150
+ ```ts
151
+ function writeSync(targetPath: string, data: string | Uint8Array): void
152
+ ```
153
+
154
+ Write data to a file (sync). Parent directories are created automatically. Uses `flush: true`.
155
+
156
+ ### write
157
+
158
+ ```ts
159
+ async function write(targetPath: string, data: string | Uint8Array): Promise<void>
160
+ ```
161
+
162
+ Write data to a file (async). Parent directories are created automatically. Uses `flush: true`.
163
+
164
+ ### writeJsonSync
165
+
166
+ ```ts
167
+ function writeJsonSync(
168
+ targetPath: string,
169
+ data: unknown,
170
+ options?: {
171
+ replacer?: (this: unknown, key: string | undefined, value: unknown) => unknown;
172
+ space?: string | number;
173
+ },
174
+ ): void
175
+ ```
176
+
177
+ Write data as a JSON file using `JsonConvert` (sync).
178
+
179
+ | Parameter | Type | Description |
180
+ |-----------|------|-------------|
181
+ | `targetPath` | `string` | Target file path |
182
+ | `data` | `unknown` | Data to serialize |
183
+ | `options.replacer` | `function` | Custom JSON replacer |
184
+ | `options.space` | `string \| number` | Indentation |
185
+
186
+ ### writeJson
187
+
188
+ ```ts
189
+ async function writeJson(
190
+ targetPath: string,
191
+ data: unknown,
192
+ options?: {
193
+ replacer?: (this: unknown, key: string | undefined, value: unknown) => unknown;
194
+ space?: string | number;
195
+ },
196
+ ): Promise<void>
197
+ ```
198
+
199
+ Write data as a JSON file using `JsonConvert` (async). Same options as `writeJsonSync`.
200
+
201
+ ## Directory Reading
202
+
203
+ ### readdirSync
204
+
205
+ ```ts
206
+ function readdirSync(targetPath: string): string[]
207
+ ```
208
+
209
+ List the contents of a directory (sync). Returns entry names (not full paths).
210
+
211
+ ### readdir
212
+
213
+ ```ts
214
+ async function readdir(targetPath: string): Promise<string[]>
215
+ ```
216
+
217
+ List the contents of a directory (async). Returns entry names (not full paths).
218
+
219
+ ## File Stats
220
+
221
+ ### statSync
222
+
223
+ ```ts
224
+ function statSync(targetPath: string): fs.Stats
225
+ ```
226
+
227
+ Get file/directory stats, following symlinks (sync).
228
+
229
+ ### stat
230
+
231
+ ```ts
232
+ async function stat(targetPath: string): Promise<fs.Stats>
233
+ ```
234
+
235
+ Get file/directory stats, following symlinks (async).
236
+
237
+ ### lstatSync
238
+
239
+ ```ts
240
+ function lstatSync(targetPath: string): fs.Stats
241
+ ```
242
+
243
+ Get file/directory stats without following symlinks (sync).
244
+
245
+ ### lstat
246
+
247
+ ```ts
248
+ async function lstat(targetPath: string): Promise<fs.Stats>
249
+ ```
250
+
251
+ Get file/directory stats without following symlinks (async).
252
+
253
+ ## Glob
254
+
255
+ ### globSync
256
+
257
+ ```ts
258
+ function globSync(pattern: string, options?: GlobOptions): string[]
259
+ ```
260
+
261
+ Search files by glob pattern (sync). Returns absolute paths.
262
+
263
+ | Parameter | Type | Description |
264
+ |-----------|------|-------------|
265
+ | `pattern` | `string` | Glob pattern (e.g., `"**/*.ts"`) |
266
+ | `options` | `GlobOptions` | Options passed to the `glob` library |
267
+
268
+ ### glob
269
+
270
+ ```ts
271
+ async function glob(pattern: string, options?: GlobOptions): Promise<string[]>
272
+ ```
273
+
274
+ Search files by glob pattern (async). Returns absolute paths.
275
+
276
+ ## Utilities
277
+
278
+ ### clearEmptyDirectory
279
+
280
+ ```ts
281
+ async function clearEmptyDirectory(dirPath: string): Promise<void>
282
+ ```
283
+
284
+ Recursively find and remove empty directories under a given path (async). When all subdirectories are removed and a parent becomes empty, it is also removed.
285
+
286
+ ### findAllParentChildPathsSync
287
+
288
+ ```ts
289
+ function findAllParentChildPathsSync(
290
+ childGlob: string,
291
+ fromPath: string,
292
+ rootPath?: string,
293
+ ): string[]
294
+ ```
295
+
296
+ Walk from `fromPath` upward toward the filesystem root, running a glob pattern at each level and collecting all matches (sync).
297
+
298
+ | Parameter | Type | Description |
299
+ |-----------|------|-------------|
300
+ | `childGlob` | `string` | Glob pattern to search at each directory level |
301
+ | `fromPath` | `string` | Starting path |
302
+ | `rootPath` | `string` | Optional stop path (must be an ancestor of `fromPath`) |
303
+
304
+ ### findAllParentChildPaths
305
+
306
+ ```ts
307
+ async function findAllParentChildPaths(
308
+ childGlob: string,
309
+ fromPath: string,
310
+ rootPath?: string,
311
+ ): Promise<string[]>
312
+ ```
313
+
314
+ Same as `findAllParentChildPathsSync` but asynchronous.
package/docs/pathx.md ADDED
@@ -0,0 +1,122 @@
1
+ # pathx
2
+
3
+ Namespace of path manipulation utilities. Provides normalized path types, POSIX conversion, and directory-based filtering.
4
+
5
+ Imported as:
6
+ ```typescript
7
+ import { pathx } from "@simplysm/core-node";
8
+ ```
9
+
10
+ ## Types
11
+
12
+ ### NormPath
13
+
14
+ ```ts
15
+ type NormPath = string & { [NORM]: never }
16
+ ```
17
+
18
+ Branded string type representing a normalized, resolved absolute path. Can only be created via `norm()`.
19
+
20
+ ## Functions
21
+
22
+ ### posix
23
+
24
+ ```ts
25
+ function posix(...args: string[]): string
26
+ ```
27
+
28
+ Convert a path to POSIX style (backslashes replaced with forward slashes). Accepts multiple segments which are joined with `path.join`.
29
+
30
+ ```ts
31
+ pathx.posix("C:\\Users\\test"); // "C:/Users/test"
32
+ pathx.posix("src", "index.ts"); // "src/index.ts"
33
+ ```
34
+
35
+ ### changeFileDirectory
36
+
37
+ ```ts
38
+ function changeFileDirectory(
39
+ filePath: string,
40
+ fromDirectory: string,
41
+ toDirectory: string,
42
+ ): string
43
+ ```
44
+
45
+ Change the parent directory of a file path. The file must be inside `fromDirectory`.
46
+
47
+ | Parameter | Type | Description |
48
+ |-----------|------|-------------|
49
+ | `filePath` | `string` | File path to transform |
50
+ | `fromDirectory` | `string` | Original parent directory |
51
+ | `toDirectory` | `string` | New parent directory |
52
+
53
+ **Throws:** `ArgumentError` if `filePath` is not inside `fromDirectory`.
54
+
55
+ ```ts
56
+ pathx.changeFileDirectory("/a/b/c.txt", "/a", "/x");
57
+ // "/x/b/c.txt"
58
+ ```
59
+
60
+ ### basenameWithoutExt
61
+
62
+ ```ts
63
+ function basenameWithoutExt(filePath: string): string
64
+ ```
65
+
66
+ Get the filename without its extension (last extension only).
67
+
68
+ ```ts
69
+ pathx.basenameWithoutExt("file.txt"); // "file"
70
+ pathx.basenameWithoutExt("/path/to/file.spec.ts"); // "file.spec"
71
+ ```
72
+
73
+ ### isChildPath
74
+
75
+ ```ts
76
+ function isChildPath(childPath: string, parentPath: string): boolean
77
+ ```
78
+
79
+ Check if `childPath` is a descendant of `parentPath`. Returns `false` for identical paths. Paths are normalized internally via `norm()`.
80
+
81
+ ```ts
82
+ pathx.isChildPath("/a/b/c", "/a/b"); // true
83
+ pathx.isChildPath("/a/b", "/a/b/c"); // false
84
+ pathx.isChildPath("/a/b", "/a/b"); // false (same path)
85
+ ```
86
+
87
+ ### norm
88
+
89
+ ```ts
90
+ function norm(...paths: string[]): NormPath
91
+ ```
92
+
93
+ Normalize and resolve path segments into a `NormPath` (absolute, platform-native separators).
94
+
95
+ ```ts
96
+ pathx.norm("/some/path"); // NormPath
97
+ pathx.norm("relative", "path"); // NormPath (resolved to absolute)
98
+ ```
99
+
100
+ ### filterByTargets
101
+
102
+ ```ts
103
+ function filterByTargets(
104
+ files: string[],
105
+ targets: string[],
106
+ cwd: string,
107
+ ): string[]
108
+ ```
109
+
110
+ Filter a list of file paths to only include those under the specified target directories. If `targets` is empty, all files are returned.
111
+
112
+ | Parameter | Type | Description |
113
+ |-----------|------|-------------|
114
+ | `files` | `string[]` | Absolute file paths to filter (should be under `cwd`) |
115
+ | `targets` | `string[]` | Target directory paths relative to `cwd` (POSIX style recommended) |
116
+ | `cwd` | `string` | Current working directory (absolute path) |
117
+
118
+ ```ts
119
+ const files = ["/proj/src/a.ts", "/proj/src/b.ts", "/proj/tests/c.ts"];
120
+ pathx.filterByTargets(files, ["src"], "/proj");
121
+ // ["/proj/src/a.ts", "/proj/src/b.ts"]
122
+ ```
package/docs/worker.md ADDED
@@ -0,0 +1,223 @@
1
+ # Worker
2
+
3
+ Type-safe worker thread abstraction built on Node.js `worker_threads`. Provides a Proxy-based API where worker methods are called as if they were local async functions.
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
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 that `createWorker()` returns. Used for type inference in `Worker.create<typeof import("./worker")>()`.
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
+ Mapped type that wraps all method return types with `Promise<Awaited<R>>`. Worker methods always return promises because they communicate via `postMessage`.
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 type returned by `Worker.create()`. Combines promisified methods with event subscription 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
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
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. Discriminated union on `type`.
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
114
+
115
+ Static 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 run through `tsx`. In production (`.js` files), the worker is created directly.
134
+
135
+ **Returns:** `WorkerProxy<TModule>` -- Proxy object supporting direct method calls, `on()`, `off()`, and `terminate()`.
136
+
137
+ ## createWorker
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
+ Factory function used inside the worker thread file. Registers method handlers and sets up the message protocol. Returns a sender object for emitting events back 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:** `SdError` if not running in a worker thread (no `parentPort`).
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.5",
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.5"
28
29
  },
29
30
  "devDependencies": {
30
- "@types/node": "^20.14.8"
31
+ "@types/node": "^20.19.37"
31
32
  }
32
33
  }