pulselang
v3.1.0
Published
Pulse 3.1 - Programming language with CSP concurrency, structured concurrency, optional type system, and IR-based compiler
Maintainers
Readme
Pulse 3.0
A programming language with CSP-style concurrency, structured concurrency, and cooperative scheduling.
What is Pulse?
Pulse is a programming language built for concurrent systems programming with:
- Native CSP primitives -
spawn, channels, andselectstatements built into the language - Structured concurrency - Parent tasks automatically manage child task lifecycles
- Cooperative scheduler - Deterministic task execution with predictable behavior
- Optional type system - Gradual typing with
--strict-typesflag - JavaScript interop - Compiles to JavaScript, imports from npm seamlessly
- Advanced compiler - Multi-stage IR-based backend with optimization passes
Pulse 3.0 includes a new compiler architecture with intermediate representation (IR), semantic analysis, and optional static type checking.
When to Use This
Use Pulse Runtime when you need:
Structured Concurrency
- Parent tasks automatically cancel children when cancelled or when they complete
- No orphaned tasks or background work that outlives its context
- Clear task lifecycles with completion promises
Backpressure and Flow Control
- Channels with bounded buffers to prevent unbounded queuing
- Admission control to reject requests when overloaded
- Load shedding based on queue depth or memory pressure
Deterministic Execution
- Tasks execute in predictable order based on logical time, not wall-clock time
- Same inputs produce same execution order (useful for testing and debugging)
- Batch-then-yield scheduling model
Resource Management
- Per-request resource limits (task count, duration, memory)
- Configurable concurrency pools for HTTP servers
- Memory monitoring with state change events
Production Observability
- Prometheus metrics for tasks, channels, HTTP requests
- Graceful shutdown with timeout support
- Health check endpoints for load balancers
Installation
npm install pulselangRequires Node.js 18 or higher.
New in Pulse 3.0
Compiler Architecture
Multi-Stage Compilation Pipeline:
- Lexer → Parser → AST → Semantic Analysis → Type Checking → IR → Optimization → Backend
Intermediate Representation (IR):
- IR-based backend (default in 3.0.0)
- SSA-form register-based IR with control flow graph
- Dead code elimination and constant folding optimizations
- Full validation pass for IR correctness
- ECMAScript-style completion records for exception handling
Semantic Analysis:
- Variable resolution with scope tracking
- Temporal dead zone (TDZ) detection
- const/let enforcement
- Control flow validation (return/break/continue)
Optional Type System:
- Gradual typing with
--strict-typesflag - Type annotations:
const x: number = 42 - Function signatures:
fn add(a: number, b: number): number - Type checking for annotated code only (unannotated code is never checked)
Compiler Flags
Run Pulse files with the pulse or pulselang CLI:
pulse script.pls # Default: IR backend
pulse script.pls --legacy-backend # Use legacy codegen (fallback)
pulse script.pls --strict-types # Enable type checking
pulse script.pls --strict-semantic # Treat semantic warnings as errors
pulse script.pls --sourcemap # Generate source mapsAvailable Flags:
--legacy-backend: Use legacy codegen instead of IR (fallback for compatibility)--strict-types: Enable optional static type checking--strict-semantic: Fail on semantic errors (default: warnings)--strict-ast: Enable strict AST validation--sourcemap: Generate inline source maps for debugging
Language Features
Type Annotations (optional, requires --strict-types):
// Variable type annotations
const x: number = 42;
let name: string = "Alice";
// Function signatures
fn add(a: number, b: number): number {
return a + b;
}
// Type checking only applies to annotated code
const untyped = "hello"; // No type checkingCompiler Modes:
- Default: IR-based backend with optimization passes
- Legacy Backend (
--legacy-backend): Original codegen, available as fallback - Type Checked (
--strict-types): Static type checking for annotated code
Quick Start
Basic Task Spawning
import { spawn, sleep } from 'pulselang/runtime';
// Spawn a concurrent task
const task = spawn(async () => {
await sleep(100);
return 'done';
});
// Wait for completion
const result = await task.completionPromise;
console.log(result); // 'done'HTTP Server with Scheduler Pool
import { createServerWithScheduler, spawn, sleep } from 'pulselang/runtime';
const server = createServerWithScheduler(async (req, res) => {
// All Pulse primitives work inside handlers
const task = spawn(async () => {
await sleep(100);
return `Processed ${req.url}`;
});
const result = await task.completionPromise;
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end(result);
}, {
maxPoolSize: 100, // Max 100 concurrent requests
maxQueueSize: 50, // Max 50 queued when pool full
timeout: 30000 // 30 second request timeout
});
server.listen(3000);
console.log('Server running on port 3000');Channels and Pipelines
import { spawn, Channel } from 'pulselang/runtime';
const ch = new Channel(10); // Buffered channel with capacity 10
// Producer
spawn(async () => {
for (let i = 0; i < 5; i++) {
await ch.send(i);
}
ch.close();
});
// Consumer
spawn(async () => {
for await (const value of ch) {
console.log('Received:', value);
}
});Select Statement
import { spawn, Channel, select, selectCase, sleep } from 'pulselang/runtime';
const dataCh = new Channel(1);
const timeoutCh = new Channel(1);
// Slow data source
spawn(async () => {
await sleep(2000);
await dataCh.send('data');
});
// Timeout after 1 second
spawn(async () => {
await sleep(1000);
await timeoutCh.send('timeout');
});
// Wait for first ready channel
const result = await select([
selectCase({
channel: dataCh,
op: 'recv',
handler: (data) => ({ ok: true, data })
}),
selectCase({
channel: timeoutCh,
op: 'recv',
handler: () => ({ ok: false, error: 'Timeout' })
})
]);
console.log(result); // { ok: false, error: 'Timeout' }Core Features
Structured Concurrency
Tasks form a tree structure with automatic cleanup:
import { spawn, sleep, CancelledError } from 'pulselang/runtime';
const parent = spawn(async () => {
const child = spawn(async () => {
try {
await sleep(5000);
} catch (error) {
if (error instanceof CancelledError) {
console.log('Child was cancelled');
}
}
});
await sleep(100);
return 'parent done';
});
// When parent completes, child is automatically cancelled
await parent.completionPromise;Graceful Shutdown
import { createServerWithScheduler, setupGracefulShutdown } from 'pulselang/runtime';
const server = createServerWithScheduler(handler, options);
setupGracefulShutdown(server, {
timeout: 30000,
onShutdown: (signal) => {
console.log(`Received ${signal}, shutting down...`);
},
onComplete: (result) => {
console.log(`Shutdown complete: ${result.activeWaitedFor} requests finished`);
}
});
server.listen(3000);
// On SIGTERM or SIGINT:
// 1. Stop accepting new requests
// 2. Wait for active requests (up to timeout)
// 3. Close server
// 4. Exit processObservability
import { createServerWithScheduler } from 'pulselang/runtime';
import {
enableMetrics,
getMetricsRegistry,
exportPrometheus
} from 'pulselang/runtime/observability';
// Enable metrics collection
enableMetrics();
const server = createServerWithScheduler(async (req, res) => {
if (req.url === '/metrics') {
const metrics = exportPrometheus(getMetricsRegistry());
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end(metrics);
return;
}
// Your handler...
res.writeHead(200);
res.end('OK');
});
server.listen(3000);
// GET /metrics returns Prometheus-format metrics:
// pulse_task_spawn_total 42
// pulse_http_request_duration_ms_bucket{le="100"} 35
// pulse_http_request_duration_ms_bucket{le="500"} 40
// ...Resource Management
import { withResourceManagement } from 'pulselang/runtime/http-integration.js';
import {
AdmissionController,
LoadShedder
} from 'pulselang/runtime/resources';
// Setup resource management
const admissionController = new AdmissionController({
maxConcurrent: 1000,
maxQueued: 5000
});
const loadShedder = new LoadShedder({
enabled: true,
queueDepth: 1000, // Shed load if queue > 1000
memoryThreshold: 0.85 // Shed load if heap > 85%
});
// Wrap handler with resource management
const handler = withResourceManagement(async (req, res) => {
// Your handler code
res.writeHead(200);
res.end('OK');
}, {
admissionController,
loadShedder
});
const server = http.createServer(handler);
server.listen(3000);
// Requests are automatically:
// - Queued when pool is full
// - Rejected with 503 when queue is full
// - Rejected with 503 when memory/queue thresholds exceededAPI Reference
Complete API documentation with TypeScript examples: docs/api-reference.md
Core Primitives
spawn(fn, options)- Spawn a new tasksleep(ms)- Sleep for millisecondsgetRequestContext()- Get current request context (trace ID, request ID, custom metadata)
Channel Communication
Channel- CSP-style channel with buffered/unbuffered modeschannel.send(value)- Send to channelchannel.recv()- Receive from channelchannel.close()- Close channelfor await...of- Iterate over channel
Select Statement
select(cases)- Wait for first ready channel operationselectCase(config)- Define a select case (send or recv)
HTTP Integration
createServerWithScheduler(handler, options)- Create HTTP server with scheduler poolsetupGracefulShutdown(server, options)- Setup graceful shutdowncreateHealthCheckHandler(server)- Create health check endpointgetPoolStats(server)- Get pool statisticsgetHealth(server)- Get health status
Advanced
SchedulerPool- Manual pool management (most users don't need this)
Errors
CancelledError- Thrown when task is cancelledPoolExhaustedError- Thrown when pool is exhausted
TypeScript Support
Full TypeScript definitions with type inference:
import { spawn, Channel, Task } from 'pulselang/runtime';
// Type inference works
const task: Task<number> = spawn(async () => {
return 42;
});
const result: number = await task.completionPromise;
// Generic channel types
const ch = new Channel<string>(5);
await ch.send('hello'); // OK
await ch.send(42); // Type error!
const [value, ok] = await ch.recv(); // value: string, ok: booleanProduction Patterns
Pulse Runtime includes production-ready patterns built on core primitives:
- Rate Limiter - Token bucket with burst capacity
- Circuit Breaker - Fault isolation with CLOSED/OPEN/HALF_OPEN states
- Worker Pool - Bounded concurrency control
- Request Deduplication - Singleflight pattern to prevent duplicate work
- Retry Logic - Exponential backoff with jitter
See pattern documentation for usage examples.
Examples
The examples/ directory contains runnable programs:
production-server/- Complete production server with graceful shutdown and health checksobservability-demo.js- Metrics endpoint and monitoringresource-management-demo.js- Admission control and load shedding- Pattern examples in
lib/runtime/patterns/
Run examples:
node examples/production-server/server.js
node examples/observability-demo.js
node examples/resource-management-demo.jsPerformance
Pulse Runtime 2.0 includes a comprehensive benchmark suite:
- Primitive benchmarks (spawn, sleep, channels, select)
- HTTP server load tests at various concurrency levels
- Memory leak detection (validated with 50k+ operations)
- Automated regression detection
Run benchmarks:
node benchmarks/primitives/spawn-bench.js
node benchmarks/http/http-bench.js
node benchmarks/memory/leak-detection.jsPerformance characteristics:
- Zero overhead when observability/resource management disabled
- <5% overhead when metrics enabled with sampling
- Deterministic batch-then-yield scheduling model
- No memory leaks in sustained high-load scenarios
Testing
Run the full test suite:
npm testAll tests (42/42) pass:
- Deterministic scheduler tests
- Channel tests (buffered, unbuffered, iteration)
- Select tests (multiple channels, timeouts)
- HTTP integration tests (pool, timeout, abort)
- Error path tests (cleanup, cancellation)
- Graceful shutdown tests
Documentation
- API Reference - Complete API documentation with TypeScript examples
- Getting Started - Detailed getting started guide
- HTTP Guide - HTTP server integration guide
- Phase completion docs in
docs/for implementation details
Design Principles
Pulse Runtime 2.0 follows these principles:
- Determinism - Tasks execute in predictable order based on logical time
- Structured concurrency - Parent-child relationships with automatic cleanup
- Opt-in features - Observability and resource management are separate imports
- Zero overhead when disabled - Fast-path inline checks for optional features
- Production-first - Graceful shutdown, health checks, metrics, admission control
- API stability - Public API is frozen, semantic versioning guarantees
Known Limitations
- HTTP handlers are the only supported entry point (no standalone CLI/batch support)
- Distributed tracing not yet implemented (metrics only, no span propagation)
- Debug inspectors deferred (no runtime introspection endpoints)
- Worker pools don't support dynamic scaling
Contributing
Contributions welcome. The codebase is organized as:
lib/runtime/- Core scheduler, channels, select, HTTP integrationlib/runtime/observability/- Metrics collection and exporterslib/runtime/resources/- Resource management (admission control, load shedding)lib/runtime/patterns/- Production patternsbenchmarks/- Performance benchmarkstests/- Test suiteexamples/- Example programsdocs/- Documentation
License
MIT
Links
- Repository: https://github.com/osvfelices/pulse
- Documentation: https://osvfelices.github.io/pulse/
- Issues: https://github.com/osvfelices/pulse/issues
- npm: https://www.npmjs.com/package/pulselang
