npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@procedures/playground

v0.1.0

Published

A typed fluent API to write procedure programs in TypeScript (proc, ref, run) with autocomplete, then run them with the client.

Readme

@procedures/playground

npm

License: MIT Node.js Version

Type-safe aggregation building for procedure compositions

Overview

client-playground provides a fluent TypeScript API for composing procedure references with full type safety and autocomplete. Write aggregations as executable TypeScript files (.play.ts) that can be run directly with tsx or ts-node. Perfect for testing procedure compositions, building complex workflows, and exploring procedure APIs interactively.

Key Features

  • Type-Safe Builder API: Fluent interface with full TypeScript autocomplete
  • Executable Scripts: Write .play.ts files that run directly
  • Output References: Reference previous stage outputs with $ref syntax
  • Named Stages: Name stages for later reference in the execution DAG
  • Execution Control: Control when procedures execute ($immediate, $never, $parent)
  • Local Transport: Uses in-process transport for fast execution
  • Zero Configuration: Auto-discovers registered procedures

Architecture

graph TB
    subgraph "Playground File (.play.ts)"
        BUILDER[ProcBuilder API]
        REF[Output References]
        RUN[run() Executor]
    end

    subgraph "Runtime"
        LOCAL[Local Transport]
        REGISTRY[Procedure Registry]
        EXEC[Execution Engine]
    end

    subgraph "Procedures"
        PROC1[git.status]
        PROC2[git.add]
        PROC3[pnpm.install]
    end

    BUILDER --> REF
    REF --> RUN
    RUN --> LOCAL
    LOCAL --> REGISTRY
    REGISTRY --> EXEC
    EXEC --> PROC1
    EXEC --> PROC2
    EXEC --> PROC3

    style BUILDER fill:#7ed321
    style RUN fill:#f5a623
    style LOCAL fill:#50e3c2

Execution Flow

sequenceDiagram
    participant Script as playground.play.ts
    participant Builder as proc()
    participant Executor as run()
    participant Registry as PROCEDURE_REGISTRY
    participant Proc as Procedure Handler

    Script->>Builder: proc(["git", "status"]).input({ cwd: "." })
    Builder-->>Script: ProcBuilder with .ref
    Script->>Executor: run(procRef)
    Executor->>Registry: Discover all procedures
    Registry-->>Executor: Registered procedures
    Executor->>Executor: Create local transport
    Executor->>Executor: Sync registry to transport
    Executor->>Proc: Execute procedure
    Proc-->>Executor: Result
    Executor->>Executor: Print result (if print: true)
    Executor-->>Script: Typed result

Installation

npm install @procedures/playground

Or with pnpm:

pnpm add @procedures/playground

Dependencies

This package depends on:

  • @procedures/client - Core client/procedure system (dynamically imported)

Runtime Requirements

  • tsx or ts-node - For executing .play.ts files
  • Node.js >= 22.0.0

Usage

Quick Start

Create a playground file:

// my-workflow.play.ts
import { proc, ref, run } from "@procedures/playground";

// Execute a simple procedure
run(
  proc(["git", "status"]).input({ cwd: "." }).ref
);

Run it:

npx tsx my-workflow.play.ts

Output:

{
  "branch": "main",
  "ahead": 0,
  "behind": 0,
  "files": [],
  "clean": true
}

Builder API

The proc() builder provides a fluent interface:

import { proc } from "@procedures/playground";

// Simple procedure reference
const gitStatus = proc(["git", "status"])
  .input({ cwd: "/path/to/repo" })
  .ref;

// Named stage for reference
const hasChanges = proc(["git", "hasChanges"])
  .input({ cwd: "." })
  .name("changes")  // Can be referenced as $ref: "changes"
  .ref;

// With execution control
const conditionalAdd = proc(["git", "add"])
  .input({ all: true })
  .when("$parent")  // Execute in parent context
  .ref;

Output References

Reference outputs from named stages:

import { proc, ref, run } from "@procedures/playground";

run(
  proc(["dag", "traverse"]).input({
    visit: [
      // Stage 1: Check for changes
      proc(["git", "hasChanges"])
        .name("changes")
        .ref,

      // Stage 2: Conditional on stage 1
      proc(["client", "conditional"]).input({
        condition: ref("changes.value"),  // Reference "changes" output
        then: proc(["git", "add"]).input({ all: true }).ref,
      }).ref,
    ],
  }).ref
);

Reference Syntax

ref("changes.value")     // Reference the .value field of "changes" output
ref("$last")             // Reference the previous step's output
ref("$last.success")     // Reference the .success field of previous output
ref("install.packages")  // Deep property access

Complex Workflows

import { proc, ref, run } from "@procedures/playground";

