Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@thi.ng/wasm-api

Package Overview
Dependencies
Maintainers
1
Versions
132
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@thi.ng/wasm-api - npm Package Compare versions

Comparing version 1.4.36 to 1.4.37

8

api.js

@@ -1,2 +0,6 @@

export const EVENT_MEMORY_CHANGED = "memory-changed";
export const EVENT_PANIC = "panic";
const EVENT_MEMORY_CHANGED = "memory-changed";
const EVENT_PANIC = "panic";
export {
EVENT_MEMORY_CHANGED,
EVENT_PANIC
};

@@ -1,2 +0,12 @@

import { __decorate } from "tslib";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __decorateClass = (decorators, target, key, kind) => {
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
for (var i = decorators.length - 1, decorator; i >= 0; i--)
if (decorator = decorators[i])
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
if (kind && result)
__defProp(target, key, result);
return result;
};
import { INotifyMixin } from "@thi.ng/api/mixins/inotify";

@@ -8,413 +18,430 @@ import { topoSort } from "@thi.ng/arrays/topo-sort";

import { ConsoleLogger } from "@thi.ng/logger/console";
import { EVENT_MEMORY_CHANGED, EVENT_PANIC, } from "./api.js";
export const Panic = defError(() => "Panic");
export const OutOfMemoryError = defError(() => "Out of memory");
/**
* The main interop API bridge between the JS host environment and a WebAssembly
* module. This class provides a small core API with various typed accessors and
* utils to exchange data (scalars, arrays, strings etc.) via the WASM module's
* memory.
*
* @remarks
* All typed memory accessors are assuming the given lookup addresses are
* properly aligned to the corresponding primitive types (e.g. f32 values are
* aligned to 4 byte boundaries, f64 to 8 bytes etc.) Unaligned access is
* explicitly **not supported**! If you need such, please refer to other
* mechanisms like JS `DataView`...
*
* 64bit integers are handled via JS `BigInt` and hence require the host env to
* support it. No polyfills are provided.
*/
let WasmBridge = class WasmBridge {
logger;
id = "wasmapi";
i8;
u8;
i16;
u16;
i32;
u32;
i64;
u64;
f32;
f64;
utf8Decoder = new TextDecoder();
utf8Encoder = new TextEncoder();
imports;
exports;
api;
modules;
constructor(modules = [], logger = new ConsoleLogger("wasm")) {
this.logger = logger;
const logN = (x) => this.logger.debug(x);
const logA = (method) => (addr, len) => this.logger.debug(() => method(addr, len).join(", "));
this.api = {
printI8: logN,
printU8: logN,
printI16: logN,
printU16: logN,
printI32: logN,
printU32: (x) => this.logger.debug(x >>> 0),
printI64: (x) => this.logger.debug(x),
printU64: (x) => this.logger.debug(x),
printF32: logN,
printF64: logN,
printU8Hex: (x) => this.logger.debug(() => `0x${U8(x)}`),
printU16Hex: (x) => this.logger.debug(() => `0x${U16(x)}`),
printU32Hex: (x) => this.logger.debug(() => `0x${U32(x)}`),
printU64Hex: (x) => this.logger.debug(() => `0x${U64BIG(x)}`),
printHexdump: (addr, len) => {
this.ensureMemory();
for (let line of hexdumpLines(this.u8, addr, len)) {
this.logger.debug(line);
}
},
_printI8Array: logA(this.getI8Array.bind(this)),
_printU8Array: logA(this.getU8Array.bind(this)),
_printI16Array: logA(this.getI16Array.bind(this)),
_printU16Array: logA(this.getU16Array.bind(this)),
_printI32Array: logA(this.getI32Array.bind(this)),
_printU32Array: logA(this.getU32Array.bind(this)),
_printI64Array: logA(this.getI64Array.bind(this)),
_printU64Array: logA(this.getU64Array.bind(this)),
_printF32Array: logA(this.getF32Array.bind(this)),
_printF64Array: logA(this.getF64Array.bind(this)),
printStrZ: (addr) => this.logger.debug(() => this.getString(addr, 0)),
_printStr: (addr, len) => this.logger.debug(() => this.getString(addr, len)),
debug: () => {
debugger;
},
_panic: (addr, len) => {
const msg = this.getString(addr, len);
if (!this.notify({ id: EVENT_PANIC, value: msg })) {
throw new Panic(msg);
}
},
timer: () => performance.now(),
epoch: () => BigInt(Date.now()),
};
this.modules = modules.reduce((acc, x) => {
assert(acc[x.id] === undefined && x.id !== this.id, `duplicate API module ID: ${x.id}`);
acc[x.id] = x;
return acc;
}, {});
}
/**
* Instantiates WASM module from given `src` (and optional provided extra
* imports), then automatically calls {@link WasmBridge.init} with the
* modules exports.
*
* @remarks
* If the given `src` is a `Response` or `Promise<Response>`, the module
* will be instantiated via `WebAssembly.instantiateStreaming()`, otherwise
* the non-streaming version will be used.
*
* @param src
* @param imports
*/
async instantiate(src, imports) {
const $src = await src;
const $imports = { ...this.getImports(), ...imports };
const wasm = await ($src instanceof Response
? WebAssembly.instantiateStreaming($src, $imports)
: WebAssembly.instantiate($src, $imports));
return this.init(wasm.instance.exports);
}
/**
* Receives the WASM module's combined exports, stores them for future
* reference and then initializes all declared bridge child API modules in
* their stated dependency order. Returns false if any of the module
* initializations failed.
*
* @remarks
* Emits the {@link EVENT_MEMORY_CHANGED} event just before returning (and
* AFTER all child API modules have been initialized).
*
* @param exports
*/
async init(exports) {
this.exports = exports;
this.ensureMemory(false);
for (let id of topoSort(this.modules, (module) => module.dependencies)) {
assert(!!this.modules[id], `missing API module: ${id}`);
this.logger.debug(`initializing API module: ${id}`);
const status = await this.modules[id].init(this);
if (!status)
return false;
import {
EVENT_MEMORY_CHANGED,
EVENT_PANIC
} from "./api.js";
const Panic = defError(() => "Panic");
const OutOfMemoryError = defError(() => "Out of memory");
let WasmBridge = class {
constructor(modules = [], logger = new ConsoleLogger("wasm")) {
this.logger = logger;
const logN = (x) => this.logger.debug(x);
const logA = (method) => (addr, len) => this.logger.debug(() => method(addr, len).join(", "));
this.api = {
printI8: logN,
printU8: logN,
printI16: logN,
printU16: logN,
printI32: logN,
printU32: (x) => this.logger.debug(x >>> 0),
printI64: (x) => this.logger.debug(x),
printU64: (x) => this.logger.debug(x),
printF32: logN,
printF64: logN,
printU8Hex: (x) => this.logger.debug(() => `0x${U8(x)}`),
printU16Hex: (x) => this.logger.debug(() => `0x${U16(x)}`),
printU32Hex: (x) => this.logger.debug(() => `0x${U32(x)}`),
printU64Hex: (x) => this.logger.debug(() => `0x${U64BIG(x)}`),
printHexdump: (addr, len) => {
this.ensureMemory();
for (let line of hexdumpLines(this.u8, addr, len)) {
this.logger.debug(line);
}
this.notify({ id: EVENT_MEMORY_CHANGED, value: this.exports.memory });
return true;
}
/**
* Called automatically during initialization and from other memory
* accessors. Initializes and/or updates the various typed WASM memory views
* (e.g. after growing the WASM memory and the previous buffer becoming
* detached). Unless `notify` is false, the {@link EVENT_MEMORY_CHANGED}
* event will be emitted if the memory views had to be updated.
*
* @param notify
*/
ensureMemory(notify = true) {
const buf = this.exports.memory.buffer;
if (this.u8 && this.u8.buffer === buf)
return;
this.i8 = new Int8Array(buf);
this.u8 = new Uint8Array(buf);
this.i16 = new Int16Array(buf);
this.u16 = new Uint16Array(buf);
this.i32 = new Int32Array(buf);
this.u32 = new Uint32Array(buf);
this.i64 = new BigInt64Array(buf);
this.u64 = new BigUint64Array(buf);
this.f32 = new Float32Array(buf);
this.f64 = new Float64Array(buf);
notify &&
this.notify({
id: EVENT_MEMORY_CHANGED,
value: this.exports.memory,
});
}
/**
* Required use for WASM module instantiation to provide JS imports to the
* module. Returns an object of all WASM imports declared by the bridge core
* API and any provided bridge API modules.
*
* @remarks
* Each API module's imports will be in their own WASM import object/table,
* named using the same key which is defined by the JS side of the module
* via {@link IWasmAPI.id}. The bridge's core API is named `wasmapi` and is
* reserved.
*
* @example
* The following creates a bridge with a fictional `custom` API module:
*
* ```ts
* const bridge = new WasmBridge([new CustomAPI()]);
*
* // get combined imports object
* bridge.getImports();
* {
* // imports defined by the core API of the bridge itself
* wasmapi: { ... },
* // imports defined by the CustomAPI module
* custom: { ... }
* }
* ```
*
* Any related API bindings on the WASM (Zig) side then also need to refer
* to these custom import sections (also see `/zig/core.zig`):
*
* ```zig
* pub export "custom" fn foo(x: u32) void;
* ```
*/
getImports() {
if (!this.imports) {
this.imports = { [this.id]: this.api };
for (let id in this.modules) {
this.imports[id] = this.modules[id].getImports();
}
},
_printI8Array: logA(this.getI8Array.bind(this)),
_printU8Array: logA(this.getU8Array.bind(this)),
_printI16Array: logA(this.getI16Array.bind(this)),
_printU16Array: logA(this.getU16Array.bind(this)),
_printI32Array: logA(this.getI32Array.bind(this)),
_printU32Array: logA(this.getU32Array.bind(this)),
_printI64Array: logA(this.getI64Array.bind(this)),
_printU64Array: logA(this.getU64Array.bind(this)),
_printF32Array: logA(this.getF32Array.bind(this)),
_printF64Array: logA(this.getF64Array.bind(this)),
printStrZ: (addr) => this.logger.debug(() => this.getString(addr, 0)),
_printStr: (addr, len) => this.logger.debug(() => this.getString(addr, len)),
debug: () => {
debugger;
},
_panic: (addr, len) => {
const msg = this.getString(addr, len);
if (!this.notify({ id: EVENT_PANIC, value: msg })) {
throw new Panic(msg);
}
return this.imports;
},
timer: () => performance.now(),
epoch: () => BigInt(Date.now())
};
this.modules = modules.reduce((acc, x) => {
assert(
acc[x.id] === void 0 && x.id !== this.id,
`duplicate API module ID: ${x.id}`
);
acc[x.id] = x;
return acc;
}, {});
}
id = "wasmapi";
i8;
u8;
i16;
u16;
i32;
u32;
i64;
u64;
f32;
f64;
utf8Decoder = new TextDecoder();
utf8Encoder = new TextEncoder();
imports;
exports;
api;
modules;
/**
* Instantiates WASM module from given `src` (and optional provided extra
* imports), then automatically calls {@link WasmBridge.init} with the
* modules exports.
*
* @remarks
* If the given `src` is a `Response` or `Promise<Response>`, the module
* will be instantiated via `WebAssembly.instantiateStreaming()`, otherwise
* the non-streaming version will be used.
*
* @param src
* @param imports
*/
async instantiate(src, imports) {
const $src = await src;
const $imports = { ...this.getImports(), ...imports };
const wasm = await ($src instanceof Response ? WebAssembly.instantiateStreaming($src, $imports) : WebAssembly.instantiate($src, $imports));
return this.init(wasm.instance.exports);
}
/**
* Receives the WASM module's combined exports, stores them for future
* reference and then initializes all declared bridge child API modules in
* their stated dependency order. Returns false if any of the module
* initializations failed.
*
* @remarks
* Emits the {@link EVENT_MEMORY_CHANGED} event just before returning (and
* AFTER all child API modules have been initialized).
*
* @param exports
*/
async init(exports) {
this.exports = exports;
this.ensureMemory(false);
for (let id of topoSort(
this.modules,
(module) => module.dependencies
)) {
assert(!!this.modules[id], `missing API module: ${id}`);
this.logger.debug(`initializing API module: ${id}`);
const status = await this.modules[id].init(this);
if (!status)
return false;
}
growMemory(numPages) {
this.exports.memory.grow(numPages);
this.ensureMemory();
this.notify({ id: EVENT_MEMORY_CHANGED, value: this.exports.memory });
return true;
}
/**
* Called automatically during initialization and from other memory
* accessors. Initializes and/or updates the various typed WASM memory views
* (e.g. after growing the WASM memory and the previous buffer becoming
* detached). Unless `notify` is false, the {@link EVENT_MEMORY_CHANGED}
* event will be emitted if the memory views had to be updated.
*
* @param notify
*/
ensureMemory(notify = true) {
const buf = this.exports.memory.buffer;
if (this.u8 && this.u8.buffer === buf)
return;
this.i8 = new Int8Array(buf);
this.u8 = new Uint8Array(buf);
this.i16 = new Int16Array(buf);
this.u16 = new Uint16Array(buf);
this.i32 = new Int32Array(buf);
this.u32 = new Uint32Array(buf);
this.i64 = new BigInt64Array(buf);
this.u64 = new BigUint64Array(buf);
this.f32 = new Float32Array(buf);
this.f64 = new Float64Array(buf);
notify && this.notify({
id: EVENT_MEMORY_CHANGED,
value: this.exports.memory
});
}
/**
* Required use for WASM module instantiation to provide JS imports to the
* module. Returns an object of all WASM imports declared by the bridge core
* API and any provided bridge API modules.
*
* @remarks
* Each API module's imports will be in their own WASM import object/table,
* named using the same key which is defined by the JS side of the module
* via {@link IWasmAPI.id}. The bridge's core API is named `wasmapi` and is
* reserved.
*
* @example
* The following creates a bridge with a fictional `custom` API module:
*
* ```ts
* const bridge = new WasmBridge([new CustomAPI()]);
*
* // get combined imports object
* bridge.getImports();
* {
* // imports defined by the core API of the bridge itself
* wasmapi: { ... },
* // imports defined by the CustomAPI module
* custom: { ... }
* }
* ```
*
* Any related API bindings on the WASM (Zig) side then also need to refer
* to these custom import sections (also see `/zig/core.zig`):
*
* ```zig
* pub export "custom" fn foo(x: u32) void;
* ```
*/
getImports() {
if (!this.imports) {
this.imports = { [this.id]: this.api };
for (let id in this.modules) {
this.imports[id] = this.modules[id].getImports();
}
}
allocate(numBytes, clear = false) {
const addr = this.exports._wasm_allocate(numBytes);
if (!addr)
throw new OutOfMemoryError(`unable to allocate: ${numBytes}`);
this.logger.fine(() => `allocated ${numBytes} bytes @ 0x${U32(addr)} .. 0x${U32(addr + numBytes - 1)}`);
this.ensureMemory();
clear && this.u8.fill(0, addr, addr + numBytes);
return [addr, numBytes];
return this.imports;
}
growMemory(numPages) {
this.exports.memory.grow(numPages);
this.ensureMemory();
}
allocate(numBytes, clear = false) {
const addr = this.exports._wasm_allocate(numBytes);
if (!addr)
throw new OutOfMemoryError(`unable to allocate: ${numBytes}`);
this.logger.fine(
() => `allocated ${numBytes} bytes @ 0x${U32(addr)} .. 0x${U32(
addr + numBytes - 1
)}`
);
this.ensureMemory();
clear && this.u8.fill(0, addr, addr + numBytes);
return [addr, numBytes];
}
free([addr, numBytes]) {
this.logger.fine(
() => `freeing memory @ 0x${U32(addr)} .. 0x${U32(
addr + numBytes - 1
)}`
);
this.exports._wasm_free(addr, numBytes);
}
getI8(addr) {
return this.i8[addr];
}
getU8(addr) {
return this.u8[addr];
}
getI16(addr) {
return this.i16[addr >> 1];
}
getU16(addr) {
return this.u16[addr >> 1];
}
getI32(addr) {
return this.i32[addr >> 2];
}
getU32(addr) {
return this.u32[addr >> 2];
}
getI64(addr) {
return this.i64[addr >> 3];
}
getU64(addr) {
return this.u64[addr >> 3];
}
getF32(addr) {
return this.f32[addr >> 2];
}
getF64(addr) {
return this.f64[addr >> 3];
}
setI8(addr, x) {
this.i8[addr] = x;
return this;
}
setU8(addr, x) {
this.u8[addr] = x;
return this;
}
setI16(addr, x) {
this.i16[addr >> 1] = x;
return this;
}
setU16(addr, x) {
this.u16[addr >> 1] = x;
return this;
}
setI32(addr, x) {
this.i32[addr >> 2] = x;
return this;
}
setU32(addr, x) {
this.u32[addr >> 2] = x;
return this;
}
setI64(addr, x) {
this.i64[addr >> 3] = x;
return this;
}
setU64(addr, x) {
this.u64[addr >> 3] = x;
return this;
}
setF32(addr, x) {
this.f32[addr >> 2] = x;
return this;
}
setF64(addr, x) {
this.f64[addr >> 3] = x;
return this;
}
getI8Array(addr, len) {
return this.i8.subarray(addr, addr + len);
}
getU8Array(addr, len) {
return this.u8.subarray(addr, addr + len);
}
getI16Array(addr, len) {
addr >>= 1;
return this.i16.subarray(addr, addr + len);
}
getU16Array(addr, len) {
addr >>= 1;
return this.u16.subarray(addr, addr + len);
}
getI32Array(addr, len) {
addr >>= 2;
return this.i32.subarray(addr, addr + len);
}
getU32Array(addr, len) {
addr >>= 2;
return this.u32.subarray(addr, addr + len);
}
getI64Array(addr, len) {
addr >>= 3;
return this.i64.subarray(addr, addr + len);
}
getU64Array(addr, len) {
addr >>= 3;
return this.u64.subarray(addr, addr + len);
}
getF32Array(addr, len) {
addr >>= 2;
return this.f32.subarray(addr, addr + len);
}
getF64Array(addr, len) {
addr >>= 3;
return this.f64.subarray(addr, addr + len);
}
setI8Array(addr, buf) {
this.i8.set(buf, addr);
return this;
}
setU8Array(addr, buf) {
this.u8.set(buf, addr);
return this;
}
setI16Array(addr, buf) {
this.i16.set(buf, addr >> 1);
return this;
}
setU16Array(addr, buf) {
this.u16.set(buf, addr >> 1);
return this;
}
setI32Array(addr, buf) {
this.i32.set(buf, addr >> 2);
return this;
}
setU32Array(addr, buf) {
this.u32.set(buf, addr >> 2);
return this;
}
setI64Array(addr, buf) {
this.i64.set(buf, addr >> 3);
return this;
}
setU64Array(addr, buf) {
this.u64.set(buf, addr >> 3);
return this;
}
setF32Array(addr, buf) {
this.f32.set(buf, addr >> 2);
return this;
}
setF64Array(addr, buf) {
this.f64.set(buf, addr >> 3);
return this;
}
getString(addr, len = 0) {
this.ensureMemory();
return this.utf8Decoder.decode(
this.u8.subarray(
addr,
len > 0 ? addr + len : this.u8.indexOf(0, addr)
)
);
}
setString(str, addr, maxBytes, terminate = true) {
this.ensureMemory();
maxBytes = Math.min(maxBytes, this.u8.length - addr);
const len = this.utf8Encoder.encodeInto(
str,
this.u8.subarray(addr, addr + maxBytes)
).written;
assert(
len != null && len < maxBytes + (terminate ? 0 : 1),
`error writing string to 0x${U32(
addr
)} (max. ${maxBytes} bytes, got at least ${str.length})`
);
if (terminate) {
this.u8[addr + len] = 0;
}
free([addr, numBytes]) {
this.logger.fine(() => `freeing memory @ 0x${U32(addr)} .. 0x${U32(addr + numBytes - 1)}`);
this.exports._wasm_free(addr, numBytes);
}
getI8(addr) {
return this.i8[addr];
}
getU8(addr) {
return this.u8[addr];
}
getI16(addr) {
return this.i16[addr >> 1];
}
getU16(addr) {
return this.u16[addr >> 1];
}
getI32(addr) {
return this.i32[addr >> 2];
}
getU32(addr) {
return this.u32[addr >> 2];
}
getI64(addr) {
return this.i64[addr >> 3];
}
getU64(addr) {
return this.u64[addr >> 3];
}
getF32(addr) {
return this.f32[addr >> 2];
}
getF64(addr) {
return this.f64[addr >> 3];
}
setI8(addr, x) {
this.i8[addr] = x;
return this;
}
setU8(addr, x) {
this.u8[addr] = x;
return this;
}
setI16(addr, x) {
this.i16[addr >> 1] = x;
return this;
}
setU16(addr, x) {
this.u16[addr >> 1] = x;
return this;
}
setI32(addr, x) {
this.i32[addr >> 2] = x;
return this;
}
setU32(addr, x) {
this.u32[addr >> 2] = x;
return this;
}
setI64(addr, x) {
this.i64[addr >> 3] = x;
return this;
}
setU64(addr, x) {
this.u64[addr >> 3] = x;
return this;
}
setF32(addr, x) {
this.f32[addr >> 2] = x;
return this;
}
setF64(addr, x) {
this.f64[addr >> 3] = x;
return this;
}
getI8Array(addr, len) {
return this.i8.subarray(addr, addr + len);
}
getU8Array(addr, len) {
return this.u8.subarray(addr, addr + len);
}
getI16Array(addr, len) {
addr >>= 1;
return this.i16.subarray(addr, addr + len);
}
getU16Array(addr, len) {
addr >>= 1;
return this.u16.subarray(addr, addr + len);
}
getI32Array(addr, len) {
addr >>= 2;
return this.i32.subarray(addr, addr + len);
}
getU32Array(addr, len) {
addr >>= 2;
return this.u32.subarray(addr, addr + len);
}
getI64Array(addr, len) {
addr >>= 3;
return this.i64.subarray(addr, addr + len);
}
getU64Array(addr, len) {
addr >>= 3;
return this.u64.subarray(addr, addr + len);
}
getF32Array(addr, len) {
addr >>= 2;
return this.f32.subarray(addr, addr + len);
}
getF64Array(addr, len) {
addr >>= 3;
return this.f64.subarray(addr, addr + len);
}
setI8Array(addr, buf) {
this.i8.set(buf, addr);
return this;
}
setU8Array(addr, buf) {
this.u8.set(buf, addr);
return this;
}
setI16Array(addr, buf) {
this.i16.set(buf, addr >> 1);
return this;
}
setU16Array(addr, buf) {
this.u16.set(buf, addr >> 1);
return this;
}
setI32Array(addr, buf) {
this.i32.set(buf, addr >> 2);
return this;
}
setU32Array(addr, buf) {
this.u32.set(buf, addr >> 2);
return this;
}
setI64Array(addr, buf) {
this.i64.set(buf, addr >> 3);
return this;
}
setU64Array(addr, buf) {
this.u64.set(buf, addr >> 3);
return this;
}
setF32Array(addr, buf) {
this.f32.set(buf, addr >> 2);
return this;
}
setF64Array(addr, buf) {
this.f64.set(buf, addr >> 3);
return this;
}
getString(addr, len = 0) {
this.ensureMemory();
return this.utf8Decoder.decode(this.u8.subarray(addr, len > 0 ? addr + len : this.u8.indexOf(0, addr)));
}
setString(str, addr, maxBytes, terminate = true) {
this.ensureMemory();
maxBytes = Math.min(maxBytes, this.u8.length - addr);
const len = this.utf8Encoder.encodeInto(str, this.u8.subarray(addr, addr + maxBytes)).written;
assert(len != null && len < maxBytes + (terminate ? 0 : 1), `error writing string to 0x${U32(addr)} (max. ${maxBytes} bytes, got at least ${str.length})`);
if (terminate) {
this.u8[addr + len] = 0;
}
return len;
}
getElementById(addr, len = 0) {
const id = this.getString(addr, len);
const el = document.getElementById(id);
assert(!!el, `missing DOM element #${id}`);
return el;
}
/** {@inheritDoc @thi.ng/api#INotify.addListener} */
// @ts-ignore: mixin
// prettier-ignore
addListener(id, fn, scope) { }
/** {@inheritDoc @thi.ng/api#INotify.removeListener} */
// @ts-ignore: mixin
// prettier-ignore
removeListener(id, fn, scope) { }
/** {@inheritDoc @thi.ng/api#INotify.notify} */
// @ts-ignore: mixin
notify(event) { }
return len;
}
getElementById(addr, len = 0) {
const id = this.getString(addr, len);
const el = document.getElementById(id);
assert(!!el, `missing DOM element #${id}`);
return el;
}
/** {@inheritDoc @thi.ng/api#INotify.addListener} */
// @ts-ignore: mixin
// prettier-ignore
addListener(id, fn, scope) {
}
/** {@inheritDoc @thi.ng/api#INotify.removeListener} */
// @ts-ignore: mixin
// prettier-ignore
removeListener(id, fn, scope) {
}
/** {@inheritDoc @thi.ng/api#INotify.notify} */
// @ts-ignore: mixin
notify(event) {
}
};
WasmBridge = __decorate([
INotifyMixin
WasmBridge = __decorateClass([
INotifyMixin
], WasmBridge);
export { WasmBridge };
export {
OutOfMemoryError,
Panic,
WasmBridge
};
# Change Log
- **Last updated**: 2023-12-09T19:12:04Z
- **Last updated**: 2023-12-11T10:07:09Z
- **Generator**: [thi.ng/monopub](https://thi.ng/monopub)

@@ -5,0 +5,0 @@

import { assert } from "@thi.ng/errors/assert";
import { IDGen } from "@thi.ng/idgen";
export class ObjectIndex {
name;
logger;
idgen;
items = [];
constructor(opts) {
this.name = opts.name;
this.logger = opts.logger;
this.idgen = new IDGen(opts.bits || 32, 0);
class ObjectIndex {
name;
logger;
idgen;
items = [];
constructor(opts) {
this.name = opts.name;
this.logger = opts.logger;
this.idgen = new IDGen(opts.bits || 32, 0);
}
keys() {
return this.idgen[Symbol.iterator]();
}
*values() {
for (let id of this.idgen) {
yield this.items[id];
}
keys() {
return this.idgen[Symbol.iterator]();
}
/**
* Indexes given `item` and assigns it to the next available ID (which might
* be a previously freed ID) and returns it.
*
* @param item
*/
add(item) {
const id = this.idgen.next();
this.logger && this.logger.debug(`adding ${this.name} ID: ${id}`);
this.items[id] = item;
return id;
}
/**
* Returns true if the given `id` is valid/active.
*
* @param id
*/
has(id) {
return this.idgen.has(id);
}
/**
* First checks if given `id` is valid and if so frees it (for recycling)
* and deletes its corresponding item. If `ensure` is true (default), throws
* an error if the ID is invalid (otherwise returns false for invalid IDs).
*
* @param id
* @param ensure
*/
delete(id, ensure = true) {
if (this.idgen.has(id)) {
this.logger && this.logger.debug(`deleting ${this.name} ID: ${id}`);
this.idgen.free(id);
delete this.items[id];
return true;
}
*values() {
for (let id of this.idgen) {
yield this.items[id];
}
}
/**
* Indexes given `item` and assigns it to the next available ID (which might
* be a previously freed ID) and returns it.
*
* @param item
*/
add(item) {
const id = this.idgen.next();
this.logger && this.logger.debug(`adding ${this.name} ID: ${id}`);
this.items[id] = item;
assert(!ensure, `can't delete missing ${this.name} ID: ${id}`);
return false;
}
get(id, ensure = true) {
ensure && assert(this.idgen.has(id), `missing ${this.name} for ID: ${id}`);
return this.items[id];
}
/**
* Applies given predicate to all active items and returns ID of first
* matching. If `ensure` is true (default), throws an error if the `pred`
* didn't match anything (otherwise returns undefined).
*
* @param pred
* @param ensure
*/
find(pred, ensure = true) {
for (let id of this.idgen) {
if (pred(this.items[id]))
return id;
}
/**
* Returns true if the given `id` is valid/active.
*
* @param id
*/
has(id) {
return this.idgen.has(id);
}
/**
* First checks if given `id` is valid and if so frees it (for recycling)
* and deletes its corresponding item. If `ensure` is true (default), throws
* an error if the ID is invalid (otherwise returns false for invalid IDs).
*
* @param id
* @param ensure
*/
delete(id, ensure = true) {
if (this.idgen.has(id)) {
this.logger && this.logger.debug(`deleting ${this.name} ID: ${id}`);
this.idgen.free(id);
delete this.items[id];
return true;
}
assert(!ensure, `can't delete missing ${this.name} ID: ${id}`);
return false;
}
get(id, ensure = true) {
ensure &&
assert(this.idgen.has(id), `missing ${this.name} for ID: ${id}`);
return this.items[id];
}
/**
* Applies given predicate to all active items and returns ID of first
* matching. If `ensure` is true (default), throws an error if the `pred`
* didn't match anything (otherwise returns undefined).
*
* @param pred
* @param ensure
*/
find(pred, ensure = true) {
for (let id of this.idgen) {
if (pred(this.items[id]))
return id;
}
assert(!ensure, `given predicate matched no ${this.name}`);
}
assert(!ensure, `given predicate matched no ${this.name}`);
}
}
export {
ObjectIndex
};
{
"name": "@thi.ng/wasm-api",
"version": "1.4.36",
"version": "1.4.37",
"description": "Generic, modular, extensible API bridge and infrastructure for hybrid JS & WebAssembly projects",

@@ -27,3 +27,5 @@ "type": "module",

"scripts": {
"build": "yarn clean && tsc --declaration",
"build": "yarn build:esbuild && yarn build:decl",
"build:decl": "tsc --declaration --emitDeclarationOnly",
"build:esbuild": "esbuild --format=esm --platform=neutral --target=es2022 --tsconfig=tsconfig.json --outdir=. src/**/*.ts",
"clean": "rimraf --glob '*.js' '*.d.ts' '*.map' doc",

@@ -38,12 +40,13 @@ "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts",

"dependencies": {
"@thi.ng/api": "^8.9.11",
"@thi.ng/arrays": "^2.7.7",
"@thi.ng/checks": "^3.4.11",
"@thi.ng/errors": "^2.4.5",
"@thi.ng/hex": "^2.3.23",
"@thi.ng/idgen": "^2.2.14",
"@thi.ng/logger": "^2.0.1"
"@thi.ng/api": "^8.9.12",
"@thi.ng/arrays": "^2.7.8",
"@thi.ng/checks": "^3.4.12",
"@thi.ng/errors": "^2.4.6",
"@thi.ng/hex": "^2.3.24",
"@thi.ng/idgen": "^2.2.15",
"@thi.ng/logger": "^2.0.2"
},
"devDependencies": {
"@microsoft/api-extractor": "^7.38.3",
"esbuild": "^0.19.8",
"rimraf": "^5.0.5",

@@ -118,3 +121,3 @@ "tools": "^0.0.1",

},
"gitHead": "25f2ac8ff795a432a930119661b364d4d93b59a0\n"
"gitHead": "5e7bafedfc3d53bc131469a28de31dd8e5b4a3ff\n"
}

@@ -1,60 +0,36 @@

/**
* Generic pointer facility which on {@link Pointer.deref()} calls wrapper
* function (provided as ctor arg) to realise the pointer's target value. The
* pointer's target address can be accessed via {@link Pointer.addr}
* (read/write).
*
* @remarks
* The pointer always behaves like `volatile`, i.e. memoization of target values
* is purposfully avoided and the wrapper function is executed anew _each_ time
* the pointer is deref'd.
*/
export class Pointer {
mem;
base;
fn;
constructor(mem, base, fn) {
this.mem = mem;
this.base = base;
this.fn = fn;
}
get addr() {
return this.mem.u32[this.base >>> 2];
}
set addr(addr) {
this.mem.u32[this.base >>> 2] = addr;
}
deref() {
return this.fn(this.addr);
}
class Pointer {
constructor(mem, base, fn) {
this.mem = mem;
this.base = base;
this.fn = fn;
}
get addr() {
return this.mem.u32[this.base >>> 2];
}
set addr(addr) {
this.mem.u32[this.base >>> 2] = addr;
}
deref() {
return this.fn(this.addr);
}
}
/**
* Generic pointer facility for {@link WASM64} target, which on
* {@link Pointer64.deref()} calls wrapper function (provided as ctor arg) to
* realise the pointer's target value. The pointer's target address can be
* accessed via {@link Pointer.addr} (read/write).
*
* @remarks
* The pointer always behaves like `volatile`, i.e. memoization of target values
* is purposfully avoided and the wrapper function is executed anew _each_ time
* the pointer is deref'd.
*/
export class Pointer64 {
mem;
base;
fn;
constructor(mem, base, fn) {
this.mem = mem;
this.base = base;
this.fn = fn;
}
get addr() {
return this.mem.u64[Number(this.base >> BigInt(3))];
}
set addr(addr) {
this.mem.u64[Number(this.base >> BigInt(3))] = addr;
}
deref() {
return this.fn(this.addr);
}
class Pointer64 {
constructor(mem, base, fn) {
this.mem = mem;
this.base = base;
this.fn = fn;
}
get addr() {
return this.mem.u64[Number(this.base >> BigInt(3))];
}
set addr(addr) {
this.mem.u64[Number(this.base >> BigInt(3))] = addr;
}
deref() {
return this.fn(this.addr);
}
}
export {
Pointer,
Pointer64
};

@@ -394,3 +394,3 @@ <!-- This file is generated - DO NOT EDIT! -->

Package sizes (brotli'd, pre-treeshake): ESM: 2.65 KB
Package sizes (brotli'd, pre-treeshake): ESM: 2.69 KB

@@ -397,0 +397,0 @@ ## Dependencies

import { isNumber } from "@thi.ng/checks/is-number";
import { unsupported } from "@thi.ng/errors/unsupported";
/**
* Memory mapped string wrapper for Zig-style UTF-8 encoded byte slices (aka
* pointer & length pair). The actual JS string can be obtained via
* {@link WasmStringSlice.deref} and possibly mutated via
* {@link WasmStringSlice.set}.
*
* @remarks
* Currently only supports wasm32 target, need alt. solution for 64bit (possibly
* diff implementation) using bigint addresses (TODO)
*/
export class WasmStringSlice {
mem;
base;
isConst;
terminated;
maxLen;
constructor(mem, base, isConst = true, terminated = true) {
this.mem = mem;
this.base = base;
this.isConst = isConst;
this.terminated = terminated;
this.maxLen = this.length;
class WasmStringSlice {
constructor(mem, base, isConst = true, terminated = true) {
this.mem = mem;
this.base = base;
this.isConst = isConst;
this.terminated = terminated;
this.maxLen = this.length;
}
maxLen;
/**
* Returns string start address (deref'd pointer).
*/
get addr() {
this.mem.ensureMemory();
return this.mem.u32[this.base >>> 2];
}
/**
* Returns string length (read from memory)
*/
get length() {
this.mem.ensureMemory();
return this.mem.u32[this.base + 4 >>> 2];
}
/**
* Returns memory as JS string (aka wrapper for
* {@link WasmBridge.getString}).
*/
deref() {
return this.mem.getString(this.addr, this.length);
}
/**
* If given a JS string as arg (and if **not** a const slice), attempts to
* overwrite this wrapped string's memory with bytes from given string. If
* given another {@link WasmStringSlice} as arg, only the slice pointer &
* new length will be updated (always succeeds).
*
* @remarks
* When copying bytes from a JS string, an error will be thrown if the new
* string is longer than the _original_ length of the slice (i.e. from when
* this `WasmStringSlice` wrapper instance was created). Also updates the
* slice's length field to new string length.
*
* Passing a `WasmString` instance as arg is faster than JS string since
* only the slice definition itself will be updated.
*
* @param str
*/
set(str) {
this.mem.ensureMemory();
if (typeof str === "string") {
if (this.isConst)
unsupported("can't mutate const string");
this.mem.u32[this.base + 4 >>> 2] = this.mem.setString(
str,
this.addr,
this.maxLen + ~~this.terminated,
this.terminated
);
} else {
this.mem.u32[this.base >>> 2] = str.addr;
this.mem.u32[this.base + 4 >>> 2] = str.length;
}
/**
* Returns string start address (deref'd pointer).
*/
get addr() {
this.mem.ensureMemory();
return this.mem.u32[this.base >>> 2];
}
/**
* Returns string length (read from memory)
*/
get length() {
this.mem.ensureMemory();
return this.mem.u32[(this.base + 4) >>> 2];
}
/**
* Returns memory as JS string (aka wrapper for
* {@link WasmBridge.getString}).
*/
deref() {
return this.mem.getString(this.addr, this.length);
}
/**
* If given a JS string as arg (and if **not** a const slice), attempts to
* overwrite this wrapped string's memory with bytes from given string. If
* given another {@link WasmStringSlice} as arg, only the slice pointer &
* new length will be updated (always succeeds).
*
* @remarks
* When copying bytes from a JS string, an error will be thrown if the new
* string is longer than the _original_ length of the slice (i.e. from when
* this `WasmStringSlice` wrapper instance was created). Also updates the
* slice's length field to new string length.
*
* Passing a `WasmString` instance as arg is faster than JS string since
* only the slice definition itself will be updated.
*
* @param str
*/
set(str) {
this.mem.ensureMemory();
if (typeof str === "string") {
if (this.isConst)
unsupported("can't mutate const string");
this.mem.u32[(this.base + 4) >>> 2] = this.mem.setString(str, this.addr, this.maxLen + ~~this.terminated, this.terminated);
}
else {
this.mem.u32[this.base >>> 2] = str.addr;
this.mem.u32[(this.base + 4) >>> 2] = str.length;
}
}
setSlice(...args) {
this.mem.ensureMemory();
const [slice, terminated] = ((isNumber(args[0])
? [[args[0], args[1]], args[2]]
: [args[0], args[1]]));
this.mem.u32[this.base >>> 2] = slice[0];
this.mem.u32[(this.base + 4) >>> 2] = slice[1];
this.terminated = terminated;
return slice;
}
/**
* Encodes given string to UTF-8 (by default zero terminated), allocates
* memory for it, updates this slice and returns a {@link MemorySlice} of
* the allocated region.
*
* @remarks
* If `terminated` is true, the stored slice length will **NOT** include the
* sentinel! E.g. the slice length of zero-terminated string `"abc"` is 3,
* but the number of allocated bytes is 4. This is done for compatibility
* with Zig's sentinel-terminated slice handling (e.g. `[:0]u8` slices).
*
* Regardless of `terminated` setting, the returned `MemorySlice` **always**
* covers the entire allocated region!
*
* @param str
* @param terminate
*/
setAlloc(str, terminate = true) {
const slice = __alloc(this.mem, str, terminate);
this.setSlice(terminate ? [slice[0], slice[1] - 1] : slice, terminate);
return slice;
}
toJSON() {
return this.deref();
}
toString() {
return this.deref();
}
valueOf() {
return this.deref();
}
}
setSlice(...args) {
this.mem.ensureMemory();
const [slice, terminated] = isNumber(args[0]) ? [[args[0], args[1]], args[2]] : [args[0], args[1]];
this.mem.u32[this.base >>> 2] = slice[0];
this.mem.u32[this.base + 4 >>> 2] = slice[1];
this.terminated = terminated;
return slice;
}
/**
* Encodes given string to UTF-8 (by default zero terminated), allocates
* memory for it, updates this slice and returns a {@link MemorySlice} of
* the allocated region.
*
* @remarks
* If `terminated` is true, the stored slice length will **NOT** include the
* sentinel! E.g. the slice length of zero-terminated string `"abc"` is 3,
* but the number of allocated bytes is 4. This is done for compatibility
* with Zig's sentinel-terminated slice handling (e.g. `[:0]u8` slices).
*
* Regardless of `terminated` setting, the returned `MemorySlice` **always**
* covers the entire allocated region!
*
* @param str
* @param terminate
*/
setAlloc(str, terminate = true) {
const slice = __alloc(this.mem, str, terminate);
this.setSlice(terminate ? [slice[0], slice[1] - 1] : slice, terminate);
return slice;
}
toJSON() {
return this.deref();
}
toString() {
return this.deref();
}
valueOf() {
return this.deref();
}
}
/**
* Memory mapped string wrapper for C-style UTF-8 encoded and **always**
* zero-terminated char pointers. The actual JS string can be obtained via
* {@link WasmStringSlice.deref} and mutated via {@link WasmStringSlice.set}.
*/
export class WasmStringPtr {
mem;
base;
isConst;
constructor(mem, base, isConst = true) {
this.mem = mem;
this.base = base;
this.isConst = isConst;
class WasmStringPtr {
constructor(mem, base, isConst = true) {
this.mem = mem;
this.base = base;
this.isConst = isConst;
}
/**
* Returns string start address (deref'd pointer).
*/
get addr() {
this.mem.ensureMemory();
return this.mem.u32[this.base >>> 2];
}
set addr(addr) {
this.mem.ensureMemory();
this.mem.u32[this.base >>> 2] = addr;
}
/**
* Returns computed string length (scanning memory for zero sentinel)
*/
get length() {
this.mem.ensureMemory();
const idx = this.mem.u8.indexOf(0, this.addr);
return idx >= 0 ? idx - this.addr : 0;
}
/**
* Returns memory as JS string (aka wrapper for
* {@link WasmBridge.getString}).
*/
deref() {
return this.mem.getString(this.addr, this.length);
}
/**
* If given a JS string as arg (and if this `WasmStringPtr` instance itself
* is not a `const` pointer), attempts to overwrite this wrapped string's
* memory with bytes from given string. If given another
* {@link WasmStringPtr}, it merely overrides the pointer to the new one
* (always succeeds).
*
* @remarks
* Unlike with {@link WasmStringSlice.set} this implementation which
* performs bounds checking when copying bytes from a JS string, this method
* only throws an error if the new string is longer than the available
* memory (from the start address until the end of the WASM memory).
* **Therefore, this is as (un)safe as a C pointer and should be used with
* caution!**
*
* Passing a `WasmStringPtr` instance as arg is faster than JS string since
* only the pointer itself will be updated.
*
* @param str
*/
set(str) {
const addr = this.addr;
if (typeof str === "string") {
if (this.isConst)
unsupported("can't mutate const string");
this.mem.ensureMemory();
this.mem.setString(str, addr, this.mem.u8.byteLength - addr, true);
} else {
this.addr = str.addr;
this.isConst = str.isConst;
}
/**
* Returns string start address (deref'd pointer).
*/
get addr() {
this.mem.ensureMemory();
return this.mem.u32[this.base >>> 2];
}
set addr(addr) {
this.mem.ensureMemory();
this.mem.u32[this.base >>> 2] = addr;
}
/**
* Returns computed string length (scanning memory for zero sentinel)
*/
get length() {
this.mem.ensureMemory();
const idx = this.mem.u8.indexOf(0, this.addr);
return idx >= 0 ? idx - this.addr : 0;
}
/**
* Returns memory as JS string (aka wrapper for
* {@link WasmBridge.getString}).
*/
deref() {
return this.mem.getString(this.addr, this.length);
}
/**
* If given a JS string as arg (and if this `WasmStringPtr` instance itself
* is not a `const` pointer), attempts to overwrite this wrapped string's
* memory with bytes from given string. If given another
* {@link WasmStringPtr}, it merely overrides the pointer to the new one
* (always succeeds).
*
* @remarks
* Unlike with {@link WasmStringSlice.set} this implementation which
* performs bounds checking when copying bytes from a JS string, this method
* only throws an error if the new string is longer than the available
* memory (from the start address until the end of the WASM memory).
* **Therefore, this is as (un)safe as a C pointer and should be used with
* caution!**
*
* Passing a `WasmStringPtr` instance as arg is faster than JS string since
* only the pointer itself will be updated.
*
* @param str
*/
set(str) {
const addr = this.addr;
if (typeof str === "string") {
if (this.isConst)
unsupported("can't mutate const string");
this.mem.ensureMemory();
this.mem.setString(str, addr, this.mem.u8.byteLength - addr, true);
}
else {
this.addr = str.addr;
this.isConst = str.isConst;
}
}
/**
* Encodes given string to UTF-8 (by default zero terminated), allocates
* memory for it, updates this pointer to new address and returns allocated
* {@link MemorySlice}.
*
* @remarks
* See {@link WasmStringSlice.setAlloc} for important details.
*
* @param str
*/
setAlloc(str) {
const slice = __alloc(this.mem, str, true);
this.mem.u32[this.base >>> 2] = slice[0];
return slice;
}
toJSON() {
return this.deref();
}
toString() {
return this.deref();
}
valueOf() {
return this.deref();
}
}
/**
* Encodes given string to UTF-8 (by default zero terminated), allocates
* memory for it, updates this pointer to new address and returns allocated
* {@link MemorySlice}.
*
* @remarks
* See {@link WasmStringSlice.setAlloc} for important details.
*
* @param str
*/
setAlloc(str) {
const slice = __alloc(this.mem, str, true);
this.mem.u32[this.base >>> 2] = slice[0];
return slice;
}
toJSON() {
return this.deref();
}
toString() {
return this.deref();
}
valueOf() {
return this.deref();
}
}
const __alloc = (mem, str, terminate) => {
const buf = new TextEncoder().encode(str);
const slice = mem.allocate(buf.length + ~~terminate);
if (slice[1] > 0) {
mem.u8.set(buf, slice[0]);
terminate && (mem.u8[slice[0] + buf.length] = 0);
}
return slice;
const buf = new TextEncoder().encode(str);
const slice = mem.allocate(buf.length + ~~terminate);
if (slice[1] > 0) {
mem.u8.set(buf, slice[0]);
terminate && (mem.u8[slice[0] + buf.length] = 0);
}
return slice;
};
export {
WasmStringPtr,
WasmStringSlice
};
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc