Skip to main content

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)

Import stays import cc_node. 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() is String(). Non-finite floats cross tagged, never silently nulled.
  • The domain rules hold: handles never cross bridges; stats() is the handle ledger and release() drops one early; destroy() is idempotent, every door answers bridge is closed after, 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.

  1. create(node='/path/to/node') from code — per-domain.
  2. CC_NODE_BIN in the environment.
  3. node on PATH.

And which packages it sees is the working directory's node_modulesrequire 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.

Measured

From examples/bench_wire.py on a 4-vCPU x86-64 box, node 22 / python 3.11 (dated baselines under perf/baselines/ in the repo):

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): examples/use_node.py.

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

concurrent_c_node-0.2.0.tar.gz (11.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

concurrent_c_node-0.2.0-py3-none-any.whl (10.6 kB view details)

Uploaded Python 3

File details

Details for the file concurrent_c_node-0.2.0.tar.gz.

File metadata

  • Download URL: concurrent_c_node-0.2.0.tar.gz
  • Upload date:
  • Size: 11.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.6

File hashes

Hashes for concurrent_c_node-0.2.0.tar.gz
Algorithm Hash digest
SHA256 8545eec7b2d7ee011e49c341afd4a9a2551fc1a9a645a2195745111fb44c7891
MD5 4fa7bad0567cea691f336a820a33e5f9
BLAKE2b-256 67cae61e549375c835c7910af4a30bac73af2af362f5898057ecfd996c7888e3

See more details on using hashes here.

File details

Details for the file concurrent_c_node-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for concurrent_c_node-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e5f7cab621b015a1c47d378f730d9ff2bddfd2ab4f035cab109278240a8d5cd6
MD5 7196189cc68399427414a2de49ae157e
BLAKE2b-256 88d1d9cf1c6358c4b74a6b5d74d03f3df2a4465ca4da6885c43c21e20e552a9b

See more details on using hashes here.

Release history Release notifications | RSS feed

0.23.3

2 files

0.23.2

2 files

0.23.1

2 files

0.23.0

2 files

0.22.1

2 files

0.22.0

2 files

0.21.0

2 files

0.20.0

2 files

0.19.0

2 files

0.18.0

2 files

0.17.11

2 files

0.17.10

2 files

0.17.9

2 files

0.17.8

2 files

0.17.7

2 files

0.17.6

2 files

0.17.5

2 files

0.17.4

2 files

0.17.3

2 files

0.17.2

2 files

0.17.1

2 files

0.17.0

2 files

0.16.0

2 files

0.15.0

2 files

0.14.0

2 files

0.12.0

2 files

0.11.0

2 files

0.10.0

2 files

0.7.0

2 files

0.6.0

2 files

0.5.0

2 files

0.4.1

2 files

0.4.0

2 files

0.3.0

2 files

0.2.1

2 files

This release

0.2.0 This release

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page