@mearl/daemon-core 3.0.0-beta.9 → 3.0.0
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.en.md +102 -0
- package/README.md +2 -0
- package/package.json +3 -2
package/README.en.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# @mearl/daemon-core
|
|
2
|
+
|
|
3
|
+
[简体中文](README.md) | [English](README.en.md)
|
|
4
|
+
|
|
5
|
+
Daemon lifecycle primitives shared by Mearl's Node packages: process liveness, JSON registration records, log rotation, and a detached daemon manager.
|
|
6
|
+
|
|
7
|
+
`cloud-server`, `cloud-connector`, and `native-host` share daemon operations (`isAlive`, atomic registry reads and writes, log rotation, and keyed paths) here so all readers and writers agree on **record locations and formats**.
|
|
8
|
+
|
|
9
|
+
This Node-only package can be used as a normal dependency or inlined into an execution host bundle.
|
|
10
|
+
|
|
11
|
+
## API
|
|
12
|
+
|
|
13
|
+
### Process liveness
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
isAlive(pid: number | undefined | null): boolean
|
|
17
|
+
isProcessGroupAlive(group: number): boolean
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Uses signal-0 to probe liveness. `EPERM` counts as alive: the process exists but belongs to another user. On Linux, `/proc/<pid>/stat` identifies exited, unreaped zombie and dead processes as stopped. If other threads remain after the main thread exits, or status cannot be read, it conservatively treats the process as alive.
|
|
21
|
+
|
|
22
|
+
POSIX process-group checks include descendants surviving the root process. Linux confirms execution has stopped only when two checks find the same exited members and creation identities. Active descendants, membership changes, permission errors, or read failures leave cleanup unconfirmed. Windows does not use POSIX process-group checks.
|
|
23
|
+
|
|
24
|
+
### Path derivation (multiple instances)
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
DAEMON_HOME // ~/.mearl
|
|
28
|
+
hashKey(key: string): string // First 16 hexadecimal digits of sha256(key)
|
|
29
|
+
keyedDir(subdir: string): string // ~/.mearl/<subdir>
|
|
30
|
+
keyedConfigFile(subdir, key): string // ~/.mearl/<subdir>/<hash>.json
|
|
31
|
+
keyedLogFile(subdir, key): string // ~/.mearl/<subdir>/<hash>.log
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Multi-instance subsystems, such as connectors keyed by `serverUrl`, derive per-instance paths with these helpers. **All readers and writers must use them** to agree on record locations.
|
|
35
|
+
|
|
36
|
+
### Record registry
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
interface DaemonRecord { pid: number; startedAt?: string; [k: string]: unknown }
|
|
40
|
+
|
|
41
|
+
ensureDir(dir: string): void
|
|
42
|
+
writeRecord(file: string, record: DaemonRecord): void // Cross-process lock; atomic temporary file + rename
|
|
43
|
+
claimRecord(file: string, record: DaemonRecord): boolean // Atomically claim only if absent
|
|
44
|
+
readRecord<T>(file: string): T | null
|
|
45
|
+
removeRecord(file: string): void
|
|
46
|
+
removeRecordIfPid(file: string, pid: number): boolean // Remove only if still owned by this PID
|
|
47
|
+
writeRecordIfPid(file: string, pid: number, record: DaemonRecord): boolean
|
|
48
|
+
removeInvalidRecord(file: string): boolean // Clean up unparseable records
|
|
49
|
+
listRecordFiles(dir: string): string[] // Absolute paths of all *.json files
|
|
50
|
+
readLiveRecords<T>(dir: string): T[] // Read live records and clean up dead ones
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Daemon manager
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
createDaemon(opts: CreateDaemonOptions): Daemon
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Builds a single-instance daemon manager around `(configFile, logFile)`. `start()` atomically claims a startup lock,
|
|
60
|
+
launches the consumer CLI detached with `--foreground`, and immediately writes a `starting` record
|
|
61
|
+
with the child PID. The ready child overwrites the record, allowing the parent to confirm startup. Concurrent `start()` calls reuse a starting
|
|
62
|
+
or running child. Multi-instance subsystems derive their own `configFile` / `logFile` from the instance key.
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
interface CreateDaemonOptions {
|
|
66
|
+
name: string; // Log label, e.g. 'CloudServer' / 'CloudConnector'
|
|
67
|
+
scriptPath: string; // Absolute path to the consumer's bundled cli.js
|
|
68
|
+
configFile: string; // Instance JSON record written by the foreground child
|
|
69
|
+
logFile: string; // Instance log file
|
|
70
|
+
foregroundArgs?: string[]; // argv appended after `--foreground`
|
|
71
|
+
readyTimeoutMs?: number; // Child record timeout, default 15000ms
|
|
72
|
+
formatInfo?: (record: DaemonRecord) => string | null; // Additional startup information
|
|
73
|
+
startingRecord?: (pid: number, startedAt: string) => DaemonRecord;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface Daemon {
|
|
77
|
+
start(): Promise<void>;
|
|
78
|
+
stop(expectedPid?: number): Promise<void>; // Stop only the matching instance when PID is supplied
|
|
79
|
+
restart(): Promise<void>;
|
|
80
|
+
status(): void;
|
|
81
|
+
logs(lines?: number): void;
|
|
82
|
+
running(): DaemonRecord | null;
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
> `scriptPath` must identify the consumer's own `cli.js`. Derive it with `fileURLToPath(import.meta.url)` in a module bundled into the CLI entry. If daemon-core is resolved separately, its `import.meta.url` points to daemon-core, not the CLI.
|
|
87
|
+
|
|
88
|
+
### Logs
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
rotateLogIfNeeded(logFile: string, maxBytes?: number): void // Rotate to <log>.1 above limit; default 5MB
|
|
92
|
+
tailFile(logFile: string, lines: number): string
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Consumers
|
|
96
|
+
|
|
97
|
+
- **cloud-server** — `createDaemon`, single instance at `~/.mearl/cloud-server.json`.
|
|
98
|
+
- **cloud-connector** — `createDaemon`, instances keyed by `serverUrl` at `~/.mearl/connectors/<hash>.json`, listed through `readLiveRecords`.
|
|
99
|
+
- **native-host** — Uses primitives such as `isAlive` / `keyedConfigFile` / `readRecord` / `readLiveRecords` to discover, take over, and list connectors through their shared registry.
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
`NodeCapabilityRegistry` from `@mearl/daemon-core/capabilities` accepts `hooks` in its third constructor argument to load only modules referenced by those bindings. The whole selection is still validated, target module bytes are digest-verified, and commits still check the current selection. Ignored modules do not execute. This partial instance suits hosts needing a few native services, such as the installer CLI; it does not replace full Native Host activation and readiness receipts. Omit `hooks` for full loading.
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @mearl/daemon-core
|
|
2
2
|
|
|
3
|
+
[简体中文](README.md) | [English](README.en.md)
|
|
4
|
+
|
|
3
5
|
Mearl 各 Node 包共享的守护进程生命周期原语:进程存活探测、JSON 记录注册表、日志滚动,以及一个 detached 守护进程管理器。
|
|
4
6
|
|
|
5
7
|
把 `cloud-server`、`cloud-connector`、`native-host` 里原本各写一份、容易彼此漂移的 daemon 逻辑(`isAlive`、原子读写注册表、日志滚动、按 key 派生路径)收敛到这一处,确保「写方」和「读方」对**记录位置与格式**始终一致。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mearl/daemon-core",
|
|
3
|
-
"version": "3.0.0
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Shared Node daemon lifecycle primitives for Mearl — process liveness, JSON record registry, log rotation, and a detached daemon manager",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
|
+
"README.en.md",
|
|
27
28
|
"dist",
|
|
28
29
|
"!dist/**/*.map"
|
|
29
30
|
],
|
|
@@ -43,7 +44,7 @@
|
|
|
43
44
|
"typescript": "^5.8.3"
|
|
44
45
|
},
|
|
45
46
|
"dependencies": {
|
|
46
|
-
"@mearl/preset": "3.0.0
|
|
47
|
+
"@mearl/preset": "3.0.0"
|
|
47
48
|
},
|
|
48
49
|
"repository": {
|
|
49
50
|
"type": "git",
|