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