// Multi-step build and deploy workflow
run(
  proc(["dag", "traverse"]).input({
    visit: [
      // 1. Install dependencies
      proc(["pnpm", "install"])
        .name("install")
        .ref,

      // 2. Run tests
      proc(["shell", "exec"])
        .input({ command: "npm test" })
        .name("test")
        .ref,

      // 3. Build only if tests pass
      proc(["client", "conditional"]).input({
        condition: ref("test.exitCode"),  // 0 = success
        operator: "equals",
        value: 0,
        then: proc(["shell", "exec"])
          .input({ command: "npm run build" })
          .name("build")
          .ref,
      }).ref,

      // 4. Deploy if build succeeded
      proc(["client", "conditional"]).input({
        condition: ref("build.success"),
        then: proc(["docker", "build"]).input({
          context: ".",
          tag: "my-app:latest",
        }).ref,
      }).ref,
    ],
  }).ref
);

Running Without Printing

import { run } from "@procedures/playground";

// Get result without printing
const result = await run(
  proc(["git", "status"]).ref,
  { print: false }
);

console.log(`Current branch: ${result.branch}`);

// Custom formatting
const data = await run(
  proc(["git", "log"]).input({ count: 5 }).ref,
  { print: true, format: "text" }
);

API Reference

proc(path: ProcedurePath): ProcBuilder

Create a procedure reference builder.

Parameters:

  • path: string[] - Procedure path (e.g., ["git", "status"])

Returns: ProcBuilder with methods:

.input<T>(input: T): ProcBuilder<T>

Set the input for this procedure.

proc(["git", "add"])
  .input({ files: ["file.ts"], all: false })
  .ref

.name(name: string): ProcBuilder

Name this stage for later reference with $ref.

proc(["git", "hasChanges"])
  .name("changes")
  .ref

// Later:
ref("changes.value")

.when(when: string): ProcBuilder

Set when this reference should be executed.

Values:

  • "$immediate" - Execute immediately
  • "$never" - Never execute (for testing)
  • "$parent" - Execute in parent context
  • Custom context name
proc(["git", "add"])
  .when("$parent")
  .ref

.ref: ProcedureRefJson

Get the procedure reference object.

const procRef = proc(["git", "status"])
  .input({ cwd: "." })
  .ref;

// procRef is:
// {
//   $proc: ["git", "status"],
//   input: { cwd: "." }
// }

ref(path: string): OutputRef

Create an output reference.

Parameters:

  • path: string - Reference path (e.g., "stageName.value" or "$last.value")

Returns: OutputRef object

const outputRef = ref("changes.value");
// { $ref: "changes.value" }

run<T>(procedureRef: ProcedureRefJson, options?: RunOptions): Promise<T>

Execute a procedure reference.

Parameters:

  • procedureRef: ProcedureRefJson - Procedure reference to execute
  • options?: RunOptions - Execution options

Options:

interface RunOptions {
  print?: boolean;      // Print result (default: true)
  format?: "json" | "text";  // Output format (default: "json")
}

Returns: Promise<T> - Typed result

// With printing (default)
await run(proc(["git", "status"]).ref);

// Without printing
const result = await run(
  proc(["git", "status"]).ref,
  { print: false }
);

// Text format
await run(
  proc(["shell", "exec"]).input({ command: "ls" }).ref,
  { format: "text" }
);

Types

ProcBuilder

interface ProcBuilder<TInput = unknown> {
  input<T>(input: T): ProcBuilder<T>;
  name(name: string): ProcBuilder<TInput>;
  when(when: string): ProcBuilder<TInput>;
  readonly ref: ProcedureRefJson<TInput>;
}

ProcedureRefJson

interface ProcedureRefJson<TInput = unknown> {
  $proc: ProcedurePath;
  input: TInput;
  $name?: string;
  $when?: string;
}

OutputRef

interface OutputRef {
  readonly $ref: string;
}

Examples

Git Workflow

// git-workflow.play.ts
import { proc, ref, run } from "@procedures/playground";

run(
  proc(["dag", "traverse"]).input({
    visit: [
      // Check for changes
      proc(["git", "hasChanges"])
        .name("changes")
        .ref,

      // Stage all if changes exist
      proc(["client", "conditional"]).input({
        condition: ref("changes.value"),
        then: proc(["git", "add"]).input({ all: true }).ref,
      }).ref,

      // Commit
      proc(["git", "commit"]).input({
        message: "Automated commit from playground",
      }).ref,
    ],
  }).ref
);

Build Pipeline

// build.play.ts
import { proc, run } from "@procedures/playground";

run(
  proc(["dag", "traverse"]).input({
    visit: [
      proc(["pnpm", "install"]).ref,
      proc(["shell", "exec"]).input({ command: "npm run lint" }).ref,
      proc(["shell", "exec"]).input({ command: "npm test" }).ref,
      proc(["shell", "exec"]).input({ command: "npm run build" }).ref,
    ],
  }).ref
);

Docker Deployment

// deploy.play.ts
import { proc, ref, run } from "@procedures/playground";

run(
  proc(["dag", "traverse"]).input({
    visit: [
      // Build image
      proc(["docker", "build"]).input({
        context: ".",
        tag: "my-app:latest",
      }).name("build").ref,

      // Run container
      proc(["docker", "run"]).input({
        image: "my-app:latest",
        name: "my-app-container",
        ports: ["3000:3000"],
        detach: true,
      }).ref,

      // Health check
      proc(["shell", "exec"]).input({
        command: "curl http://localhost:3000/health",
      }).ref,
    ],
  }).ref
);

