Lightweight persistent stdout rendering for Node.js.
nspin-render is a small composable terminal rendering primitive focused on:
- explicit lifecycle semantics
- deterministic stdout persistence
- bounded rendering behavior
- graceful degradation
- operational transparency
The renderer intentionally avoids:
- framework orchestration
- process ownership
- signal ownership
- runtime-global coordination
- terminal monopolization
- nspin-render
- Overview
- Index
- Installation
- Philosophy
- Basic Usage
- Lifecycle
- Runtime State
- Multiple Spinner Instances
- Graceful Degradation
- Non-TTY Behavior
- Explicit Ownership
- Runtime Philosophy
- Documentation
- Development
- Design Direction
- Runtime Compatibility
- Why This Exists
- Support
- Feedback
- Contributing
- Code of Conduct
- Changelog
- License
bun add @imgildev/nspin-rendernpm install @imgildev/nspin-rendernspin-render is designed around a simple principle:
stdout mutation should remain explicit, local, and mechanically understandableThe project prioritizes:
- composability
- bounded behavior
- explicit ownership
- graceful degradation
- predictable lifecycle semantics
Over:
- abstraction layering
- orchestration systems
- hidden coordination
- framework-style APIs
import { Spinner } from "@imgildev/nspin-render";
const spinner = new Spinner({
frames: ["◐", "◓", "◑", "◒"],
interval: 80,
format: ["bold", "cyan"],
});
spinner.start("Loading...");
setTimeout(() => {
spinner.stop();
}, 2000);The renderer exposes explicit lifecycle operations:
start → pause/resume → stop → destroyLifecycle operations are:
- idempotent
- locally scoped
- composable
- best-effort under terminal instability
spinner.pause();
spinner.resume();Paused instances:
- preserve ownership
- stop render scheduling
- resume deterministically
spinner.destroy();Destroyed instances:
- permanently relinquish ownership
- cannot restart
- degrade safely into no-ops
spinner.isRunning;
spinner.isPaused;
spinner.elapsedMs;State access remains:
- readonly
- local
- mechanically transparent
nspin-render supports concurrent spinner instances through bounded local cursor coordination.
const spinnerA = new Spinner();
const spinnerB = new Spinner();
spinnerA.start("Task A");
spinnerB.start("Task B");The renderer intentionally avoids:
- terminal-global orchestration
- layout engines
- centralized runtime coordination
Terminal environments are imperfect.
nspin-render intentionally tolerates:
- partial TTY support
- cursor failures
- stdout write failures
- external stdout interference
- interrupted cleanup
The renderer favors:
bounded degradation over strict terminal controlFailures remain:
- local
- composable
- operationally bounded
Persistent rendering requires TTY cursor support.
In non-TTY environments:
- cursor movement is intentionally avoided
- persistent redraw behavior is disabled
- rendering degrades to append-only output
This improves compatibility with:
- CI pipelines
- redirected stdout
- logging pipelines
- non-interactive runtimes
Example degraded output:
Loading...
Loading...
Loading...Each spinner instance owns only:
- its own lifecycle
- its own rendering state
- its own cleanup behavior
The renderer does NOT own:
- the process
- signal policy
- terminal-global behavior
- runtime orchestration
This keeps rendering behavior:
- composable
- embeddable
- operationally transparent
The renderer intentionally favors:
- simplicity
- lifecycle clarity
- bounded behavior
- composability
- operational resilience
Over:
- orchestration
- abstraction layering
- speculative extensibility
- framework-style coordination
-
API_REFERENCE.md- Complete API contracts and runtime semantics.
-
ARCHITECTURE.md- Architectural philosophy, ownership model, and rendering coordination.
-
TROUBLESHOOTING.md- Runtime caveats, degradation behavior, and terminal compatibility notes.
-
CONTRIBUTING.md- Contribution workflow and project governance expectations.
-
SECURITY.md- Security policy and supported runtime expectations.
Install dependencies:
bun installRun tests:
bun testRun checks:
bun run checkBuild the package:
bun run buildThe project evolves through:
- operational refinement
- bounded hardening
- composability preservation
- lifecycle clarity
- resilience improvements
The architecture intentionally resists:
- framework drift
- authority expansion
- orchestration semantics
- speculative complexity
Long-term stability is preferred over continuous expansion.
Contributions should preserve the project's core invariants:
- explicit ownership
- composability
- bounded coordination
- graceful degradation
- mechanical transparency
The project intentionally prioritizes:
- simplicity
- operational clarity
- maintainability
- deterministic lifecycle behavior
Over:
- abstraction layering
- orchestration systems
- speculative extensibility
- feature accumulation
Before introducing changes, prefer asking:
does this materially improve operational clarity or runtime resilience without increasing architectural complexity?nspin-render is designed for:
- modern Node.js runtimes
- Bun-compatible environments
- interactive TTY terminals
Behavior may vary across:
- terminal emulators
- CI environments
- shell implementations
- redirected stdout pipelines
Graceful degradation is intentional.
Most spinner libraries optimize primarily for:
- visual effects
- feature accumulation
- abstraction convenience
nspin-render instead focuses on:
- deterministic stdout persistence
- explicit lifecycle semantics
- bounded rendering behavior
- composable terminal interaction
The goal is not to become a terminal framework.
The goal is to remain:
- small
- explicit
- resilient
- mechanically understandable
If you encounter any issues or have suggestions for improvements, please open an issue on GitHub.
If you enjoy using nspin-render, please consider leaving a review on GitHub or sharing your feedback.
Contributions are welcome! Please review our Contributing Guidelines and Development Guide for setup instructions and coding standards.
We strive to create a welcoming, inclusive, and respectful community. Please see our Code of Conduct before contributing.
For a complete list of changes, see the CHANGELOG.md.
This package is licensed under the MIT License.