@onivoro/server-process 24.0.0 → 24.0.1

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/README.md +262 -399
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @onivoro/server-process
2
2
 
3
- A comprehensive process management library for Node.js applications, providing utilities for executing commands, managing Docker containers, PostgreSQL operations, and handling system processes with reactive programming support.
3
+ A lightweight Node.js process management library providing utilities for command execution, Docker container operations, PostgreSQL commands, and reactive process handling.
4
4
 
5
5
  ## Installation
6
6
 
@@ -10,517 +10,380 @@ npm install @onivoro/server-process
10
10
 
11
11
  ## Features
12
12
 
13
- - **Command Execution**: Promise-based and reactive command execution
14
- - **Docker Integration**: Docker container management utilities
15
- - **PostgreSQL Tools**: PSql command execution and database operations
16
- - **Process Management**: Spawn and manage child processes
17
- - **Reactive Streams**: RxJS-based reactive process handling
18
- - **JSON Processing**: Parse command output as JSON
13
+ - **Promise-based Command Execution**: Simple async/await command execution
14
+ - **Reactive Process Streams**: RxJS-based reactive command execution
15
+ - **Docker Container Support**: Execute commands within Docker containers
16
+ - **PostgreSQL Integration**: Execute psql commands with container support
17
+ - **Process I/O Management**: Handle stdin/stdout streams reactively
18
+ - **JSON Output Processing**: Parse command output as JSON
19
19
  - **Line-by-Line Processing**: Stream processing for large outputs
20
- - **Error Handling**: Comprehensive error handling for process operations
21
- - **Cross-Platform**: Works on Windows, macOS, and Linux
22
20
 
23
- ## Quick Start
21
+ ## API Reference
24
22
 
25
- ### Basic Command Execution
23
+ ### Command Execution
24
+
25
+ #### `execPromise(cmd, options?)`
26
+
27
+ Execute a command and return a promise that resolves to stdout as a string:
26
28
 
27
29
  ```typescript
28
- import { execPromise, shell } from '@onivoro/server-process';
30
+ import { execPromise } from '@onivoro/server-process';
29
31
 
30
32
  // Simple command execution
31
- const result = await execPromise('ls -la');
32
- console.log(result.stdout);
33
+ const output = await execPromise('ls -la');
34
+ console.log(output); // stdout as string
33
35
 
34
- // Alternative using shell function (if available in common)
35
- const output = await shell('pwd');
36
- console.log(output);
36
+ // With options
37
+ const result = await execPromise('git status', { cwd: '/path/to/repo' });
37
38
  ```
38
39
 
39
- ### Docker Operations
40
-
41
- ```typescript
42
- import { Docker } from '@onivoro/server-process';
43
-
44
- const docker = new Docker();
40
+ **Parameters:**
41
+ - `cmd: string` - Command to execute
42
+ - `options?: EncodingOption & ExecOptions` - Node.js exec options
45
43
 
46
- // List running containers
47
- const containers = await docker.listContainers();
44
+ **Returns:** `Promise<string>` - Command stdout
48
45
 
49
- // Run a container
50
- await docker.run('nginx:latest', {
51
- ports: ['80:80'],
52
- detach: true,
53
- name: 'my-nginx'
54
- });
55
-
56
- // Execute command in container
57
- const result = await docker.exec('my-nginx', 'ls /usr/share/nginx/html');
58
- ```
46
+ #### `spawnPromise(program, args?, options?)`
59
47
 
60
- ### PostgreSQL Operations
48
+ Spawn a process and return a promise that resolves to joined stdout:
61
49
 
62
50
  ```typescript
63
- import { PSql } from '@onivoro/server-process';
64
-
65
- const psql = new PSql({
66
- host: 'localhost',
67
- port: 5432,
68
- database: 'mydb',
69
- username: 'user',
70
- password: 'password'
71
- });
51
+ import { spawnPromise } from '@onivoro/server-process';
72
52
 
73
- // Execute SQL query
74
- const users = await psql.query('SELECT * FROM users');
53
+ // Spawn with arguments
54
+ const output = await spawnPromise('node', ['--version']);
55
+ console.log(output); // Node.js version
75
56
 
76
- // Execute SQL file
77
- await psql.executeFile('./migrations/001_create_tables.sql');
57
+ // With spawn options
58
+ const result = await spawnPromise('npm', ['install'], {
59
+ cwd: '/path/to/project',
60
+ env: { ...process.env, NODE_ENV: 'production' }
61
+ });
78
62
  ```
