@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 +126 -0
- package/docs/fs-watcher.md +107 -0
- package/docs/fsx.md +287 -0
- package/docs/pathx.md +115 -0
- package/docs/worker.md +223 -0
- package/package.json +5 -4
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.
|
|
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.
|
|
28
|
+
"@simplysm/core-common": "14.0.4"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
|
-
"@types/node": "^20.
|
|
31
|
+
"@types/node": "^20.19.37"
|
|
31
32
|
}
|
|
32
33
|
}
|