concurrent-c-node 0.12.0__tar.gz → 0.14.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: concurrent-c-node
3
- Version: 0.12.0
3
+ Version: 0.14.0
4
4
  Summary: JavaScript and npm packages from Python over the Concurrent-C bridge: one spawned Node child per domain, host-controlled lifetime.
5
5
  License: MIT
6
6
  Project-URL: Repository, https://github.com/sreekotay/concurrent-c
@@ -99,10 +99,12 @@ Examples ship in the wheel. Same domain model and materialization
99
99
  rules as the npm sibling, pointed the other way:
100
100
 
101
101
  - **Values**: plain data (finite numbers, strings, booleans, `None`,
102
- lists/dicts of the same) crosses by value; everything else is a live
103
- handle owned by the domain attribute access is property lookup
104
- (methods arrive bound), calls are calls, `str()` is `String()`.
105
- Non-finite floats cross tagged, never silently nulled.
102
+ lists and non-empty dicts/objects of the same) crosses by value; an
103
+ empty `{}` stays a live handle (so bags you mint in JS keep property
104
+ access). Everything else is a live handle owned by the domain —
105
+ attribute access is property lookup (methods arrive bound), calls are
106
+ calls, `str()` is `String()`. Non-finite floats cross tagged, never
107
+ silently nulled.
106
108
  - **The domain rules hold**: handles never cross bridges; `stats()` is
107
109
  the handle ledger and `release()` drops one early; `destroy()` is
108
110
  idempotent, every door answers `bridge is closed` after, and the
@@ -161,6 +163,35 @@ memcpy per side, the receiver consumes the spill file, and the sender
161
163
  sweeps it if the child died first. Nothing strays, and nothing is
162
164
  silently truncated: an unsupported type is an articulate error.
163
165
 
166
+ ## Common issues
167
+
168
+ **`Cannot find module '…'`.** `require` / `import` resolve from the
169
+ **Python process cwd** (`node_modules` next to your program), not from
170
+ the site-packages install of this wheel. `npm install lodash` in the
171
+ project directory is the fix; or pass `create(node='/path/to/node')` /
172
+ `CC_NODE_BIN` when the wrong Node is on `PATH`. Missing-module errors
173
+ name that cwd rule.
174
+
175
+ **Empty `{}` is a live handle.** `js.eval('({})')` stays a `JsHandle`
176
+ so later property use matches Node. Non-empty plain objects still cross
177
+ as Python `dict`s (data returns). Same-domain handles chain
178
+ (`h.update(…).digest(…)`); foreign-domain handles do not.
179
+
180
+ **Thenables are awaited in the child.** Promise-based npm APIs need no
181
+ `async`/`await` on the Python side — the call blocks until settle (or
182
+ raises `JsError` on reject). That is the opposite of
183
+ `concurrent-c-python`'s isolated surface, where every call is already a
184
+ JS Promise you must await.
185
+
186
+ **Wire cost vs tiny work.** Round trip is ~100µs class; a one-line JS
187
+ helper on three numbers loses to pure Python. Prefer Python (or a native
188
+ CC module) for small/hot work; use the bridge when Node/npm owns the
189
+ kernel (crypto, parsers, large buffers via shm).
190
+
191
+ **Crash isolation, not a sandbox.** The child inherits your environment
192
+ and privileges — do not evaluate untrusted JavaScript. `destroy()` is
193
+ cooperative; CPU-bound JS is not preemptible (wait or kill + new domain).
194
+
164
195
  ## Choosing the node
165
196
 
166
197
  Same ambient-first rule as the rest of the family: the domain runs
@@ -88,10 +88,12 @@ Examples ship in the wheel. Same domain model and materialization
88
88
  rules as the npm sibling, pointed the other way:
89
89
 
