@gjsify/sab-native 0.4.13 → 0.4.14

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.
package/lib/esm/index.js CHANGED
@@ -1 +1 @@
1
- import"./_virtual/_rolldown/runtime.js";let e=null;const t=globalThis.imports?.gi;if(t)try{e=t.GjsifySabNative}catch{}const n=e;function hasNativeSab(){return e!==null}const r="@gjsify/sab-native prebuild is not loaded — install the package on a Linux system with the prebuild available, or build locally via `meson compile` in packages/node/sab-native/.";var i=class SharedBuffer{_native;constructor(e){this._native=e}static create(t){if(!e)throw Error(r);if(!Number.isInteger(t)||t<=0)throw TypeError(`SharedBuffer.create: size must be a positive integer`);let n=e.SharedBuffer.create(t);if(!n)throw Error(`memfd_create or mmap failed`);return new SharedBuffer(n)}static fromFd(t,n){if(!e)throw Error(r);if(!Number.isInteger(t)||t<0)throw TypeError(`SharedBuffer.fromFd: fd must be a non-negative integer`);if(!Number.isInteger(n)||n<=0)throw TypeError(`SharedBuffer.fromFd: size must be a positive integer`);let i=e.SharedBuffer.from_fd(t,n);if(!i)throw Error(`mmap failed`);return new SharedBuffer(i)}get byteLength(){return this._assertOpen().byte_length}get fd(){return this._assertOpen().fd}get closed(){return this._native===null}close(){this._native=null}getUint8(e){return this._assertOpen().get_u8(e)}setUint8(e,t){this._assertOpen().set_u8(e,t&255)}getUint32LE(e){return this._assertOpen().get_u32_le(e)}setUint32LE(e,t){this._assertOpen().set_u32_le(e,t>>>0)}getInt32LE(e){return this._assertOpen().get_i32_le(e)}setInt32LE(e,t){this._assertOpen().set_i32_le(e,t|0)}getBigUint64LE(e){return this._assertOpen().get_u64_le(e)}setBigUint64LE(e,t){this._assertOpen().set_u64_le(e,t)}readBytes(e,t){let n=this._assertOpen().read_bytes(e,t),r=globalThis.imports?.byteArray;if(r&&typeof r.fromGBytes==`function`){let e=r.fromGBytes(n);return new Uint8Array(e)}return new Uint8Array(n.toArray())}writeBytes(e,t){let n=globalThis.imports?.byteArray,r;if(n&&typeof n.toGBytes==`function`)r=n.toGBytes(t);else{let e=globalThis.imports?.gi?.GLib;r=e?.Bytes?new e.Bytes(t):t}this._assertOpen().write_bytes(e,r)}get _nativeHandle(){return this._assertOpen()}_assertOpen(){if(!this._native)throw Error(`SharedBuffer has been closed`);return this._native}};const a={add32(e,t,n){return e._nativeHandle.atomic_add_i32(t,n|0)},sub32(e,t,n){return e._nativeHandle.atomic_sub_i32(t,n|0)},load32(e,t){return e._nativeHandle.atomic_load_i32(t)},store32(e,t,n){e._nativeHandle.atomic_store_i32(t,n|0)},exchange32(e,t,n){return e._nativeHandle.atomic_xchg_i32(t,n|0)},compareExchange32(e,t,n,r){return e._nativeHandle.atomic_cmpxchg_i32(t,n|0,r|0)},wait32(e,t,n,r){let i=e._nativeHandle.futex_wait(t,n|0,r|0);if(i===0)return`ok`;if(i===-1)return`not-equal`;if(i===-2)return`timed-out`;if(i===-3)return`interrupted`;throw Error(`futex_wait returned errno ${-i}`)},notify32(e,t,n){return e._nativeHandle.futex_wake(t,n|0)}},o=e?{makePair(){let[t,n,r]=e.FdChannel.make_pair();if(!t)throw Error(`socketpair() failed`);return{parentFd:n,childFd:r}},sendFd(t,n,r){return e.FdChannel.send_fd(t,n,r>>>0)},recvFd(t){let[n,r]=e.FdChannel.recv_fd(t);if(n===0)return null;if(n<0)throw Error(`recvmsg failed`);return{fd:n,tag:r>>>0}},closeFd(t){e.FdChannel.close_fd(t)}}:null;export{i as SharedBuffer,a as atomics,o as fdChannel,hasNativeSab,n as nativeSab};
1
+ import"./_virtual/_rolldown/runtime.js";let e=null;const t=globalThis.imports?.gi;if(t)try{e=t.GjsifySabNative}catch{}const n=e;function hasNativeSab(){return e!==null}const r="@gjsify/sab-native prebuild is not loaded — install the package on a Linux system with the prebuild available, or build locally via `meson compile` in packages/node/sab-native/.";var i=class SharedBuffer{_native;constructor(e){this._native=e}static create(t){if(!e)throw Error(r);if(!Number.isInteger(t)||t<=0)throw TypeError(`SharedBuffer.create: size must be a positive integer`);let n=e.SharedBuffer.create(t);if(!n)throw Error(`memfd_create or mmap failed`);return new SharedBuffer(n)}static fromFd(t,n){if(!e)throw Error(r);if(!Number.isInteger(t)||t<0)throw TypeError(`SharedBuffer.fromFd: fd must be a non-negative integer`);if(!Number.isInteger(n)||n<=0)throw TypeError(`SharedBuffer.fromFd: size must be a positive integer`);let i=e.SharedBuffer.from_fd(t,n);if(!i)throw Error(`mmap failed`);return new SharedBuffer(i)}get byteLength(){return this._assertOpen().byte_length}get fd(){return this._assertOpen().fd}get closed(){return this._native===null}close(){this._native=null}getUint8(e){return this._assertOpen().get_u8(e)}setUint8(e,t){this._assertOpen().set_u8(e,t&255)}getUint32LE(e){return this._assertOpen().get_u32_le(e)}setUint32LE(e,t){this._assertOpen().set_u32_le(e,t>>>0)}getInt32LE(e){return this._assertOpen().get_i32_le(e)}setInt32LE(e,t){this._assertOpen().set_i32_le(e,t|0)}getBigUint64LE(e){return this._assertOpen().get_u64_le(e)}setBigUint64LE(e,t){this._assertOpen().set_u64_le(e,t)}readBytes(e,t){let n=this._assertOpen().read_bytes(e,t),r=globalThis.imports?.byteArray;if(r&&typeof r.fromGBytes==`function`){let e=r.fromGBytes(n);return new Uint8Array(e)}return new Uint8Array(n.toArray())}viewBytes(e,t){return this.readBytes(e,t)}toBuffer(e=0,t){let n=t??this.byteLength-e,r=this.viewBytes(e,n),i=globalThis.Buffer;if(typeof i?.from!=`function`)throw Error(`SharedBuffer.toBuffer: globalThis.Buffer is not registered. Import "@gjsify/buffer/register" or rely on --globals auto.`);return i.from(r.buffer,r.byteOffset,r.byteLength)}writeBytes(e,t){let n=globalThis.imports?.byteArray,r;if(n&&typeof n.toGBytes==`function`)r=n.toGBytes(t);else{let e=globalThis.imports?.gi?.GLib;r=e?.Bytes?new e.Bytes(t):t}this._assertOpen().write_bytes(e,r)}get _nativeHandle(){return this._assertOpen()}_assertOpen(){if(!this._native)throw Error(`SharedBuffer has been closed`);return this._native}};const a={add32(e,t,n){return e._nativeHandle.atomic_add_i32(t,n|0)},sub32(e,t,n){return e._nativeHandle.atomic_sub_i32(t,n|0)},load32(e,t){return e._nativeHandle.atomic_load_i32(t)},store32(e,t,n){e._nativeHandle.atomic_store_i32(t,n|0)},exchange32(e,t,n){return e._nativeHandle.atomic_xchg_i32(t,n|0)},compareExchange32(e,t,n,r){return e._nativeHandle.atomic_cmpxchg_i32(t,n|0,r|0)},wait32(e,t,n,r){let i=e._nativeHandle.futex_wait(t,n|0,r|0);if(i===0)return`ok`;if(i===-1)return`not-equal`;if(i===-2)return`timed-out`;if(i===-3)return`interrupted`;throw Error(`futex_wait returned errno ${-i}`)},notify32(e,t,n){return e._nativeHandle.futex_wake(t,n|0)}},o=e?{makePair(){let[t,n,r]=e.FdChannel.make_pair();if(!t)throw Error(`socketpair() failed`);return{parentFd:n,childFd:r}},sendFd(t,n,r){return e.FdChannel.send_fd(t,n,r>>>0)},recvFd(t){let[n,r]=e.FdChannel.recv_fd(t);if(n===0)return null;if(n<0)throw Error(`recvmsg failed`);return{fd:n,tag:r>>>0}},closeFd(t){e.FdChannel.close_fd(t)}}:null;export{i as SharedBuffer,a as atomics,o as fdChannel,hasNativeSab,n as nativeSab};
@@ -105,9 +105,59 @@ export declare class SharedBuffer {
105
105
  /**
106
106
  * Read a byte range out as a Uint8Array. ONE-TIME COPY — modifications
107
107
  * to the returned array do NOT propagate back to the region. Use
108
- * `writeBytes()` to commit changes back.
108
+ * `writeBytes()` to commit changes back, or `viewBytes()` for a
109
+ * zero-copy view.
109
110
  */
110
111
  readBytes(offset: number, length: number): Uint8Array;
112
+ /**
113
+ * Return a fresh `Uint8Array` containing the bytes at `[offset, offset+length)`.
114
+ * Same semantics as `readBytes()` — kept under this name because
115
+ * downstream tooling (`Buffer.from`, `node:crypto`'s `Hash.update`,
116
+ * `fs.writeSync`) calls into this method via duck-typing and the
117
+ * `viewBytes` name reads more naturally there.
118
+ *
119
+ * **NOT zero-copy in current GJS.** GJS's `byteArray.fromGBytes`
120
+ * (`refs/gjs/gjs/byteArray.cpp::from_gbytes_func`) allocates a fresh
121
+ * `JS::ArrayBuffer` and memcpy's the GBytes data into it — by design,
122
+ * for alignment + immutability reasons.
123
+ *
124
+ * **Why no internal fix is possible**: a true zero-copy view would need
125
+ * `JS::NewExternalArrayBuffer` against the mmap pointer, but that JSAPI
126
+ * call requires a `JSContext*` which GJS does not expose to
127
+ * GObject-introspected `.so` plugins. The fix has to land in GJS
128
+ * itself (e.g. a `byteArray.fromGBytesShared` helper). Tracked under
129
+ * STATUS.md "Upstream GJS Patch Candidates".
130
+ *
131
+ * Modifications to the returned array therefore do NOT propagate back
132
+ * to the region — use `writeBytes()` to commit changes.
133
+ *
134
+ * @throws Error if `byteArray.fromGBytes` is not available (GJS < 1.66).
135
+ */
136
+ viewBytes(offset: number, length: number): Uint8Array;
137
+ /**
138
+ * Return a `Buffer` containing the bytes at `[offset, offset+length)`.
139
+ * The Buffer is a fresh allocation (see `viewBytes()` for the "not
140
+ * zero-copy in current GJS" caveat — the limitation is in GJS'
141
+ * `byteArray.fromGBytes`, not bypassable from a `.so` plugin without
142
+ * upstream patching GJS) — `buf.writeUInt32LE(...)`, `buf.subarray(...)`,
143
+ * `buf.toString('hex')`, `createHash().update(buf)` all work, but
144
+ * writes do NOT propagate back to the shared region.
145
+ *
146
+ * Requires `globalThis.Buffer` to be registered (via
147
+ * `@gjsify/buffer/register`) — otherwise throws. Consumers running
148
+ * under the standard gjsify CLI bundle have Buffer registered via
149
+ * `--globals auto`; for ad-hoc scripts, add an explicit
150
+ * `import '@gjsify/buffer/register'` at the entry point.
151
+ *
152
+ * Return type is generic to avoid an import dependency on
153
+ * `@gjsify/buffer` — the concrete return is a `Buffer` (subclass of
154
+ * `Uint8Array`). Default `T = Uint8Array` is always safe;
155
+ * `toBuffer<Buffer>()` gets the full Buffer surface.
156
+ *
157
+ * @param offset byte offset into the shared region. Defaults to 0.
158
+ * @param length byte length. Defaults to `byteLength - offset`.
159
+ */
160
+ toBuffer<T extends Uint8Array = Uint8Array>(offset?: number, length?: number): T;
111
161
  /**
112
162
  * Write a byte range into the region. memcpy on the C side.
113
163
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/sab-native",
3
- "version": "0.4.13",
3
+ "version": "0.4.14",
4
4
  "description": "Optional Vala/GObject bridge providing cross-process shared memory + atomics for @gjsify/worker_threads. Wraps Linux memfd_create(2) + mmap(MAP_SHARED) to back JS-visible SharedBuffer regions whose fds can be passed to child workers via SCM_RIGHTS over a Unix-domain socket; exposes typed accessors (getInt32LE / setUint8 / etc.) plus atomics built on __atomic_* GCC builtins and SYS_futex for wait/notify. Workaround for the stock-GJS gap where SharedArrayBuffer is unavailable (Mozilla disables the SAB constructor without COOP/COEP opts) and SAB cannot share backing stores across Gio.Subprocess workers anyway.",
5
5
  "type": "module",
6
6
  "main": "lib/esm/index.js",
@@ -56,8 +56,8 @@
56
56
  "@girs/gio-2.0": "2.88.0-4.0.0-rc.15"
57
57
  },
58
58
  "devDependencies": {
59
- "@gjsify/cli": "^0.4.13",
60
- "@gjsify/unit": "^0.4.13",
59
+ "@gjsify/cli": "^0.4.14",
60
+ "@gjsify/unit": "^0.4.14",
61
61
  "@ts-for-gir/cli": "^4.0.0-rc.15",
62
62
  "@types/node": "^25.6.2",
63
63
  "typescript": "^6.0.3"