@simplysm/core-node 13.0.78 → 13.0.81

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +3 -2
  2. package/README.md +0 -404
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplysm/core-node",
3
- "version": "13.0.78",
3
+ "version": "13.0.81",
4
4
  "description": "Simplysm package - Core module (node)",
5
5
  "author": "simplysm",
6
6
  "license": "Apache-2.0",
@@ -14,6 +14,7 @@
14
14
  "types": "./dist/index.d.ts",
15
15
  "files": [
16
16
  "dist",
17
+ "docs",
17
18
  "src",
18
19
  "tests"
19
20
  ],
@@ -24,6 +25,6 @@
24
25
  "glob": "^13.0.6",
25
26
  "minimatch": "^10.2.4",
26
27
  "tsx": "^4.21.0",
27
- "@simplysm/core-common": "13.0.78"
28
+ "@simplysm/core-common": "13.0.81"
28
29
  }
29
30
  }
package/README.md DELETED
@@ -1,404 +0,0 @@
1
- # @simplysm/core-node
2
-
3
- Simplysm package - Core module (node)
4
-
5
- Node.js utility library providing filesystem helpers, path utilities, a file-system watcher, and a type-safe worker thread abstraction.
6
-
7
- ## Installation
8
-
9
- ```bash
10
- pnpm add @simplysm/core-node
11
- ```
12
-
13
- ---
14
-
15
- ## Table of Contents
16
-
17
- - [Filesystem Utilities](#filesystem-utilities)
18
- - [Path Utilities](#path-utilities)
19
- - [File System Watcher](#file-system-watcher)
20
- - [Worker Thread Abstraction](#worker-thread-abstraction)
21
-
22
- ---
23
-
24
- ## Filesystem Utilities
25
-
26
- Import as a namespace:
27
-
28
- ```ts
29
- import { fsx } from "@simplysm/core-node";
30
- ```
31
-
32
- Wraps the built-in `fs` module with recursive directory creation, retry logic, glob support, and consistent error wrapping via `SdError`. Both synchronous (`Sync` suffix) and asynchronous variants are provided for most operations.
33
-
34
- ### Existence Check
35
-
36
- | Function | Signature | Description |
37
- |----------|-----------|-------------|
38
- | `fsx.existsSync` | `(targetPath: string) => boolean` | Check if a path exists (synchronous) |
39
- | `fsx.exists` | `(targetPath: string) => Promise<boolean>` | Check if a path exists (asynchronous) |
40
-
41
- ```ts
42
- if (await fsx.exists("/some/file.txt")) { /* ... */ }
43
- if (fsx.existsSync("/some/file.txt")) { /* ... */ }
44
- ```
45
-
46
- ### Create Directory
47
-
48
- | Function | Signature | Description |
49
- |----------|-----------|-------------|
50
- | `fsx.mkdirSync` | `(targetPath: string) => void` | Create directory recursively (synchronous) |
51
- | `fsx.mkdir` | `(targetPath: string) => Promise<void>` | Create directory recursively (asynchronous) |
52
-
53
- ```ts
54
- await fsx.mkdir("/some/deep/dir");
55
- fsx.mkdirSync("/some/deep/dir");
56
- ```
57
-
58
- ### Delete
59
-
60
- | Function | Signature | Description |
61
- |----------|-----------|-------------|
62
- | `fsx.rmSync` | `(targetPath: string) => void` | Delete file or directory recursively (synchronous, no retry) |
63
- | `fsx.rm` | `(targetPath: string) => Promise<void>` | Delete file or directory recursively (asynchronous, retries up to 6 times at 500ms intervals on transient errors) |
64
-
65
- ```ts
66
- await fsx.rm("/some/dir");
67
- fsx.rmSync("/some/file.txt");
68
- ```
69
-
70
- ### Copy
71
-
72
- | Function | Signature | Description |
73
- |----------|-----------|-------------|
74
- | `fsx.copySync` | `(sourcePath: string, targetPath: string, filter?: (absolutePath: string) => boolean) => void` | Copy file or directory (synchronous) |
75
- | `fsx.copy` | `(sourcePath: string, targetPath: string, filter?: (absolutePath: string) => boolean) => Promise<void>` | Copy file or directory (asynchronous) |
76
-
77
- If `sourcePath` does not exist, the function returns without error. The `filter` callback receives the absolute path of each child item; returning `false` excludes that item and its descendants. The top-level `sourcePath` is never filtered.
78
-
79
- ```ts
80
- await fsx.copy("/src", "/dest", (p) => !p.includes("node_modules"));
81
- fsx.copySync("/src", "/dest");
82
- ```
83
-
84
- ### Read File
85
-
86
- | Function | Signature | Description |
87
- |----------|-----------|-------------|
88
- | `fsx.readSync` | `(targetPath: string) => string` | Read file as UTF-8 string (synchronous) |
89
- | `fsx.read` | `(targetPath: string) => Promise<string>` | Read file as UTF-8 string (asynchronous) |
90
- | `fsx.readBufferSync` | `(targetPath: string) => Buffer` | Read file as `Buffer` (synchronous) |
91
- | `fsx.readBuffer` | `(targetPath: string) => Promise<Buffer>` | Read file as `Buffer` (asynchronous) |
92
- | `fsx.readJsonSync` | `<TData = unknown>(targetPath: string) => TData` | Read and parse a JSON file (synchronous) |
93
- | `fsx.readJson` | `<TData = unknown>(targetPath: string) => Promise<TData>` | Read and parse a JSON file (asynchronous) |
94
-
95
- ```ts
96
- const text = await fsx.read("/config.txt");
97
- const buf = fsx.readBufferSync("/image.png");
98
- const config = await fsx.readJson<Config>("/config.json");
99
- ```
100
-
101
- ### Write File
102
-
103
- | Function | Signature | Description |
104
- |----------|-----------|-------------|
105
- | `fsx.writeSync` | `(targetPath: string, data: string \| Uint8Array) => void` | Write to file, auto-creating parent directories (synchronous) |
106
- | `fsx.write` | `(targetPath: string, data: string \| Uint8Array) => Promise<void>` | Write to file, auto-creating parent directories (asynchronous) |
107
- | `fsx.writeJsonSync` | `(targetPath: string, data: unknown, options?: { replacer?: ..., space?: string \| number }) => void` | Serialize and write JSON to file (synchronous) |
108
- | `fsx.writeJson` | `(targetPath: string, data: unknown, options?: { replacer?: ..., space?: string \| number }) => Promise<void>` | Serialize and write JSON to file (asynchronous) |
109
-
110
- The `writeJsonSync`/`writeJson` `options` parameter:
111
- - `replacer?: (this: unknown, key: string | undefined, value: unknown) => unknown` — custom JSON replacer function
112
- - `space?: string | number` — indentation (passed to `JSON.stringify`)
113
-
114
- ```ts
115
- await fsx.write("/output.txt", "hello");
116
- fsx.writeSync("/output.bin", new Uint8Array([1, 2, 3]));
117
- await fsx.writeJson("/config.json", { key: "value" }, { space: 2 });
118
- fsx.writeJsonSync("/config.json", { key: "value" });
119
- ```
120
-
121
- ### Read Directory
122
-
123
- | Function | Signature | Description |
124
- |----------|-----------|-------------|
125
- | `fsx.readdirSync` | `(targetPath: string) => string[]` | List directory entries (synchronous) |
126
- | `fsx.readdir` | `(targetPath: string) => Promise<string[]>` | List directory entries (asynchronous) |
127
-
128
- ```ts
129
- const entries = await fsx.readdir("/some/dir");
130
- ```
131
-
132
- ### File Information
133
-
134
- | Function | Signature | Description |
135
- |----------|-----------|-------------|
136
- | `fsx.statSync` | `(targetPath: string) => fs.Stats` | Get `fs.Stats` following symlinks (synchronous) |
137
- | `fsx.stat` | `(targetPath: string) => Promise<fs.Stats>` | Get `fs.Stats` following symlinks (asynchronous) |
138
- | `fsx.lstatSync` | `(targetPath: string) => fs.Stats` | Get `fs.Stats` without following symlinks (synchronous) |
139
- | `fsx.lstat` | `(targetPath: string) => Promise<fs.Stats>` | Get `fs.Stats` without following symlinks (asynchronous) |
140
-
141
- ```ts
142
- const stats = await fsx.stat("/some/file.txt");
143
- console.log(stats.size);
144
- ```
145
-
146
- ### Glob
147
-
148
- | Function | Signature | Description |
149
- |----------|-----------|-------------|
150
- | `fsx.globSync` | `(pattern: string, options?: GlobOptions) => string[]` | Find files by glob pattern, returns absolute paths (synchronous) |
151
- | `fsx.glob` | `(pattern: string, options?: GlobOptions) => Promise<string[]>` | Find files by glob pattern, returns absolute paths (asynchronous) |
152
-
153
- ```ts
154
- const files = await fsx.glob("src/**/*.ts");
155
- const configs = fsx.globSync("**/tsconfig.json", { dot: true });
156
- ```
157
-
158
- ### Utilities
159
-
160
- | Function | Signature | Description |
161
- |----------|-----------|-------------|
162
- | `fsx.clearEmptyDirectory` | `(dirPath: string) => Promise<void>` | Recursively delete all empty directories under `dirPath`; if all children of a parent are removed, the parent is also deleted |
163
- | `fsx.findAllParentChildPathsSync` | `(childGlob: string, fromPath: string, rootPath?: string) => string[]` | Find matching paths by traversing parent directories toward the root (synchronous) |
164
- | `fsx.findAllParentChildPaths` | `(childGlob: string, fromPath: string, rootPath?: string) => Promise<string[]>` | Find matching paths by traversing parent directories toward the root (asynchronous) |
165
-
166
- `findAllParentChildPathsSync` / `findAllParentChildPaths` walk from `fromPath` up to `rootPath` (or the filesystem root if omitted), collecting all paths that match `childGlob` in each directory. `fromPath` must be a child of `rootPath`; otherwise the search goes to the filesystem root.
167
-
168
- ```ts
169
- await fsx.clearEmptyDirectory("/build");
170
-
171
- const configs = fsx.findAllParentChildPathsSync("tsconfig.json", "/project/src", "/project");
172
- // → ["/project/src/tsconfig.json", "/project/tsconfig.json"]
173
-
174
- const pkgFiles = await fsx.findAllParentChildPaths("package.json", process.cwd());
175
- ```
176
-
177
- ---
178
-
179
- ## Path Utilities
180
-
181
- Import as a namespace:
182
-
183
- ```ts
184
- import { pathx } from "@simplysm/core-node";
185
- ```
186
-
187
- Complements the built-in `path` module with normalization, POSIX conversion, and filtering helpers.
188
-
189
- ### Types
190
-
191
- | Type | Description |
192
- |------|-------------|
193
- | `pathx.NormPath` | Branded `string` type for normalized absolute paths. Can only be produced by `pathx.norm()`. |
194
-
195
- ### Functions
196
-
197
- | Function | Signature | Description |
198
- |----------|-----------|-------------|
199
- | `pathx.norm` | `(...paths: string[]) => NormPath` | Resolve segments to an absolute `NormPath` using `path.resolve` |
200
- | `pathx.posix` | `(...args: string[]) => string` | Join segments and convert backslashes to forward slashes |
201
- | `pathx.changeFileDirectory` | `(filePath: string, fromDirectory: string, toDirectory: string) => string` | Re-root a file path to a different base directory; throws if `filePath` is not inside `fromDirectory` |
202
- | `pathx.basenameWithoutExt` | `(filePath: string) => string` | Get filename without its last extension |
203
- | `pathx.isChildPath` | `(childPath: string, parentPath: string) => boolean` | Return `true` if `childPath` is strictly inside `parentPath` (same path returns `false`) |
204
- | `pathx.filterByTargets` | `(files: string[], targets: string[], cwd: string) => string[]` | Filter absolute paths to only those matching or under any entry in `targets` (relative to `cwd`); returns `files` unchanged when `targets` is empty |
205
-
206
- ```ts
207
- import { pathx } from "@simplysm/core-node";
208
-
209
- // norm
210
- const abs: pathx.NormPath = pathx.norm("relative", "path");
211
-
212
- // posix
213
- pathx.posix("C:\\Users\\test"); // "C:/Users/test"
214
- pathx.posix("src", "index.ts"); // "src/index.ts"
215
-
216
- // changeFileDirectory
217
- pathx.changeFileDirectory("/a/b/c.txt", "/a", "/x"); // "/x/b/c.txt"
218
-
219
- // basenameWithoutExt
220
- pathx.basenameWithoutExt("file.spec.ts"); // "file.spec"
221
-
222
- // isChildPath
223
- pathx.isChildPath("/a/b/c", "/a/b"); // true
224
- pathx.isChildPath("/a/b", "/a/b"); // false
225
-
226
- // filterByTargets
227
- const files = ["/proj/src/a.ts", "/proj/src/b.ts", "/proj/tests/c.ts"];
228
- pathx.filterByTargets(files, ["src"], "/proj");
229
- // → ["/proj/src/a.ts", "/proj/src/b.ts"]
230
- ```
231
-
232
- ---
233
-
234
- ## File System Watcher
235
-
236
- ```ts
237
- import { FsWatcher } from "@simplysm/core-node";
238
- import type { FsWatcherEvent, FsWatcherChangeInfo } from "@simplysm/core-node";
239
- ```
240
-
241
- A chokidar-based watcher that debounces rapid events and delivers them as a single batched array to a callback. Glob patterns are supported in the `paths` array and are matched using minimatch.
242
-
243
- **Note:** `ignoreInitial` is always set to `true` internally. If you pass `options.ignoreInitial: false`, the callback is invoked once with an empty array on the first `onChange` call, but the actual initial file list is not included.
244
-
245
- ### API
246
-
247
- | API | Description |
248
- |-----|-------------|
249
- | `FsWatcher.watch(paths, options?)` | Static method. Start watching and wait until the watcher is ready. Returns `Promise<FsWatcher>`. |
250
- | `watcher.onChange(opt, cb)` | Register a debounced batch-change handler. `opt.delay` sets the debounce window in ms. Returns `this` for chaining. |
251
- | `watcher.close()` | Stop watching and dispose all resources. Returns `Promise<void>`. |
252
-
253
- **`FsWatcher.watch` parameters:**
254
- - `paths: string[]` — file/directory paths or glob patterns to watch
255
- - `options?: ChokidarOptions` — passed to chokidar (except `ignoreInitial`, which is always overridden to `true`)
256
-
257
- **`onChange` parameters:**
258
- - `opt: { delay?: number }` — debounce delay in milliseconds
259
- - `cb: (changeInfos: FsWatcherChangeInfo[]) => void | Promise<void>` — called with the merged batch of changes
260
-
261
- **Event merging rules applied within a debounce window:**
262
- - `add` then `change` → `add`
263
- - `add` then `unlink` → removed (no change emitted)
264
- - `unlink` then `add` or `change` → `add`
265
- - `unlinkDir` then `addDir` → `addDir`
266
- - Otherwise the latest event wins.
267
-
268
- ### Types
269
-
270
- ```ts
271
- type FsWatcherEvent = "add" | "addDir" | "change" | "unlink" | "unlinkDir";
272
-
273
- interface FsWatcherChangeInfo {
274
- event: FsWatcherEvent;
275
- path: NormPath; // normalized absolute path
276
- }
277
- ```
278
-
279
- ### Example
280
-
281
- ```ts
282
- import { FsWatcher } from "@simplysm/core-node";
283
-
284
- const watcher = await FsWatcher.watch(["src/**/*.ts", "public"]);
285
-
286
- watcher.onChange({ delay: 300 }, async (changes) => {
287
- for (const { event, path } of changes) {
288
- console.log(`${event}: ${path}`);
289
- }
290
- });
291
-
292
- // Later:
293
- await watcher.close();
294
- ```
295
-
296
- ---
297
-
298
- ## Worker Thread Abstraction
299
-
300
- ```ts
301
- import { createWorker, Worker } from "@simplysm/core-node";
302
- import type {
303
- WorkerModule,
304
- WorkerProxy,
305
- PromisifyMethods,
306
- WorkerRequest,
307
- WorkerResponse,
308
- } from "@simplysm/core-node";
309
- ```
310
-
311
- Type-safe RPC bridge over Node.js `worker_threads`. Define methods in a worker file and call them from the main thread with full TypeScript inference. In development (`.ts` files), workers are executed via `tsx`; in production (`.js` files), workers are executed directly.
312
-
313
- ### `createWorker`
314
-
315
- Used inside a worker file to register RPC methods and optionally declare typed events.
316
-
317
- ```ts
318
- function createWorker<
319
- TMethods extends Record<string, (...args: any[]) => unknown>,
320
- TEvents extends Record<string, unknown> = Record<string, never>,
321
- >(
322
- methods: TMethods,
323
- ): {
324
- send<TEventName extends keyof TEvents & string>(event: TEventName, data?: TEvents[TEventName]): void;
325
- __methods: TMethods;
326
- __events: TEvents;
327
- }
328
- ```
329
-
330
- - `methods` — object mapping method names to their implementations
331
- - Returns an object with a `send(event, data?)` method for emitting typed events to the main thread
332
- - The `__methods` and `__events` fields carry type information used by `Worker.create`
333
-
334
- ```ts
335
- // worker.ts
336
- import { createWorker } from "@simplysm/core-node";
337
-
338
- interface MyEvents { progress: number; }
339
-
340
- const methods = {
341
- add: (a: number, b: number) => a + b,
342
- calc: (x: number) => {
343
- sender.send("progress", 50);
344
- return x * 2;
345
- },
346
- };
347
-
348
- const sender = createWorker<typeof methods, MyEvents>(methods);
349
- export default sender;
350
- ```
351
-
352
- ### `Worker.create`
353
-
354
- Used in the main thread to create a typed proxy to a worker file.
355
-
356
- ```ts
357
- Worker.create<TModule extends WorkerModule>(
358
- filePath: string,
359
- opt?: Omit<WorkerOptions, "stdout" | "stderr">,
360
- ): WorkerProxy<TModule>
361
- ```
362
-
363
- - `filePath` — absolute path or `file://` URL to the worker file (`.ts` in development, `.js` in production)
364
- - `opt` — standard `worker_threads` `WorkerOptions` (excluding `stdout`/`stderr`, which are always piped)
365
- - Returns a `WorkerProxy` with all worker methods promisified, plus `on()`, `off()`, and `terminate()`
366
-
367
- ```ts
368
- // main.ts
369
- import { Worker } from "@simplysm/core-node";
370
-
371
- const worker = Worker.create<typeof import("./worker")>("./worker.ts");
372
-
373
- worker.on("progress", (value) => console.log("progress:", value));
374
-
375
- const result = await worker.add(10, 20); // 30
376
- await worker.terminate();
377
- ```
378
-
379
- ### Types
380
-
381
- | Type | Description |
382
- |------|-------------|
383
- | `WorkerModule` | Shape constraint for the module returned by `createWorker()`. Used as the type parameter of `Worker.create`. |
384
- | `WorkerProxy<TModule>` | Proxy type returned by `Worker.create()`. Provides promisified methods plus `on(event, listener)`, `off(event, listener)`, and `terminate()`. |
385
- | `PromisifyMethods<TMethods>` | Maps each method's return type to `Promise<Awaited<R>>`. |
386
- | `WorkerRequest` | Internal RPC request message shape: `{ id: string, method: string, params: unknown[] }`. |
387
- | `WorkerResponse` | Internal RPC response message — a discriminated union of `"return"`, `"error"`, `"event"`, and `"log"` variants. |
388
-
389
- ### `WorkerProxy` interface detail
390
-
391
- ```ts
392
- type WorkerProxy<TModule extends WorkerModule> =
393
- PromisifyMethods<TModule["default"]["__methods"]> & {
394
- on<TEventName extends keyof TModule["default"]["__events"] & string>(
395
- event: TEventName,
396
- listener: (data: TModule["default"]["__events"][TEventName]) => void,
397
- ): void;
398
- off<TEventName extends keyof TModule["default"]["__events"] & string>(
399
- event: TEventName,
400
- listener: (data: TModule["default"]["__events"][TEventName]) => void,
401
- ): void;
402
- terminate(): Promise<void>;
403
- };
404
- ```