90
90
  - **Values**: plain data (finite numbers, strings, booleans, `None`,
91
- lists/dicts of the same) crosses by value; everything else is a live
92
- handle owned by the domain attribute access is property lookup
93
- (methods arrive bound), calls are calls, `str()` is `String()`.
94
- Non-finite floats cross tagged, never silently nulled.
91
+ lists and non-empty dicts/objects of the same) crosses by value; an
92
+ empty `{}` stays a live handle (so bags you mint in JS keep property
93
+ access). Everything else is a live handle owned by the domain —
94
+ attribute access is property lookup (methods arrive bound), calls are
95
+ calls, `str()` is `String()`. Non-finite floats cross tagged, never
96
+ silently nulled.
95
97
  - **The domain rules hold**: handles never cross bridges; `stats()` is
96
98
  the handle ledger and `release()` drops one early; `destroy()` is
97
99
  idempotent, every door answers `bridge is closed` after, and the
@@ -150,6 +152,35 @@ memcpy per side, the receiver consumes the spill file, and the sender
150
152
  sweeps it if the child died first. Nothing strays, and nothing is
151
153
  silently truncated: an unsupported type is an articulate error.
152
154
 
155
+ ## Common issues
156
+
157
+ **`Cannot find module '…'`.** `require` / `import` resolve from the
158
+ **Python process cwd** (`node_modules` next to your program), not from
159
+ the site-packages install of this wheel. `npm install lodash` in the
160
+ project directory is the fix; or pass `create(node='/path/to/node')` /
161
+ `CC_NODE_BIN` when the wrong Node is on `PATH`. Missing-module errors
162
+ name that cwd rule.
163
+
164
+ **Empty `{}` is a live handle.** `js.eval('({})')` stays a `JsHandle`
165
+ so later property use matches Node. Non-empty plain objects still cross
166
+ as Python `dict`s (data returns). Same-domain handles chain
167
+ (`h.update(…).digest(…)`); foreign-domain handles do not.
168
+
169
+ **Thenables are awaited in the child.** Promise-based npm APIs need no
170
+ `async`/`await` on the Python side — the call blocks until settle (or
171
+ raises `JsError` on reject). That is the opposite of
172
+ `concurrent-c-python`'s isolated surface, where every call is already a
173
+ JS Promise you must await.
174
+
175
+ **Wire cost vs tiny work.** Round trip is ~100µs class; a one-line JS
176
+ helper on three numbers loses to pure Python. Prefer Python (or a native
177
+ CC module) for small/hot work; use the bridge when Node/npm owns the
178
+ kernel (crypto, parsers, large buffers via shm).
179
+
180
+ **Crash isolation, not a sandbox.** The child inherits your environment
181
+ and privileges — do not evaluate untrusted JavaScript. `destroy()` is
182
+ cooperative; CPU-bound JS is not preemptible (wait or kill + new domain).
183
+
153
184
  ## Choosing the node
154
185
 
155
186
  Same ambient-first rule as the rest of the family: the domain runs
@@ -9,10 +9,11 @@
9
9
  The mirror of the cc-python bridge, same rules pointed the other way:
10
10
  attribute access is property lookup (methods arrive bound), a call is a
11
11
  call, plain data (finite numbers, strings, booleans, None, lists and
12
- dicts of the same) crosses by value and everything else stays a live
13
- handle owned by the domain. A thenable result is awaited in the child
14
- before the reply, so async package APIs work with nothing extra. A
15
- Python callable passed as an argument becomes a JS function; its
12
+ non-empty dicts of the same) crosses by value; an empty dict/object
13
+ stays a live handle (bags keep property access). Everything else stays
14
+ a live handle owned by the domain. A thenable result is awaited in the
15
+ child before the reply, so async package APIs work with nothing extra.
16
+ A Python callable passed as an argument becomes a JS function; its
16
17
  exceptions cross back as JS errors and vice versa, messages intact.
17
18
  Handles never cross domains; every door after destroy() answers
18
19
  articulately; destroy is idempotent and `with cc_node.create() as js:`
@@ -29,7 +30,7 @@ import subprocess
29
30
  import tempfile
30
31
 
31
32
  __all__ = ["create", "JsError", "JsHandle", "__version__"]
32
- __version__ = "0.4.0"
33
+ __version__ = "0.12.1"
33
34
 
34
35
  # Typed buffers cross as typed arrays; big ones spill through shared
35
36
  # memory (tmpfs where available) — one memcpy per side, receiver