79
63
 
80
- ## Usage Examples
64
+ **Parameters:**
65
+ - `program: string` - Program to spawn
66
+ - `args?: string[]` - Command arguments
67
+ - `options?: any` - Node.js spawn options
68
+
69
+ **Returns:** `Promise<string>` - Joined stdout (rejects with stderr on error)
81
70
 
82
71
  ### Reactive Command Execution
83
72
 
84
- ```typescript
85
- import { execRx, execRxAsLines, execRxAsJson } from '@onivoro/server-process';
86
- import { tap, catchError } from 'rxjs/operators';
87
- import { of } from 'rxjs';
88
-
89
- // Execute command reactively
90
- execRx('ping google.com')
91
- .pipe(
92
- tap(data => console.log('Output:', data)),
93
- catchError(error => {
94
- console.error('Command failed:', error);
95
- return of(null);
96
- })
97
- )
98
- .subscribe();
99
-
100
- // Process output line by line
101
- execRxAsLines('tail -f /var/log/system.log')
102
- .pipe(
103
- tap(line => console.log('Log line:', line))
104
- )
105
- .subscribe();
106
-
107
- // Parse JSON output
108
- execRxAsJson('docker inspect my-container')
109
- .pipe(
110
- tap(json => console.log('Container info:', json))
111
- )
112
- .subscribe();
113
- ```
73
+ #### `execRx(cmd, options?, emitStdErr?)`
114
74
 
115
- ### Process Spawning
75
+ Execute a command reactively, emitting stdout (and optionally stderr):
116
76
 
117
77
  ```typescript
118
- import { spawnPromise } from '@onivoro/server-process';
78
+ import { execRx } from '@onivoro/server-process';
119
79
 
120
- // Spawn a long-running process
121
- const result = await spawnPromise('node', ['server.js'], {
122
- cwd: '/path/to/app',
123
- env: { ...process.env, NODE_ENV: 'production' }
80
+ execRx('ping -c 3 google.com').subscribe({
81
+ next: (output) => console.log('Output:', output),
82
+ error: (err) => console.error('Error:', err),
83
+ complete: () => console.log('Command completed')
124
84
  });
125
85
 
126
- console.log('Process exit code:', result.code);
127
- console.log('Process output:', result.stdout);
86
+ // Without stderr
87
+ execRx('ls -la', undefined, false).subscribe(console.log);
128
88
  ```
129
89
 
130
- ### Docker Container Management
90
+ **Parameters:**
91
+ - `cmd: string` - Command to execute
92
+ - `options?: EncodingOption & ExecOptions` - Node.js exec options
93
+ - `emitStdErr?: boolean` - Include stderr in output (default: true)
131
94
 
132
- ```typescript
133
- import { Docker } from '@onivoro/server-process';
95
+ **Returns:** `Observable<string>` - Command output stream
134
96
 
135
- class ContainerManager {
136
- private docker = new Docker();
97
+ #### `execRxAsLines(cmd, options?, emitStdErr?)`
137
98
 
138
- async deployApplication(imageName: string, config: any): Promise<string> {
139
- // Pull latest image
140
- await this.docker.pull(imageName);
99
+ Execute a command and emit output line by line:
141
100
 
142
- // Stop existing container if running
143
- try {
144
- await this.docker.stop(config.containerName);
145
- await this.docker.remove(config.containerName);
146
- } catch (error) {
147
- // Container might not exist, continue
148
- }
149
-
150
- // Run new container
151
- const containerId = await this.docker.run(imageName, {
152
- name: config.containerName,
153
- ports: config.ports,
154
- volumes: config.volumes,
155
- env: config.environment,
156
- detach: true,
157
- restart: 'unless-stopped'
158
- });
159
-
160
- return containerId;
161
- }
162
-
163
- async getContainerLogs(containerName: string): Promise<string> {
164
- return this.docker.logs(containerName, { tail: 100 });
165
- }
101
+ ```typescript
102
+ import { execRxAsLines } from '@onivoro/server-process';
166
103
 
