export interface NativeSharedBuffer { readonly fd: number; readonly byte_length: number; get_u8(offset: number): number; set_u8(offset: number, v: number): void; get_u32_le(offset: number): number; set_u32_le(offset: number, v: number): void; get_i32_le(offset: number): number; set_i32_le(offset: number, v: number): void; get_u64_le(offset: number): bigint; set_u64_le(offset: number, v: bigint): void; read_bytes(offset: number, length: number): unknown; write_bytes(offset: number, data: unknown): void; atomic_add_i32(offset: number, v: number): number; atomic_sub_i32(offset: number, v: number): number; atomic_load_i32(offset: number): number; atomic_store_i32(offset: number, v: number): void; atomic_xchg_i32(offset: number, v: number): number; atomic_cmpxchg_i32(offset: number, expected: number, desired: number): number; futex_wait(offset: number, expected: number, timeout_ms: number): number; futex_wake(offset: number, count: number): number; } export interface NativeSharedBufferClass { create(size: number): NativeSharedBuffer | null; from_fd(fd: number, size: number): NativeSharedBuffer | null; } export interface NativeFdChannelClass { make_pair(): [boolean, number, number]; send_fd(socket_fd: number, fd_to_send: number, tag: number): boolean; recv_fd(socket_fd: number): [number, number]; close_fd(fd: number): boolean; } export interface GjsifySabNativeModule { SharedBuffer: NativeSharedBufferClass; FdChannel: NativeFdChannelClass; } /** * Resolve the `GjsifySabNative` GI module, normalising EVERY unavailable case to * `null`: no `gi` at all (not GJS), a throwing property access (typelib not on * `GI_TYPELIB_PATH` — the macOS/Windows case), and a value that is not the module * we expect (stale, partial or shadowed typelib). The third needs the shape check: * a partial value otherwise leaves `hasNativeSab()` reporting `true` and fails with * an opaque `TypeError` at first use. * * @internal exported so a spec can pin the degradation contract in-process * (`shared-buffer.gjs.spec.ts`). */ export declare function resolveNativeSab(gi: Record | undefined): GjsifySabNativeModule | null; /** The native GjsifySabNative module, or null if not installed. */ export declare const nativeSab: GjsifySabNativeModule | null; /** * THE gate for cross-process `SharedBuffer`, and platform-conditional by design: * `true` only on Linux (ADR 0013), `false` on macOS, Windows, Node and the browser. * Guard every use of `SharedBuffer` / `atomics` / `fdChannel` with it. */ export declare function hasNativeSab(): boolean; /** * Thrown by `SharedBuffer.create()` / `.fromFd()` when the native backend is * unavailable. Says "platform-scoped capability", not "broken install". * * @internal exported so specs and docs can assert the exact contract text. */ export declare const NATIVE_SAB_UNAVAILABLE: string; /** * A shared-memory region backed by an anonymous memfd and mmap(MAP_SHARED). The * `.fd` may be passed to a child over a Unix-domain socket via SCM_RIGHTS; the * child mmaps the same fd with `SharedBuffer.fromFd()` to share the backing store. * * Reads and writes are little-endian regardless of host byte-order — the C shim * swaps where needed (s390x / ppc64). * * Lifecycle: the memfd is closed and the mmap freed by the native destructor, i.e. * when this instance is garbage-collected. `.close()` does NOT release the region; * it only drops our reference so later access throws. */ export declare class SharedBuffer { /** @internal */ private _native; private constructor(); /** * Allocate a fresh anonymous shared-memory region. `size` SHOULD be a multiple * of the page size — smaller values work but waste a whole page per region. * * @throws if the prebuild is not loaded, or memfd_create/mmap fail. */ static create(size: number): SharedBuffer; /** * Map an existing shared-memory fd — typically received via SCM_RIGHTS — into * this process. The fd is dup'd, so the caller keeps ownership of their copy. * * `size` MUST match the sender's view of the region: the kernel does not check * it for a memfd, so a mismatch silently produces a partial map. */ static fromFd(fd: number, size: number): SharedBuffer; /** Region size in bytes. */ get byteLength(): number; /** * File descriptor of the backing memfd — hand it to a child via * `Gio.SubprocessLauncher.take_fd()` pre-spawn or SCM_RIGHTS post-spawn. */ get fd(): number; /** True if this region has been released via close(). */ get closed(): boolean; /** * Drop this view of the region. Idempotent. NOT a release: the memfd and mmap go * away with the native GObject's destructor, so this only makes later access * throw rather than segfault. The backing store also survives in any other * process still holding the fd mapped. */ close(): void; getUint8(offset: number): number; setUint8(offset: number, v: number): void; getUint32LE(offset: number): number; setUint32LE(offset: number, v: number): void; getInt32LE(offset: number): number; setInt32LE(offset: number, v: number): void; getBigUint64LE(offset: number): bigint; setBigUint64LE(offset: number, v: bigint): void; /** * Read a byte range as a Uint8Array. ONE-TIME COPY — modifications do NOT * propagate back; use `writeBytes()` to commit changes. */ readBytes(offset: number, length: number): Uint8Array; /** * Identical to `readBytes()`, under a second name because downstream tooling * (`Buffer.from`, `node:crypto`'s `Hash.update`, `fs.writeSync`) duck-types into * it and `viewBytes` reads naturally there. * * Despite the name it is NOT zero-copy, and cannot be made so from here: GJS's * `byteArray.fromGBytes` (`refs/gjs/gjs/byteArray.cpp::from_gbytes_func`) * allocates a fresh `JS::ArrayBuffer` and memcpys into it, deliberately, for * alignment and immutability. A real view needs `JS::NewExternalArrayBuffer` * over the mmap pointer, which needs a `JSContext*` that GJS does not hand to * introspected `.so` plugins — so the fix belongs in GJS (a * `byteArray.fromGBytesShared`). Tracked in status/upstream-patch-candidates.md. */ viewBytes(offset: number, length: number): Uint8Array; /** * The bytes at `[offset, offset+length)` as a `Buffer`, so `writeUInt32LE`, * `subarray`, `toString('hex')` and `createHash().update()` all work — on a * COPY, per `viewBytes()`, so writes do not reach the shared region. * * Needs `globalThis.Buffer` registered, which `--globals auto` does for the * standard CLI bundle; an ad-hoc script needs an explicit * `import '@gjsify/buffer/register'`. * * The return type is generic only to avoid a dependency on `@gjsify/buffer`; the * concrete value is always a `Buffer`, so `toBuffer()` gets the full * surface and the `Uint8Array` default is always safe. */ toBuffer(offset?: number, length?: number): T; /** Write a byte range into the region. memcpy on the C side. */ writeBytes(offset: number, data: Uint8Array): void; /** @internal Internal escape-hatch for atomics + worker_threads transfer. */ get _nativeHandle(): NativeSharedBuffer; private _assertOpen; } /** * Atomic operations against a `SharedBuffer`. Memory order: SEQ_CST. * * The `Atomics.*` built-ins are not an option: GJS does not expose `Atomics` at all * (verified on gjs 1.88.1), and a `SharedBuffer` is not a typed-array view in any * case. This namespace mirrors the common surface over the memfd-backed region. */ export declare const atomics: { /** `[*(int32_t*)(sb+offset)] += v`, returns previous value. */ add32(sb: SharedBuffer, offset: number, v: number): number; sub32(sb: SharedBuffer, offset: number, v: number): number; load32(sb: SharedBuffer, offset: number): number; store32(sb: SharedBuffer, offset: number, v: number): void; exchange32(sb: SharedBuffer, offset: number, v: number): number; /** * Strong compare-and-swap. Returns the previous value. CAS succeeded iff * `returned === expected`. */ compareExchange32(sb: SharedBuffer, offset: number, expected: number, desired: number): number; /** * Linux futex_wait. Compare `*(int32_t*)(sb+offset)` to `expected`; if * equal, block until woken or timeout (0 ms = non-blocking probe; * `-1` ms = infinite). * * Returns: * - `'ok'` — woken by a matching `notify32()` call. * - `'not-equal'` — value didn't match `expected`; no wait happened. * - `'timed-out'` — timeout expired before any wake. * - `'interrupted'` — interrupted by signal (EINTR); caller may retry. */ wait32(sb: SharedBuffer, offset: number, expected: number, timeoutMs: number): "ok" | "not-equal" | "timed-out" | "interrupted"; /** Wake up to `count` waiters on `sb+offset`. Returns number actually woken. */ notify32(sb: SharedBuffer, offset: number, count: number): number; }; /** * Unix-domain socket pair + SCM_RIGHTS fd transfer. * * @internal for `@gjsify/worker_threads` to wire up the cross-process SharedBuffer * transfer at `Worker` spawn time; direct consumers use * `Worker.postMessage(value, [sb])`. */ export declare const fdChannel: { makePair(): { parentFd: number; childFd: number; }; /** * Send one fd over an open SOCK_SEQPACKET pair via SCM_RIGHTS. `false` on * `sendmsg()` failure, with errno left on the calling thread for the caller * to shape into an error. */ sendFd(socketFd: number, fdToSend: number, tag: number): boolean; /** Blocking recv of one fd + tag, or `null` on orderly EOF. */ recvFd(socketFd: number): { fd: number; tag: number; } | null; /** close(2) on any fd. Idempotent: the shim reports success on EBADF too. */ closeFd(fd: number): void; };