@@ -304,7 +305,12 @@ class Bridge:
304
305
  enc = self._encode_buffer(a)
305
306
  if enc is not None:
306
307
  return enc
307
- raise JsError("cc-node: unsupported argument type: %r" % type(a))
308
+ raise JsError(
309
+ "cc-node: unsupported argument type: %r "
310
+ "(numbers, str, bool, None, list/tuple, dict with str keys, "
311
+ "bytes/array.array/1-D numpy, callables, or this domain's "
312
+ "JsHandle — same-domain handles chain)"
313
+ % (type(a),))
308
314
 
309
315
  def _encode_buffer(self, a):
310
316
  # Typed buffers cross as typed arrays: bytes/array.array/1-D
@@ -9,13 +9,15 @@
9
9
  * numbering) says which via CC_WIRE_IN / CC_WIRE_OUT. Handles are
10
10
  * integers into one table; results follow the bridge materialization
11
11
  * rule — plain data (finite numbers, strings, booleans, null, arrays
12
- * and plain objects of the same) crosses as a value, everything else
13
- * stays a handle. A thenable result is awaited before the reply, so
14
- * async package APIs need nothing special from the Python side. A
15
- * Python callable crosses as {$f: id}; invoking it sends a nested `cb`
16
- * request and BLOCKS on a synchronous read for the answer — legal
17
- * because the protocol is strictly alternating, so nothing else can be
18
- * in flight. EOF on the request fd is the host vanishing: exit.
12
+ * and non-empty plain objects of the same) crosses as a value; an
13
+ * empty plain object stays a handle (so `eval('({})')` remains a live
14
+ * JS object materializing it to Python `{}` dropped property access).
15
+ * A thenable result is awaited before the reply, so async package APIs
16
+ * need nothing special from the Python side. A Python callable crosses
17
+ * as {$f: id}; invoking it sends a nested `cb` request and BLOCKS on a
18
+ * synchronous read for the answer legal because the protocol is
19
+ * strictly alternating, so nothing else can be in flight. EOF on the
20
+ * request fd is the host vanishing: exit.
19
21
  *
20
22
  * cc/include/ccc/script/js.cch embeds this file verbatim (the CC
21
23
  * isolated tier speaks the same wire); js_iso_smoke pins the two
@@ -151,6 +153,14 @@ function encodeResult(v) {
151
153
  return encodeBuffer(kind,
152
154
  Buffer.from(v.buffer, v.byteOffset, v.byteLength));
153
155
  if (Buffer.isBuffer(v)) return encodeBuffer('u8', v);
156
+ // Empty plain {} / Object.create(null) stay handles — callers mint
157
+ // bags for later property use. Non-empty plain objects still cross
158
+ // by value (data returns).
159
+ const proto = Object.getPrototypeOf(v);
160
+ if (!Array.isArray(v) &&
161
+ (proto === Object.prototype || proto === null) &&
162
+ Object.keys(v).length === 0)
163
+ return { h: put(v) };
154
164
  }
155
165
  if (v === null || isPlain(v, 0)) return { v };
156
166
  return { h: put(v) };