167
- async healthCheck(containerName: string): Promise<boolean> {
168
- try {
169
- const result = await this.docker.exec(containerName, 'curl -f http://localhost/health');
170
- return result.exitCode === 0;
171
- } catch (error) {
172
- return false;
173
- }
174
- }
175
- }
104
+ execRxAsLines('cat large-file.txt').subscribe({
105
+ next: (line) => console.log('Line:', line),
106
+ complete: () => console.log('File processed')
107
+ });
176
108
  ```
177
109
 
178
- ### Database Migration with PSql
110
+ **Parameters:**
111
+ - `cmd: string` - Command to execute
112
+ - `options?: ExecOptions` - Node.js exec options
113
+ - `emitStdErr?: boolean` - Include stderr in output (default: true)
179
114
 
180
- ```typescript
181
- import { PSql } from '@onivoro/server-process';
182
- import { readdir } from 'fs/promises';
183
- import { join } from 'path';
115
+ **Returns:** `Observable<string>` - Stream of individual lines
184
116
 
185
- class MigrationRunner {
186
- private psql: PSql;
117
+ #### `execRxAsJson(cmd, options?, emitStdErr?)`
187
118
 
188
- constructor(connectionConfig: any) {
189
- this.psql = new PSql(connectionConfig);
190
- }
119
+ Execute a command and parse output as JSON:
191
120
 
192
- async runMigrations(migrationsDir: string): Promise<void> {
193
- // Ensure migrations table exists
194
- await this.psql.query(`
195
- CREATE TABLE IF NOT EXISTS migrations (
196
- id SERIAL PRIMARY KEY,
197
- filename VARCHAR(255) NOT NULL,
198
- executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
199
- )
200
- `);
201
-
202
- // Get executed migrations
203
- const executedMigrations = await this.psql.query(
204
- 'SELECT filename FROM migrations ORDER BY id'
205
- );
206
- const executedFiles = executedMigrations.map(row => row.filename);
207
-
208
- // Get migration files
209
- const files = await readdir(migrationsDir);
210
- const migrationFiles = files
211
- .filter(file => file.endsWith('.sql'))
212
- .sort();
213
-
214
- // Execute pending migrations
215
- for (const file of migrationFiles) {
216
- if (!executedFiles.includes(file)) {
217
- console.log(`Executing migration: ${file}`);
218
-
219
- try {
220
- await this.psql.executeFile(join(migrationsDir, file));
221
- await this.psql.query(
222
- 'INSERT INTO migrations (filename) VALUES ($1)',
223
- [file]
224
- );
225
- console.log(`Migration ${file} completed successfully`);
226
- } catch (error) {
227
- console.error(`Migration ${file} failed:`, error);
228
- throw error;
229
- }
230
- }
231
- }
232
- }
121
+ ```typescript
122
+ import { execRxAsJson } from '@onivoro/server-process';
233
123
 
234
- async rollback(steps: number = 1): Promise<void> {
235
- const migrations = await this.psql.query(
236
- 'SELECT filename FROM migrations ORDER BY id DESC LIMIT $1',
237
- [steps]
238
- );
239
-
240
- for (const migration of migrations) {
241
- // Execute rollback script if exists
242
- const rollbackFile = migration.filename.replace('.sql', '.rollback.sql');
243
- try {
244
- await this.psql.executeFile(rollbackFile);
245
- await this.psql.query(
246
- 'DELETE FROM migrations WHERE filename = $1',
247
- [migration.filename]
248
- );
249
- console.log(`Rolled back migration: ${migration.filename}`);
250
- } catch (error) {
251
- console.error(`Rollback failed for ${migration.filename}:`, error);
252
- throw error;
253
- }
254
- }
255
- }
256
- }
124
+ execRxAsJson('docker inspect my-container').subscribe({
125
+ next: (json) => console.log('Container info:', json),
126
+ error: (err) => console.error('Failed to parse JSON:', err)
127
+ });
257
128
  ```
258
129
 
259
- ### System Monitoring
130
+ **Parameters:**
131
+ - `cmd: string` - Command to execute
132
+ - `options?: ExecOptions` - Node.js exec options
133
+ - `emitStdErr?: boolean` - Include stderr in output (default: true)
260
134
 
261
- ```typescript
262
- import { execPromise, execRxAsLines } from '@onivoro/server-process';
135
+ **Returns:** `Observable<any>` - Stream of parsed JSON objects
263
136
 
264
- class SystemMonitor {
265
- async getSystemInfo(): Promise<any> {
266
- const [cpu, memory, disk] = await Promise.all([
267
- this.getCpuInfo(),
268
- this.getMemoryInfo(),
269
- this.getDiskInfo()
270
- ]);
137
+ ### Docker Class
271
138
 
272
- return { cpu, memory, disk };
273
- }
139
+ Execute commands within Docker containers:
274
140
 
275
- private async getCpuInfo(): Promise<any> {
276
- const result = await execPromise('cat /proc/cpuinfo');
277
- // Parse CPU information
278
- return this.parseCpuInfo(result.stdout);
279
- }
141
+ ```typescript
142
+ import { Docker } from '@onivoro/server-process';
280
143
 
281
- private async getMemoryInfo(): Promise<any> {
282
- const result = await execPromise('free -m');
283
- return this.parseMemoryInfo(result.stdout);
284
- }
144
+ const docker = new Docker('my-postgres-container', 'psql');
285
145
 
286
- private async getDiskInfo(): Promise<any> {
287
- const result = await execPromise('df -h');
288
- return this.parseDiskInfo(result.stdout);
289
- }
146
+ // Execute psql command in container
147
+ docker.execRx('-c "SELECT version();"').subscribe({
148
+ next: (result) => console.log('DB Version:', result),
149
+ error: (err) => console.error('Query failed:', err)
150
+ });
151
+ ```
290
152
 
291
- monitorSystemLogs(): void {
292
- execRxAsLines('tail -f /var/log/syslog')
293
- .subscribe(line => {
294
- if (this.isErrorLog(line)) {
295
- this.handleSystemError(line);
296
- }
297
- });
298
- }
153
+ **Constructor:**
154
+ - `containerName: string` - Name of the Docker container
155
+ - `binaryName: string` - Binary to execute within the container
299
156
 
300
- private isErrorLog(line: string): boolean {
301
- return line.toLowerCase().includes('error') ||
302
- line.toLowerCase().includes('fail');
303
- }
157
+ **Methods:**
158
+ - `execRx(cmd, options?, emitStdErr?)` - Execute command reactively within container
304
159
 
305
- private handleSystemError(errorLine: string): void {
306
- console.error('System error detected:', errorLine);
307
- // Implement alerting logic
308
- }
309
- }
310
- ```
160
+ ### PSql Class
311
161
 
312
- ### Process Exit Handling
162
+ Execute PostgreSQL commands with optional Docker container support:
313
163
 
314
164
  ```typescript
315
- import { exit, listen } from '@onivoro/server-process';
165
+ import { PSql } from '@onivoro/server-process';
316
166
 
317
- class GracefulShutdown {
318
- private cleanup: Array<() => Promise<void>> = [];
167
+ // Local psql
168
+ const psql = new PSql();
169
+ psql.execRx('SELECT COUNT(*) FROM users;', 'mydb', 'postgres').subscribe(console.log);
319
170
 
320
- addCleanupTask(task: () => Promise<void>): void {
321
- this.cleanup.push(task);
322
- }
171
+ // Container-based psql
172
+ const containerPsql = new PSql('postgres-container');
173
+ containerPsql.execRx('SELECT NOW();', 'mydb', 'postgres').subscribe(console.log);
174
+ ```
323
175
 
324
- setupGracefulShutdown(): void {
325
- // Listen for termination signals
326
- listen('SIGTERM', async () => {
327
- console.log('Received SIGTERM, shutting down gracefully...');
328
- await this.performCleanup();
329
- exit(0);
330
- });
176
+ **Constructor:**
177
+ - `containerName?: string` - Optional Docker container name
331
178
 
332
- listen('SIGINT', async () => {
333
- console.log('Received SIGINT, shutting down gracefully...');
334
- await this.performCleanup();
335
- exit(0);
336
- });
179
+ **Methods:**
180
+ - `execRx(cmd, db, username)` - Execute psql command
181
+ - `cmd: string` - SQL command to execute
182
+ - `db: string` - Database name
183
+ - `username: string` - PostgreSQL username
337
184
 
338
- // Handle uncaught exceptions
339
- process.on('uncaughtException', async (error) => {
340
- console.error('Uncaught exception:', error);
341
- await this.performCleanup();
342
- exit(1);
343
- });
344
- }
185
+ ### Process I/O Management
345
186
 
346
- private async performCleanup(): Promise<void> {
347
- console.log('Performing cleanup tasks...');
348
-
349
- await Promise.all(
350
- this.cleanup.map(async (task, index) => {
351
- try {
352
- await task();
353
- console.log(`Cleanup task ${index + 1} completed`);
354
- } catch (error) {
355
- console.error(`Cleanup task ${index + 1} failed:`, error);
356
- }
357
- })
358
- );
359
- }
360
- }
187
+ #### `listen()`
361
188
 
