concurrent-c-node
JavaScript — and every npm package — from Python.
Part of Concurrent-C — a
strict C11-superset preprocessor: .ccs lowers to plain C and compiles
with your host C compiler. (This bridge itself is pure Python.)
import cc_node
js = cc_node.create() # an Isolation Domain: one node child
_ = js.require('lodash') # resolved from YOUR cwd's node_modules
_.chunk([1, 2, 3, 4, 5], 2) # [[1, 2], [3, 4], [5]]
_.sortBy([{'n': 3}, {'n': 1}], 'n') # dicts cross as objects, and back
semver = js.require('semver')
semver.satisfies('1.2.3', '^1.0.0') # True
js.destroy() # or: with cc_node.create() as js: ...
The bridge is pure Python, stdlib only — no compiled code, no
dependencies, nothing to build. The domain is a spawned node
child (~28ms to first call), so you get real Node: full stdlib, native
addons, whatever npm installs. Promise-based APIs look synchronous
from Python, and bulk data crosses through shared memory — an 8MB
array in 9ms where the same values as a JSON list take 583ms.
pip install concurrent-c-node # needs node on PATH (or point at one)
python -m cc_node.examples.use_node
python -m cc_node.examples.bench_wire
Import stays import cc_node. Examples ship in the wheel. The mirror of
concurrent-c-python
— same domain model, same materialization rules, pointed the other way:
- Values: plain data (finite numbers, strings, booleans,
None, lists/dicts of the same) crosses by value; everything else is a live handle owned by the domain — attribute access is property lookup (methods arrive bound), calls are calls,str()isString(). Non-finite floats cross tagged, never silently nulled. - The domain rules hold: handles never cross bridges;
stats()is the handle ledger andrelease()drops one early;destroy()is idempotent, every door answersbridge is closedafter, and the child dies with the bridge (and on host exit, via stdin EOF).
Async is free
A thenable result is awaited in the child before the reply, so
promise-based package APIs need nothing special — no event loop on the
Python side, no await:
fetchish = js.eval('async (x) => { return { doubled: x * 2 } }')
fetchish(21) # {'doubled': 42} — just a call
Whatever an npm package's API returns — value or promise — the call site reads the same.
Callbacks: Python functions as JS functions
A Python callable passed as an argument crosses as a JS function, and may be called back any number of times — including from inside async JS code:
mapped = js.eval('(f) => [1, 2, 3].map(f)')(lambda x, *rest: x * 10)
# [10, 20, 30] — JS conventions apply: map passes (value, index, array),
# so a lambda takes *rest. Exceptions cross both ways, messages intact.
Nested callbacks compose (the wire alternates strictly), and a Python exception inside one surfaces as the JS error at the call site — and vice versa.
Buffers: typed arrays, shared memory
bytes, array.array, and 1-D numpy arrays cross as
Float64Array / Int32Array / Uint8Array / … and come back as numpy
arrays (or array.array without numpy):
import array
total = js.eval('(a) => a.reduce((s, x) => s + x, 0)')
total(array.array('d', range(1_000_000))) # crosses via shared memory
Small buffers inline; big ones spill through shared memory — one memcpy per side, the receiver consumes the spill file, and the sender sweeps it if the child died first. Nothing strays, and nothing is silently truncated: an unsupported type is an articulate error.
Choosing the node
Same ambient-first rule as the rest of the family: the domain runs
whatever node your project runs.
create(node='/path/to/node')from code — per-domain.CC_NODE_BINin the environment.nodeonPATH.
And which packages it sees is the working directory's
node_modules — require resolves exactly as node itself would there.
Run Python in your project, get your project's packages: npm install
next to your program is the whole setup.
Publishing
From the Concurrent-C repo root (packs this wheel and the npm sibling):
./scripts/publish_bridges.sh # → out/pypi/concurrent_c_node-* (+ npm tgz)
./scripts/publish_bridges.sh --publish # bump patch, pack, twine + npm publish
Measured
From python -m cc_node.examples.bench_wire (sources under
cc_node/examples/)
on a 4-vCPU x86-64 box, node 22 / python 3.11
(perf/baselines/cc_node_bridge_py_20260809.txt;
catalog: perf/baselines/README.md):
| what | result |
|---|---|
| spawn a domain (node child, first eval) | 28ms |
| wire round trip (smallest call) | 116µs |
| Python-callback round trip (JS → Python → JS) | 238µs |
8MB array('d') argument, shm spill |
9.2ms |
| the same 8MB as a JSON list | 583ms — the spill is 63x |
The wire is strict request/response JSON over stdio with the shared-memory spill for bulk data — the same discipline concurrent-c-python's isolated domains speak, mirrored. True pinned zero-copy leases remain future work.
A worked tour (builtin Node modules, chains, callbacks, thenables,
buffers — no npm install needed):
python -m cc_node.examples.use_node.
Adversarial multi-child storm (fanout, callback blizzard, shm hail,
teardown derby): stress/bridge/
— ./stress/bridge/run.sh (CHAOS_SCALE=full for bigger N; latency demos
stay in cc_node/examples/).
And when the hot path is YOUR code rather than an npm package, skip the wire entirely: a page of Concurrent-C (or C) exports as a native module for Python and Node both — 40-90ns calls, stable-ABI artifacts. See Native modules for Node and Python.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file concurrent_c_node-0.3.0.tar.gz.
File metadata
- Download URL: concurrent_c_node-0.3.0.tar.gz
- Upload date:
- Size: 14.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
977850c5943c7cc82d9eba63a9bd344bfda0518ba3d645cd60b77c8081dd963d
|
|
| MD5 |
0586f3a5539ac68675724600ad97b4c8
|
|
| BLAKE2b-256 |
fefbde5754fa61adfe41e7c4bf1ad83af3bdbc9389eaa4cdd0dbd7a907afb7cc
|
File details
Details for the file concurrent_c_node-0.3.0-py3-none-any.whl.
File metadata
- Download URL: concurrent_c_node-0.3.0-py3-none-any.whl
- Upload date:
- Size: 13.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cf6f98c7da925639d677663fa24b5174b9173f9c10a808542e064f0aac426304
|
|
| MD5 |
01de8d2c3385b2ba6a0fd14cc1b7b441
|
|
| BLAKE2b-256 |
a77c774ef31ab517e95b17eda8704935b8d857312ce196f5b14c061a0011c9b5
|