@@ -215,8 +225,34 @@ async function main() {
215
225
  try {
216
226
  let r;
217
227
  switch (req.op) {
218
- case 'require': r = requireCwd(req.name); break;
219
- case 'import': r = await import(req.name); break;
228
+ case 'require':
229
+ try {
230
+ r = requireCwd(req.name);
231
+ } catch (e) {
232
+ if (e && e.code === 'MODULE_NOT_FOUND') {
233
+ throw new Error(
234
+ String(e.message) +
235
+ ' — npm install into this working directory\'s ' +
236
+ 'node_modules (require resolves from cwd), or pass ' +
237
+ 'create(node=...) for a different Node');
238
+ }
239
+ throw e;
240
+ }
241
+ break;
242
+ case 'import':
243
+ try {
244
+ r = await import(req.name);
245
+ } catch (e) {
246
+ const msg = String(e && e.message !== undefined ? e.message : e);
247
+ if (/Cannot find module|ERR_MODULE_NOT_FOUND/i.test(msg)) {
248
+ throw new Error(
249
+ msg +
250
+ ' — install the package for this Node (cwd node_modules ' +
251
+ 'or a path import), or pass create(node=...)');
252
+ }
253
+ throw e;
254
+ }
255
+ break;
220
256
  case 'eval': r = (0, eval)(req.src); break;
221
257
  case 'get': {
222
258
  const o = getH(req.h);
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: concurrent-c-node
3
- Version: 0.12.0
3
+ Version: 0.14.0
4
4
  Summary: JavaScript and npm packages from Python over the Concurrent-C bridge: one spawned Node child per domain, host-controlled lifetime.
5
5
  License: MIT
6
6
  Project-URL: Repository, https://github.com/sreekotay/concurrent-c
@@ -99,10 +99,12 @@ Examples ship in the wheel. Same domain model and materialization
99
99
  rules as the npm sibling, pointed the other way:
100
100
 
101
101
  - **Values**: plain data (finite numbers, strings, booleans, `None`,
102
- lists/dicts of the same) crosses by value; everything else is a live
103
- handle owned by the domain attribute access is property lookup
104
- (methods arrive bound), calls are calls, `str()` is `String()`.
105
- Non-finite floats cross tagged, never silently nulled.
102
+ lists and non-empty dicts/objects of the same) crosses by value; an
103
+ empty `{}` stays a live handle (so bags you mint in JS keep property
104
+ access). Everything else is a live handle owned by the domain —
105
+ attribute access is property lookup (methods arrive bound), calls are
106
+ calls, `str()` is `String()`. Non-finite floats cross tagged, never
107
+ silently nulled.
106
108
  - **The domain rules hold**: handles never cross bridges; `stats()` is
107
109
  the handle ledger and `release()` drops one early; `destroy()` is
108
110
  idempotent, every door answers `bridge is closed` after, and the
@@ -161,6 +163,35 @@ memcpy per side, the receiver consumes the spill file, and the sender
161
163
  sweeps it if the child died first. Nothing strays, and nothing is
162
164
  silently truncated: an unsupported type is an articulate error.
163
165
 
166
+ ## Common issues
167
+
168
+ **`Cannot find module '…'`.** `require` / `import` resolve from the
169
+ **Python process cwd** (`node_modules` next to your program), not from
170
+ the site-packages install of this wheel. `npm install lodash` in the
171
+ project directory is the fix; or pass `create(node='/path/to/node')` /
172
+ `CC_NODE_BIN` when the wrong Node is on `PATH`. Missing-module errors
173
+ name that cwd rule.
174
+
175
+ **Empty `{}` is a live handle.** `js.eval('({})')` stays a `JsHandle`
176
+ so later property use matches Node. Non-empty plain objects still cross
177
+ as Python `dict`s (data returns). Same-domain handles chain
178
+ (`h.update(…).digest(…)`); foreign-domain handles do not.
179
+
180
+ **Thenables are awaited in the child.** Promise-based npm APIs need no
181
+ `async`/`await` on the Python side — the call blocks until settle (or
182
+ raises `JsError` on reject). That is the opposite of
183
+ `concurrent-c-python`'s isolated surface, where every call is already a
184
+ JS Promise you must await.
185
+
186
+ **Wire cost vs tiny work.** Round trip is ~100µs class; a one-line JS
187
+ helper on three numbers loses to pure Python. Prefer Python (or a native
188
+ CC module) for small/hot work; use the bridge when Node/npm owns the
189
+ kernel (crypto, parsers, large buffers via shm).
190
+
191
+ **Crash isolation, not a sandbox.** The child inherits your environment
192
+ and privileges — do not evaluate untrusted JavaScript. `destroy()` is
193
+ cooperative; CPU-bound JS is not preemptible (wait or kill + new domain).
194
+
164
195
  ## Choosing the node
165
196
 
166
197
  Same ambient-first rule as the rest of the family: the domain runs
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "concurrent-c-node"
7
- version = "0.12.0"
7
+ version = "0.14.0"
8
8
  description = "JavaScript and npm packages from Python over the Concurrent-C bridge: one spawned Node child per domain, host-controlled lifetime."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"