362
- // Usage
363
- const shutdown = new GracefulShutdown();
189
+ Create reactive streams for process stdin/stdout:
364
190
 
365
- // Add cleanup tasks
366
- shutdown.addCleanupTask(async () => {
367
- // Close database connections
368
- await database.close();
369
- });
191
+ ```typescript
192
+ import { listen } from '@onivoro/server-process';
370
193
 
371
- shutdown.addCleanupTask(async () => {
372
- // Stop background jobs
373
- await jobQueue.stop();
194
+ const { stdout, stdin } = listen();
195
+
196
+ // Handle stdin data
197
+ stdin.subscribe((data) => {
198
+ console.log('Received input:', data.toString());
374
199
  });
375
200
 
376
- shutdown.setupGracefulShutdown();
201
+ // stdin automatically completes when process.stdin closes
377
202
  ```
378
203
 
379
- ## API Reference
204
+ **Returns:** `{ stdout: Subject, stdin: Subject }` - Reactive streams for process I/O
380
205
 
381
- ### Command Execution
382
-
383
- #### execPromise(command, options?)
206
+ #### `exit(code)`
384
207
 
385
- Execute a command and return a promise:
208
+ Create a process exit function with specified exit code:
386
209
 
387
210
  ```typescript
388
- interface ExecResult {
389
- stdout: string;
390
- stderr: string;
391
- code: number;
392
- }
211
+ import { exit } from '@onivoro/server-process';
393
212
 
394
- function execPromise(command: string, options?: ExecOptions): Promise<ExecResult>
395
- ```
213
+ const exitWithError = exit(1);
214
+ const exitSuccess = exit(0);
396
215
 
