Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Key Concepts

Before you dive deep, let's cover a few concepts that will make everything click.

Properties vs Methods

This trips up everyone at first. Some APIs are properties (no parentheses), some are methods (with parentheses).

Properties:

.lines    // Not .lines()
.status   // Not .status()
.first    // Not .first()
.last     // Not .last()

Methods:

.collect()
.map()
.filter()
.count()

Why? Properties are getters that return new objects or promises. Methods are functions you call. Your IDE will help, but when in doubt, check the docs.

Error Propagation

Errors flow through pipelines like data—no need to check at every step:

try {
  await run("command1")
    .run("command2")
    .run("command3")
    .lines
    .forEach(process);
} catch (error) {
  // All errors caught here
  handle(error);
}

Errors from processes, transformations, or your own code all propagate to the same place. For details, see Error Handling.

Resource Management

Golden rule: Always consume process output.

Good:

await run("ls").lines.collect();  // ✅ Output consumed
await run("ls").lines.forEach(console.log);  // ✅ Output consumed

Bad:

const p = run("ls");  // ❌ Output never consumed = resource leak

Why? Unconsumed output keeps the process handle open. Always use .collect(), .forEach(), or iterate through the output.

Enumeration Pattern

enumerate() wraps an iterable to give it Array-like methods. To add indices, call .enum() on the result:

import { enumerate } from "jsr:@j50n/proc@0.23.3";

// enumerate() wraps the iterable, .enum() adds indices
const numbered = await enumerate(["a", "b", "c"])
  .enum()  // Returns [item, index] tuples
  .map(([item, i]) => `${i}: ${item}`)
  .collect();
// ["0: a", "1: b", "2: c"]

Why two steps? enumerate() gives you the methods (map, filter, etc.), while .enum() is just one of many operations you can perform. You might not always need indices:

// Use enumerate() without .enum() for other operations
const doubled = await enumerate([1, 2, 3])
  .map(n => n * 2)  // No indices needed
  .collect();

Streaming Everything

proc is lazy and streaming by default. Nothing happens until you consume the output.

// This doesn't run anything yet
const pipeline = run("cat", "huge-file.txt")
  .run("grep", "error")
  .lines
  .map(line => line.toUpperCase());

// Now it runs, one line at a time
for await (const line of pipeline) {
  console.log(line);  // Processes one line at a time
}

This means you can process files larger than memory. The data flows through, never all loaded at once.

Type Safety

proc is fully typed. Your IDE will guide you:

const lines: string[] = await run("ls").lines.collect();
//    ^-- TypeScript knows this is string[]

const count: number = await run("ls").lines.count();
//    ^-- TypeScript knows this is number

If you see a type error, you're probably using the API wrong. Check the docs!

Next Steps

Now that you understand the concepts, dive into: