codinglombok / lombokasync
Cooperative async on PHP Fibers: event loop with timers, tasks, timeout, bounded and unbounded mpsc channels, oneshot channels, joinAll and select with a cross-language contract. Zero dependencies.
Package info
github.com/codinglombok/LombokAsync
Language:Rust
pkg:composer/codinglombok/lombokasync
Requires
- php: >=8.1
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Small async toolkit with one behaviour in five languages: bounded and unbounded mpsc channels, oneshot channels,
join_all,select, andtimeout, plus a single-threaded Rust executor. Zero runtime dependencies.
Part of the Lombok Ecosystem.
Mengapa library ini? (Why this library?)
- Same rules everywhere. Rust, TypeScript, Python, Go, and PHP follow one written contract (SPEC): when a send is
fullorclosed, when a receive isempty, which task wins aselecttie (the lowest index), which errorjoin_allreports (the lowest index, not the earliest). All five ports run the same 141 vector cases in CI. - Explicit channel states. Non-blocking
try_send/try_recvwith named outcomes, bounded capacity for backpressure, sender cloning, and receiverclose()that still drains queued values. - A real small executor in Rust. Ready queue, timer heap, thread parking instead of busy-polling, wakers usable from other threads, and
#![forbid(unsafe_code)]. - Idiomatic per language. Rust
Result, TypeScript status objects andAsyncError, Python exceptions onasyncio, Go sentinel errors withcontextvariants, PHP Fibers with a timer-driven event loop.
Installation
| Language | Package | Status |
|---|---|---|
| Rust | lombokasync (crates.io) |
not yet published |
| TypeScript / JavaScript | lombokasync (npm) |
not yet published; dual ESM + CJS build |
| Python | lombokasync (PyPI) |
not yet published |
| Go | github.com/codinglombok/lombokasync/go |
tag go/v0.2.0 on release |
| PHP | codinglombok/lombokasync (Packagist) |
not yet published; root composer.json — release.yml pings Packagist on v* tags (one-time submit) |
On a v* tag, release.yml also creates a GitHub Release containing every port's package artifact (npm tarball, Python sdist + wheel, Rust .crate, PHP composer dist zip) and pings Packagist.
Until then, use the source in this repository. See docs/How To Dist_LombokAsync_0.2.0.md.
Quick start
Rust
use lombokasync::{block_on, mpsc, spawn, timeout}; use std::time::Duration; let total = block_on(async { let (tx, mut rx) = mpsc::bounded::<u32>(8).unwrap(); for i in 0..3 { let tx = tx.clone(); spawn(async move { tx.send(i).await.unwrap() }); } drop(tx); let mut sum = 0; while let Some(v) = rx.recv().await { sum += v; } sum }); assert_eq!(total, 3); assert_eq!(block_on(timeout(Duration::from_millis(50), async { 1 })), Ok(1));
TypeScript
import { mpscChannel, select, timeout, AsyncError } from 'lombokasync'; const [tx, rx] = mpscChannel<number>(2); tx.trySend(1); // 'ok' tx.trySend(2); // 'ok' tx.trySend(3); // 'full' rx.tryRecv(); // { status: 'value', value: 1 } const { index, value } = await select([fetchA(), fetchB()]); try { await timeout(500, fetchSlow()); } catch (e) { if (e instanceof AsyncError && e.code === 'TIMEOUT') { /* ... */ } }
Python
import lombokasync as la async def main(): tx, rx = la.mpsc_channel(capacity=8) la.spawn(producer(tx)) async for item in rx: print(item) results = await la.join_all([fetch(1), fetch(2)]) index, value = await la.select([fast(), slow()]) la.run(main())
Go
import lombokasync "github.com/codinglombok/lombokasync/go" tx, rx, _ := lombokasync.NewBoundedMpsc[int](8) go func() { defer tx.Close(); for i := 0; i < 3; i++ { tx.Send(i) } }() for v, ok := rx.Recv(); ok; v, ok = rx.Recv() { fmt.Println(v) } v, err := lombokasync.Timeout(time.Second, func() int { return 42 })
PHP
use LombokAsync\Async; use LombokAsync\Channel\MpscChannel; $sum = Async::run(function (): int { [$tx, $rx] = MpscChannel::bounded(8); Async::spawn(function () use ($tx) { foreach ([1, 2, 3] as $v) { $tx->send($v); } $tx->close(); }); $sum = 0; foreach ($rx as $v) { $sum += $v; } return $sum; });
Ports
| Feature | Rust | TypeScript | Python | Go | PHP |
|---|---|---|---|---|---|
| mpsc (bounded/unbounded, clone, close) | YES | YES | YES | YES | YES |
| oneshot | YES | YES | YES | YES | YES |
| join_all / select / timeout | YES | YES | YES | YES | YES |
| own executor | YES (block_on) |
JS event loop | asyncio | goroutines | YES (Fibers) |
| shared vectors | 141/141 | 141/141 | 141/141 | 141/141 | 141/141 |
Known limitations
No I/O reactor (sockets, files), no broadcast or watch channels, no thread pool; the Rust executor is single-threaded. Go Timeout cannot stop the goroutine it started (use TimeoutCtx). PHP publishing to Packagist reads this monorepo directly (root composer.json, composer dist trimmed by .gitattributes export-ignore) and is automated in release.yml; it needs a one-time submission on Packagist plus the PACKAGIST_USERNAME / PACKAGIST_TOKEN secrets. Full list: docs/Full Summary Project_LombokAsync_0.2.0.md.
Upgrading from 0.1.0
0.2.0 changes every port: channel outcomes are explicit, timeout reports a TIMEOUT error instead of None/undefined, select takes a list and returns the winning index, and the Go module path is now lowercase. See CHANGELOG.md.
Development
cd rust && cargo test && cargo clippy --all-targets -- -D warnings cd typescript && npm ci && npm run lint && npm run coverage && npm run build && npm run build:cjs && npm run smoke cd python && python -m pytest && ruff check . && mypy cd go && go test -race ./... cd php && php tests/run.php && phpstan analyse --no-progress --memory-limit=1G && composer validate --strict bash scripts/lombok-doctor.sh LombokAsync
Coverage gates follow the ecosystem standard: 95% lines/statements and 90% branches in every port (Rust line-based via cargo-llvm-cov, TS 95/90/90, Python branch mode, Go statements, PHP pcov).
License
Apache-2.0. See LICENSE.