397
- #### spawnPromise(command, args?, options?)
398
-
399
- Spawn a process and return a promise:
400
-
401
- ```typescript
402
- function spawnPromise(
403
- command: string,
404
- args?: string[],
405
- options?: SpawnOptions
406
- ): Promise<SpawnResult>
216
+ // Use in error handling
217
+ if (someErrorCondition) {
218
+ console.error('Fatal error occurred');
219
+ exitWithError();
220
+ }
407
221
  ```
408
222
 
409
- ### Reactive Execution
410
-
411
- #### execRx(command, options?)
223
+ **Parameters:**
224
+ - `code: number` - Exit code
412
225
 
413
- Execute command reactively:
226
+ **Returns:** `() => never` - Function that exits the process
414
227
 
415
- ```typescript
416
- function execRx(command: string, options?: ExecOptions): Observable<string>
417
- ```
418
-
419
- #### execRxAsLines(command, options?)
228
+ ## Usage Examples
420
229
 
421
- Execute command and emit output line by line:
230
+ ### Basic Command Execution
422
231
 
423
232
  ```typescript
424
- function execRxAsLines(command: string, options?: ExecOptions): Observable<string>
425
- ```
426
-
427
- #### execRxAsJson(command, options?)
428
-
429
- Execute command and parse output as JSON:
233
+ import { execPromise, spawnPromise } from '@onivoro/server-process';
430
234
 
