rust-store

Node.js / Python parity

The problem

You ship the same product as a Node.js service and a Python service. If the two services each carry their own query, permission and computed-column logic, they will drift — one fixes a pagination bug, the other keeps it; one honours a field-level permission, the other forgets. Users see two different products behind the same API.

Why rust-store

There is exactly one implementation of the semantics: the Rust core. The Node binding (rust-store-node, napi-rs) and the Python binding (rust-store-py, PyO3) are thin bridges that convert JSON in and JSON out. They add no behaviour, so the two languages cannot disagree about what a query means.

The only surface difference is naming: camelCase on the Node side, snake_case on the Python side. Everything else — command JSON, error semantics, computed-column flow — is identical.

Walkthrough

The same registry and the same query, on both sides:

// Node.js: rust-store-node (camelCase)
const { Registry, systemContext } = require('rust-store-node');

const reg = new Registry();
reg.register({ name: 'User', collection: 'users', fields: { name: { type: 'string' } }, relations: {} });

const plan = reg.planQuery('User{name}', {}, null); // → MongoDB command JSON
console.log(JSON.stringify(plan));
# Python: rust-store-py (snake_case)
from rust_store_py import Registry

reg = Registry()
reg.register({"name": "User", "collection": "users", "fields": {"name": {"type": "string"}}, "relations": {}})

plan = reg.plan_query("User{name}", {}, None)  # → MongoDB command JSON (dict)
print(plan)

Both print the same command JSON. The names line up one-to-one:

Node (camelCase) Python (snake_case)
planQuery plan_query
planQueryOne / planQueryWithCount plan_query_one / plan_query_with_count
planFederated / mergeFederated plan_federated / merge_federated
dialectTranslate / restoreRows dialect_translate / restore_rows
setFn / clearFns set_fn / clear_fns
systemContext() (module-level) system_context() (module-level)

Parameters are positional and correspond across the two bindings (cross-language parity is prioritised over parameter count). One important semantic asymmetry: Python raises every error as a PyErr exception; it is never mixed into the returned dict.

The parity suites prove the equivalence continuously:

# 1) core: pure Rust, all semantics live here
cargo test -p rust-store-core        # core/tests/parity*.rs
#    parity, parity_computes, parity_commands, parity_write,
#    parity_fnfns, parity_dialect, parity_federation

# 2) Node binding
cd core-node && npm ci && npx napi build --platform && npm test
#    test/parity.test.js, test/dialect.smoke.test.js, test/t2q.skill.test.js

# 3) Python binding
cd core-py && pip install maturin pytest && maturin build --out dist && pip install --force-reinstall dist/*.whl
cd .. && python -m pytest core-py/test/parity_test.py -v

# 4) golden fixtures, recomputed on all three sides
node tools/verify-fixtures.js

Why semantics cannot drift:

Pitfalls

See also