Testing Procedures

// test-procedures.play.ts
import { proc, run } from "@procedures/playground";

// Test git status
console.log("=== Testing git.status ===");
await run(proc(["git", "status"]).input({ cwd: "." }).ref);

// Test pnpm store path
console.log("\n=== Testing pnpm.store.path ===");
await run(proc(["pnpm", "store", "path"]).ref);

// Test shell execution
console.log("\n=== Testing shell.exec ===");
await run(proc(["shell", "exec"]).input({ command: "echo 'Hello from playground!'" }).ref);

Conditional Logic

// conditional.play.ts
import { proc, ref, run } from "@procedures/playground";

run(
  proc(["dag", "traverse"]).input({
    visit: [
      // Get environment
      proc(["shell", "exec"])
        .input({ command: "echo $NODE_ENV" })
        .name("env")
        .ref,

      // Install based on environment
      proc(["client", "conditional"]).input({
        condition: ref("env.stdout"),
        operator: "contains",
        value: "production",
        then: proc(["pnpm", "install"]).input({ production: true }).ref,
        else: proc(["pnpm", "install"]).ref,
      }).ref,
    ],
  }).ref
);

Use Cases

1. Interactive Procedure Testing

Quickly test procedure calls without setting up a full client:

// test-docker.play.ts
import { proc, run } from "@procedures/playground";

await run(proc(["docker", "ps"]).input({ all: true }).ref);
npx tsx test-docker.play.ts

2. Complex Workflow Prototyping

Prototype multi-step workflows before integrating into apps:

// prototype-workflow.play.ts
import { proc, ref, run } from "@procedures/playground";

// Test the entire workflow
run(proc(["dag", "traverse"]).input({ visit: [...] }).ref);

3. CI/CD Scripts

Write deployment scripts as executable TypeScript:

// deploy.play.ts
run(proc(["dag", "traverse"]).input({
  visit: [
    proc(["git", "pull"]).ref,
    proc(["pnpm", "install"]).ref,
    proc(["shell", "exec"]).input({ command: "npm run build" }).ref,
    proc(["docker", "build"]).input({ context: ".", tag: "app:latest" }).ref,
    proc(["docker", "compose", "up"]).input({ detach: true }).ref,
  ],
}).ref);

4. Development Automation

Automate common development tasks:

// setup.play.ts
// Run: npx tsx setup.play.ts
import { proc, run } from "@procedures/playground";

run(proc(["dag", "traverse"]).input({
  visit: [
    proc(["git", "checkout"]).input({ branch: "main" }).ref,
    proc(["git", "pull"]).ref,
    proc(["pnpm", "install"]).ref,
    proc(["shell", "exec"]).input({ command: "npm run setup:env" }).ref,
  ],
}).ref);

Advanced Usage

Custom Transport

import { run } from "@procedures/playground";
import { LocalTransport, PROCEDURE_REGISTRY } from "@procedures/client";

// run() uses LocalTransport internally
// The registry is synced automatically

Error Handling

import { proc, run } from "@procedures/playground";

try {
  const result = await run(
    proc(["nonexistent", "procedure"]).ref,
    { print: false }
  );
} catch (error) {
  console.error("Execution failed:", error.message);
}

TypeScript Integration

import { proc, run } from "@procedures/playground";
import type { GitStatusOutput } from "@procedures/git";

// Typed result
const status = await run<GitStatusOutput>(
  proc(["git", "status"]).input({ cwd: "." }).ref,
  { print: false }
);

console.log(`Branch: ${status.branch}`);
console.log(`Clean: ${status.clean}`);

Performance Considerations

  • Local Transport: Uses in-process transport for minimal overhead
  • Registry Sync: Procedures synced once per run() call
  • No Network: All execution is local - no HTTP/WebSocket overhead
  • Fast Iteration: Changes to .play.ts files are immediate (no build step)

Comparison with Alternatives

vs. Direct Client Usage

// Direct client - more boilerplate
import { Client, LocalTransport, PROCEDURE_REGISTRY } from "@procedures/client";
const client = new Client(new LocalTransport({ registry: PROCEDURE_REGISTRY }));
const result = await client.exec(["git", "status"], { cwd: "." });

// Playground - concise
import { proc, run } from "@procedures/playground";
const result = await run(proc(["git", "status"]).input({ cwd: "." }).ref);

vs. Shell Scripts

# Shell script - no type safety
git status
pnpm install
npm test
// Playground - type-safe, composable
run(proc(["dag", "traverse"]).input({
  visit: [
    proc(["git", "status"]).ref,
    proc(["pnpm", "install"]).ref,
    proc(["shell", "exec"]).input({ command: "npm test" }).ref,
  ],
}).ref);

License

MIT

Related Packages

  • @procedures/client - Core client/procedure system
  • @procedures/bundle-dev - Development bundle with procedures
  • All client-* packages - Provide procedures to use in playground