431
- ```typescript
432
- function execRxAsJson<T>(command: string, options?: ExecOptions): Observable<T>
235
+ async function deployApp() {
236
+ try {
237
+ // Check if Docker is running
238
+ await execPromise('docker --version');
239
+
240
+ // Build and deploy
241
+ const buildOutput = await spawnPromise('docker', ['build', '-t', 'myapp', '.']);
242
+ console.log('Build completed:', buildOutput);
243
+
244
+ const runOutput = await spawnPromise('docker', ['run', '-d', '-p', '3000:3000', 'myapp']);
245
+ console.log('Container started:', runOutput);
246
+ } catch (error) {
247
+ console.error('Deployment failed:', error);
248
+ }
249
+ }
433
250
  ```
434
251
 
435
- ### Docker Class
436
-
437
- Docker container management:
252
+ ### Reactive Log Monitoring
438
253
 
439
254
  ```typescript
440
- class Docker {
441
- async run(image: string, options?: DockerRunOptions): Promise<string>
442
- async stop(container: string): Promise<void>
443
- async remove(container: string): Promise<void>
444
- async exec(container: string, command: string): Promise<ExecResult>
445
- async logs(container: string, options?: LogOptions): Promise<string>
446
- async listContainers(): Promise<ContainerInfo[]>
447
- async pull(image: string): Promise<void>
255
+ import { execRxAsLines } from '@onivoro/server-process';
256
+
257
+ function monitorLogs(logFile: string) {
258
+ execRxAsLines(`tail -f ${logFile}`)
259
+ .subscribe({
260
+ next: (line) => {
261
+ if (line.includes('ERROR')) {
262
+ console.error('🚨 Error detected:', line);
263
+ // Trigger alert
264
+ } else if (line.includes('WARN')) {
265
+ console.warn('⚠️ Warning:', line);
266
+ }
267
+ },
268
+ error: (err) => console.error('Log monitoring failed:', err)
269
+ });
448
270
  }
449
- ```
450
271
 
451
- ### PSql Class
272
+ monitorLogs('/var/log/app.log');
273
+ ```
452
274
 
453
- PostgreSQL operations:
275
+ ### Database Operations with Docker
454
276
 
455
277
  ```typescript
456
- class PSql {
457
- constructor(config: PSqlConfig)
458
- async query(sql: string, params?: any[]): Promise<any[]>
459
- async executeFile(filePath: string): Promise<void>
460
- async transaction<T>(callback: (client: any) => Promise<T>): Promise<T>
278
+ import { PSql } from '@onivoro/server-process';
279
+
280
+ class DatabaseManager {
281
+ private psql = new PSql('postgres-container');
282
+
283
+ async checkHealth() {
284
+ return new Promise((resolve, reject) => {
285
+ this.psql.execRx('SELECT 1;', 'postgres', 'admin').subscribe({
286
+ next: (result) => {
287
+ console.log('Database is healthy');
288
+ resolve(result);
289
+ },
290
+ error: reject
291
+ });
292
+ });
293
+ }
294
+
295
+ async getUserCount(database: string) {
296
+ return new Promise((resolve, reject) => {
297
+ this.psql.execRx('SELECT COUNT(*) FROM users;', database, 'admin').subscribe({
298
+ next: (result) => resolve(parseInt(result.trim())),
299
+ error: reject
300
+ });
301
+ });
302
+ }
461
303
  }
462
304
  ```
463
305
 
464
- ### Process Management
306
+ ### JSON Processing
465
307
 
466
- #### listen(signal, callback)
308
+ ```typescript
309
+ import { execRxAsJson } from '@onivoro/server-process';
467
310
 
468
- Listen for process signals:
311
+ interface ContainerInfo {
312
+ Id: string;
313
+ Name: string;
314
+ State: { Status: string };
315
+ }
469
316
 
470
- ```typescript
471
- function listen(signal: string, callback: () => void | Promise<void>): void
317
+ function getContainerStatus(containerName: string) {
318
+ execRxAsJson<ContainerInfo[]>(`docker inspect ${containerName}`).subscribe({
319
+ next: (containers) => {
320
+ const container = containers[0];
321
+ console.log(`Container ${container.Name} is ${container.State.Status}`);
322
+ },
323
+ error: (err) => console.error('Failed to get container info:', err)
324
+ });
325
+ }
472
326
  ```
473
327
 
474
- #### exit(code?)
475
-
476
- Exit process with optional code:
328
+ ### Process I/O Handling
477
329
 
478
330
  ```typescript
479
- function exit(code?: number): never
331
+ import { listen, exit } from '@onivoro/server-process';
332
+
333
+ function setupInteractiveMode() {
334
+ const { stdin } = listen();
335
+
336
+ console.log('Enter commands (type "exit" to quit):');
337
+
338
+ stdin.subscribe({
339
+ next: (data) => {
340
+ const input = data.toString().trim();
341
+
342
+ if (input === 'exit') {
343
+ console.log('Goodbye!');
344
+ exit(0)();
345
+ } else {
346
+ console.log(`You entered: ${input}`);
347
+ }
348
+ },
349
+ complete: () => {
350
+ console.log('Input stream closed');
351
+ exit(0)();
352
+ }
353
+ });
354
+ }
480
355
  ```
481
356
 
482
- ## Best Practices
357
+ ## Error Handling
483
358
 
484
- 1. **Error Handling**: Always handle errors in process operations
485
- 2. **Resource Cleanup**: Use proper cleanup for long-running processes
486
- 3. **Signal Handling**: Implement graceful shutdown for production applications
487
- 4. **Stream Processing**: Use reactive streams for large outputs
488
- 5. **Security**: Validate input when executing external commands
489
- 6. **Logging**: Log process operations for debugging and monitoring
490
- 7. **Timeouts**: Set appropriate timeouts for long-running operations
491
- 8. **Environment**: Use environment-specific configurations
359
+ All functions handle errors appropriately:
492
360
 
493
- ## Security Considerations
494
-
495
- 1. **Command Injection**: Always validate and sanitize command inputs
496
- 2. **Privilege Escalation**: Run processes with minimal required privileges
497
- 3. **Environment Variables**: Be careful with sensitive environment variables
498
- 4. **File Permissions**: Ensure proper file permissions for executed scripts
499
- 5. **Container Security**: Follow Docker security best practices
500
-
501
- ## Testing
361
+ - **Promise-based functions** reject with error details
362
+ - **Reactive functions** emit errors through the error channel
363
+ - **Process execution errors** include exit codes and stderr information
502
364
 
503
365
  ```typescript
504
- import { execPromise, Docker } from '@onivoro/server-process';
366
+ import { execPromise, execRx } from '@onivoro/server-process';
505
367
 
506
- describe('Process Operations', () => {
507
- it('should execute commands', async () => {
508
- const result = await execPromise('echo "Hello World"');
509
- expect(result.stdout.trim()).toBe('Hello World');
510
- expect(result.code).toBe(0);
511
- });
368
+ // Promise error handling
369
+ try {
370
+ await execPromise('nonexistent-command');
371
+ } catch (error) {
372
+ console.error('Command failed:', error.message);
373
+ }
512
374
 
513
- it('should handle command errors', async () => {
514
- try {
515
- await execPromise('nonexistent-command');
516
- fail('Should have thrown an error');
517
- } catch (error) {
518
- expect(error.code).not.toBe(0);
519
- }
520
- });
375
+ // Reactive error handling
376
+ execRx('invalid-command').subscribe({
377
+ next: (output) => console.log(output),
378
+ error: (error) => console.error('Command error:', error),
379
+ complete: () => console.log('Done')
521
380
  });
522
381
  ```
523
382
 
383
+ ## TypeScript Support
384
+
385
+ All functions are fully typed with TypeScript interfaces. The library uses Node.js built-in types for options and return values.
386
+
524
387
  ## License
525
388
 
526
389
  This library is part of the Onivoro monorepo ecosystem.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onivoro/server-process",
3
- "version": "24.0.0",
3
+ "version": "24.0.1",
4
4
  "type": "commonjs",
5
5
  "main": "./src/index.js",
6
6
  "types": "./src/index.d.ts",