From 4bf11b0fa33a10a6f0f2cc691ca1b67343378729 Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Fri, 4 Oct 2024 14:29:58 -0700 Subject: [PATCH] ran "just format" --- examples/deno.ts | 12 +- src/background-plugin.ts | 249 ++--- src/call-context.ts | 90 +- src/foreground-plugin.ts | 188 +--- src/interfaces.ts | 174 ++-- src/manifest.ts | 14 +- src/mod.test.ts | 691 ++++++------- src/mod.ts | 33 +- src/polyfills/bun-capabilities.ts | 2 +- src/polyfills/deno-capabilities.ts | 2 +- src/polyfills/deno-minimatch.ts | 2 +- src/polyfills/deno-snapshot_preview1.ts | 1231 +++++++++-------------- src/polyfills/node-capabilities.ts | 3 +- src/utils.ts | 52 +- src/worker-url.ts | 4 +- src/worker.ts | 155 +-- 16 files changed, 1136 insertions(+), 1766 deletions(-) diff --git a/examples/deno.ts b/examples/deno.ts index dfda8a8..f0dfd53 100755 --- a/examples/deno.ts +++ b/examples/deno.ts @@ -1,20 +1,20 @@ #!/usr/bin/env deno run -A -import createPlugin from "../src/mod.ts"; +import createPlugin from '../src/mod.ts'; -const filename = Deno.args[0] || "wasm/hello.wasm"; -const funcname = Deno.args[1] || "run_test"; -const input = Deno.args[2] || "this is a test"; +const filename = Deno.args[0] || 'wasm/hello.wasm'; +const funcname = Deno.args[1] || 'run_test'; +const input = Deno.args[2] || 'this is a test'; const plugin = await createPlugin(filename, { useWasi: true, logLevel: 'trace', logger: console, config: { - thing: "testing", + thing: 'testing', }, }); -console.log("calling", { filename, funcname, input }); +console.log('calling', { filename, funcname, input }); const res = await plugin.call(funcname, new TextEncoder().encode(input)); // const s = new TextDecoder().decode(res.buffer); // console.log(s); diff --git a/src/background-plugin.ts b/src/background-plugin.ts index e8057f9..f6a9433 100644 --- a/src/background-plugin.ts +++ b/src/background-plugin.ts @@ -8,27 +8,28 @@ import { RESET, SET_HOST_CONTEXT, STORE, -} from "./call-context.ts"; +} from './call-context.ts'; import { type InternalConfig, MemoryOptions, PluginOutput, SAB_BASE_OFFSET, SharedArrayBufferSection, -} from "./interfaces.ts"; -import { readBodyUpTo } from "./utils.ts"; -import { WORKER_URL } from "./worker-url.ts"; -import { Worker } from "node:worker_threads"; -import { CAPABILITIES } from "./polyfills/deno-capabilities.ts"; -import { EXTISM_ENV } from "./foreground-plugin.ts"; -import { matches } from "./polyfills/deno-minimatch.ts"; +} from './interfaces.ts'; +import { readBodyUpTo } from './utils.ts'; +import { WORKER_URL } from './worker-url.ts'; +import { Worker } from 'node:worker_threads'; +import { CAPABILITIES } from './polyfills/deno-capabilities.ts'; +import { EXTISM_ENV } from './foreground-plugin.ts'; +import { matches } from './polyfills/deno-minimatch.ts'; // Firefox has not yet implemented Atomics.waitAsync, but we can polyfill // it using a worker as a one-off. // // TODO: we should probably give _each_ background plugin its own waiter // script. -const AtomicsWaitAsync = Atomics.waitAsync || +const AtomicsWaitAsync = + Atomics.waitAsync || (() => { const src = `onmessage = ev => { const [b, i, v] = ev.data @@ -36,12 +37,12 @@ const AtomicsWaitAsync = Atomics.waitAsync || postMessage(Atomics.wait(f, i, v)); }`; - const blob = new (Blob as any)([src], { type: "text/javascript" }); + const blob = new (Blob as any)([src], { type: 'text/javascript' }); const url = URL.createObjectURL(blob); const w = new Worker(url); return (ia: any, index, value) => { const promise = new Promise((resolve) => { - w.once("message", (data) => { + w.once('message', (data) => { resolve(data); }); }); @@ -80,7 +81,7 @@ class BackgroundPlugin { this.#context = context; this.hostFlag[0] = SAB_BASE_OFFSET; - this.worker.on("message", (ev) => this.#handleMessage(ev)); + this.worker.on('message', (ev) => this.#handleMessage(ev)); } async #handleTimeout() { @@ -90,18 +91,12 @@ class BackgroundPlugin { const timedOut = {}; const failed = {}; - const result = await Promise.race([ - timeout(this.opts.timeoutMs, timedOut), - Promise.all([ - terminateWorker(this.worker), - createWorker( - this.opts, - this.names, - this.modules, - this.sharedData, - ), - ]), - ].filter(Boolean)).catch(() => failed); + const result = await Promise.race( + [ + timeout(this.opts.timeoutMs, timedOut), + Promise.all([terminateWorker(this.worker), createWorker(this.opts, this.names, this.modules, this.sharedData)]), + ].filter(Boolean), + ).catch(() => failed); this.#context[RESET](); // Oof. The Wasm module failed to even _restart_ in the time allotted. There's @@ -109,7 +104,7 @@ class BackgroundPlugin { // squatting on `this.#request` so the plugin always looks "active". if (result === timedOut) { this.opts.logger.error( - "EXTISM: Plugin timed out while handling a timeout. Plugin will hang. This Wasm module may have a non-trivial `start` section.", + 'EXTISM: Plugin timed out while handling a timeout. Plugin will hang. This Wasm module may have a non-trivial `start` section.', ); this.worker = null as unknown as any; // TODO: expose some way to observe that the plugin is in a "poisoned" state. @@ -118,9 +113,7 @@ class BackgroundPlugin { // The worker failed to start up for some other reason. This is pretty unlikely to happen! if (result === failed) { - this.opts.logger.error( - "EXTISM: Plugin failed to restart during a timeout. Plugin will hang.", - ); + this.opts.logger.error('EXTISM: Plugin failed to restart during a timeout. Plugin will hang.'); this.worker = null as unknown as any; return; } @@ -128,11 +121,11 @@ class BackgroundPlugin { this.worker = worker as Worker; if (request) { - request.pop()!(new Error("EXTISM: call canceled due to timeout")); + request.pop()!(new Error('EXTISM: call canceled due to timeout')); } this.#request = null; - this.worker.on("message", (ev) => this.#handleMessage(ev)); + this.worker.on('message', (ev) => this.#handleMessage(ev)); } async reset(): Promise { @@ -140,7 +133,7 @@ class BackgroundPlugin { return false; } - await this.#invoke("reset"); + await this.#invoke('reset'); this.#context[RESET](); return true; @@ -152,21 +145,19 @@ class BackgroundPlugin { async #handleMessage(ev: any) { switch (ev?.type) { - case "invoke": + case 'invoke': return this.#handleInvoke(ev); - case "return": + case 'return': return this.#handleReturn(ev); - case "log": + case 'log': return this.#handleLog(ev); } } #handleLog(ev: any) { const fn = (this.opts.logger as any)[ev.level as string]; - if (typeof fn !== "function") { - this.opts.logger?.error( - `failed to find loglevel="${ev.level}" on logger: message=${ev.message}`, - ); + if (typeof fn !== 'function') { + this.opts.logger?.error(`failed to find loglevel="${ev.level}" on logger: message=${ev.message}`); } else { fn.call(this.opts.logger, ev.message); } @@ -195,7 +186,7 @@ class BackgroundPlugin { // host -> guest() invoke async #invoke(handler: string, ...args: any[]): Promise { if (this.#request) { - throw new Error("plugin is not reentrant"); + throw new Error('plugin is not reentrant'); } let resolve, reject; const promise = new Promise((res, rej) => { @@ -206,24 +197,24 @@ class BackgroundPlugin { this.#request = [resolve as any, reject as any]; if (!this.worker) { - throw new Error("worker not initialized"); + throw new Error('worker not initialized'); } const timedOut = {}; // Since this creates a new promise, we need to provide // an empty error handler. - Promise.race([ - timeout(this.opts.timeoutMs, timedOut), - promise, - ].filter(Boolean)).then(async (v) => { - if (v === timedOut) { - await this.#handleTimeout(); - } - }, () => {}); + Promise.race([timeout(this.opts.timeoutMs, timedOut), promise].filter(Boolean)).then( + async (v) => { + if (v === timedOut) { + await this.#handleTimeout(); + } + }, + () => {}, + ); this.worker.postMessage({ - type: "invoke", + type: 'invoke', handler, args, }); @@ -232,15 +223,11 @@ class BackgroundPlugin { } async functionExists(funcName: string): Promise { - return await this.#invoke("functionExists", funcName); + return await this.#invoke('functionExists', funcName); } // host -> guest invoke() - async call( - funcName: string, - input?: string | Uint8Array, - hostContext?: T, - ): Promise { + async call(funcName: string, input?: string | Uint8Array, hostContext?: T): Promise { const index = this.#context[STORE](input); this.#context[SET_HOST_CONTEXT](hostContext); @@ -260,9 +247,7 @@ class BackgroundPlugin { } const buf = new PluginOutput( - CAPABILITIES.allowSharedBufferCodec - ? block.buffer - : new Uint8Array(block.buffer).slice().buffer, + CAPABILITIES.allowSharedBufferCodec ? block.buffer : new Uint8Array(block.buffer).slice().buffer, ); if (shouldThrow) { @@ -273,17 +258,9 @@ class BackgroundPlugin { return buf; } - async callBlock( - funcName: string, - input: number | null, - ): Promise<[number | null, number | null]> { + async callBlock(funcName: string, input: number | null): Promise<[number | null, number | null]> { const exported = this.#context[EXPORT_STATE](); - const { results, state } = await this.#invoke( - "call", - funcName, - input, - exported, - ); + const { results, state } = await this.#invoke('call', funcName, input, exported); this.#context[IMPORT_STATE](state, true); const [err, data] = results; @@ -295,15 +272,15 @@ class BackgroundPlugin { } async getExports(): Promise { - return await this.#invoke("getExports"); + return await this.#invoke('getExports'); } async getImports(): Promise { - return await this.#invoke("getImports"); + return await this.#invoke('getImports'); } async getInstance(): Promise { - throw new Error("todo"); + throw new Error('todo'); } async close(): Promise { @@ -326,9 +303,7 @@ class BackgroundPlugin { const timer = setInterval(() => {}, 0); try { if (!func) { - throw Error( - `Plugin error: host function "${ev.namespace}" "${ev.func}" does not exist`, - ); + throw Error(`Plugin error: host function "${ev.namespace}" "${ev.func}" does not exist`); } // Fill the shared array buffer with an expected garbage value to make debugging @@ -368,7 +343,7 @@ class BackgroundPlugin { } } - if (typeof data === "bigint") { + if (typeof data === 'bigint') { promise = writer.writeUint8(SharedArrayBufferSection.RetI64); if (promise) { await promise; @@ -378,7 +353,7 @@ class BackgroundPlugin { if (promise) { await promise; } - } else if (typeof data === "number") { + } else if (typeof data === 'number') { promise = writer.writeUint8(SharedArrayBufferSection.RetF64); if (promise) { await promise; @@ -438,14 +413,9 @@ class RingBufferWriter { do { value = Atomics.load(this.flag, 0); if (value === lastKnownValue) { - const { value: result, async } = AtomicsWaitAsync( - this.flag, - 0, - lastKnownValue, - MAX_WAIT, - ); + const { value: result, async } = AtomicsWaitAsync(this.flag, 0, lastKnownValue, MAX_WAIT); if (async) { - if ((await result) === "timed-out") { + if ((await result) === 'timed-out') { continue; } } @@ -455,9 +425,7 @@ class RingBufferWriter { signal() { const old = Atomics.load(this.flag, 0); - while ( - Atomics.compareExchange(this.flag, 0, old, this.outputOffset) === old - ) {} + while (Atomics.compareExchange(this.flag, 0, old, this.outputOffset) === old) {} Atomics.notify(this.flag, 0, 1); } @@ -476,19 +444,11 @@ class RingBufferWriter { async spanningWrite(input: Uint8Array) { let inputOffset = 0; let toWrite = this.output.byteLength - this.outputOffset; - let flushedWriteCount = 1 + - Math.floor( - (input.byteLength - toWrite) / - (this.output.byteLength - SAB_BASE_OFFSET), - ); - const finalWrite = (input.byteLength - toWrite) % - (this.output.byteLength - SAB_BASE_OFFSET); + let flushedWriteCount = 1 + Math.floor((input.byteLength - toWrite) / (this.output.byteLength - SAB_BASE_OFFSET)); + const finalWrite = (input.byteLength - toWrite) % (this.output.byteLength - SAB_BASE_OFFSET); do { - new Uint8Array(this.output).set( - input.subarray(inputOffset, inputOffset + toWrite), - this.outputOffset, - ); + new Uint8Array(this.output).set(input.subarray(inputOffset, inputOffset + toWrite), this.outputOffset); // increment the offset so we know we've written _something_ (and can bypass the "did we not write anything" check in `flush()`) this.outputOffset += toWrite; @@ -542,11 +502,7 @@ class HttpContext { allowedHosts: string[]; memoryOptions: MemoryOptions; - constructor( - _fetch: typeof fetch, - allowedHosts: string[], - memoryOptions: MemoryOptions, - ) { + constructor(_fetch: typeof fetch, allowedHosts: string[], memoryOptions: MemoryOptions) { this.fetch = _fetch; this.allowedHosts = allowedHosts; this.lastStatusCode = 0; @@ -555,26 +511,19 @@ class HttpContext { contribute(functions: Record>) { functions[EXTISM_ENV] ??= {}; - functions[EXTISM_ENV].http_request = ( - callContext: CallContext, - reqaddr: bigint, - bodyaddr: bigint, - ) => this.makeRequest(callContext, reqaddr, bodyaddr); + functions[EXTISM_ENV].http_request = (callContext: CallContext, reqaddr: bigint, bodyaddr: bigint) => + this.makeRequest(callContext, reqaddr, bodyaddr); functions[EXTISM_ENV].http_status_code = () => this.lastStatusCode; } - async makeRequest( - callContext: CallContext, - reqaddr: bigint, - bodyaddr: bigint, - ) { + async makeRequest(callContext: CallContext, reqaddr: bigint, bodyaddr: bigint) { const req = callContext.read(reqaddr); if (req === null) { return 0n; } const { headers, header, url: rawUrl, method: m } = req.json(); - const method = m ?? "GET"; + const method = m ?? 'GET'; const url = new URL(rawUrl); const isAllowed = this.allowedHosts.some((allowedHost) => { @@ -582,14 +531,10 @@ class HttpContext { }); if (!isAllowed) { - throw new Error( - `Call error: HTTP request to "${url}" is not allowed (no allowedHosts match "${url.hostname}")`, - ); + throw new Error(`Call error: HTTP request to "${url}" is not allowed (no allowedHosts match "${url.hostname}")`); } - const body = bodyaddr === 0n || method === "GET" || method === "HEAD" - ? null - : callContext.read(bodyaddr)?.bytes(); + const body = bodyaddr === 0n || method === 'GET' || method === 'HEAD' ? null : callContext.read(bodyaddr)?.bytes(); const fetch = this.fetch; const response = await fetch(rawUrl, { headers: headers || header, @@ -623,18 +568,8 @@ export async function createBackgroundPlugin( names: string[], modules: WebAssembly.Module[], ): Promise { - const context = new CallContext( - SharedArrayBuffer, - opts.logger, - opts.logLevel, - opts.config, - opts.memory, - ); - const httpContext = new HttpContext( - opts.fetch, - opts.allowedHosts, - opts.memory, - ); + const context = new CallContext(SharedArrayBuffer, opts.logger, opts.logLevel, opts.config, opts.memory); + const httpContext = new HttpContext(opts.fetch, opts.allowedHosts, opts.memory); httpContext.contribute(opts.functions); // NB(chrisdickinson): In order for the host and guest to have the same "view" of the @@ -644,11 +579,7 @@ export async function createBackgroundPlugin( opts.functions[EXTISM_ENV].var_get ??= (_: CallContext, key: bigint) => { return context[ENV].var_get(key); }; - opts.functions[EXTISM_ENV].var_set ??= ( - _: CallContext, - key: bigint, - val: bigint, - ) => { + opts.functions[EXTISM_ENV].var_set ??= (_: CallContext, key: bigint, val: bigint) => { return context[ENV].var_set(key, val); }; @@ -668,25 +599,15 @@ export async function createBackgroundPlugin( earlyWorker = w; }; - const worker = await Promise.race([ - timeout(opts.timeoutMs, timedOut), - createWorker(opts, names, modules, sharedData, onworker), - ].filter(Boolean)); + const worker = await Promise.race( + [timeout(opts.timeoutMs, timedOut), createWorker(opts, names, modules, sharedData, onworker)].filter(Boolean), + ); if (worker === timedOut) { await terminateWorker(earlyWorker!); - throw new Error( - "EXTISM: timed out while waiting for plugin to instantiate", - ); + throw new Error('EXTISM: timed out while waiting for plugin to instantiate'); } - return new BackgroundPlugin( - worker as Worker, - sharedData, - names, - modules, - opts, - context, - ); + return new BackgroundPlugin(worker as Worker, sharedData, names, modules, opts, context); } async function createWorker( @@ -700,23 +621,23 @@ async function createWorker( onworker(worker); await new Promise((resolve, reject) => { - worker.on("message", function handler(ev) { - if (ev?.type !== "initialized") { + worker.on('message', function handler(ev) { + if (ev?.type !== 'initialized') { reject(new Error(`received unexpected message (type=${ev?.type})`)); } - worker.removeListener("message", handler); + worker.removeListener('message', handler); resolve(null); }); }); const onready = new Promise((resolve, reject) => { - worker.on("message", function handler(ev) { - if (ev?.type !== "ready") { + worker.on('message', function handler(ev) { + if (ev?.type !== 'ready') { reject(new Error(`received unexpected message (type=${ev?.type})`)); } - worker.removeListener("message", handler); + worker.removeListener('message', handler); resolve(null); }); }); @@ -724,10 +645,8 @@ async function createWorker( const { fetch: _, logger: __, ...rest } = opts; const message = { ...rest, - type: "init", - functions: Object.fromEntries( - Object.entries(opts.functions || {}).map(([k, v]) => [k, Object.keys(v)]), - ), + type: 'init', + functions: Object.fromEntries(Object.entries(opts.functions || {}).map(([k, v]) => [k, Object.keys(v)])), names, modules, sharedData, @@ -740,15 +659,11 @@ async function createWorker( } function timeout(ms: number | null, sentinel: any) { - return ( - ms === null - ? null - : new Promise((resolve) => setTimeout(() => resolve(sentinel), ms)) - ); + return ms === null ? null : new Promise((resolve) => setTimeout(() => resolve(sentinel), ms)); } async function terminateWorker(w: Worker) { - if (typeof (globalThis as any).Bun !== "undefined") { + if (typeof (globalThis as any).Bun !== 'undefined') { const timer = setTimeout(() => {}, 10); await w.terminate(); clearTimeout(timer); diff --git a/src/call-context.ts b/src/call-context.ts index ffbcd18..ab2239b 100644 --- a/src/call-context.ts +++ b/src/call-context.ts @@ -6,18 +6,18 @@ import { LogLevelPriority, priorityToLogLevel, logLevelToPriority, -} from "./interfaces.ts"; -import { CAPABILITIES } from "./polyfills/deno-capabilities.ts"; - -export const BEGIN = Symbol("begin"); -export const END = Symbol("end"); -export const ENV = Symbol("env"); -export const SET_HOST_CONTEXT = Symbol("set-host-context"); -export const GET_BLOCK = Symbol("get-block"); -export const IMPORT_STATE = Symbol("import-state"); -export const EXPORT_STATE = Symbol("export-state"); -export const STORE = Symbol("store-value"); -export const RESET = Symbol("reset"); +} from './interfaces.ts'; +import { CAPABILITIES } from './polyfills/deno-capabilities.ts'; + +export const BEGIN = Symbol('begin'); +export const END = Symbol('end'); +export const ENV = Symbol('env'); +export const SET_HOST_CONTEXT = Symbol('set-host-context'); +export const GET_BLOCK = Symbol('get-block'); +export const IMPORT_STATE = Symbol('import-state'); +export const EXPORT_STATE = Symbol('export-state'); +export const STORE = Symbol('store-value'); +export const RESET = Symbol('reset'); export class Block { buffer: ArrayBufferLike; @@ -60,7 +60,7 @@ export class CallContext { #logLevel: LogLevelPriority; #decoder: TextDecoder; #encoder: TextEncoder; - #arrayBufferType: { new(size: number): ArrayBufferLike }; + #arrayBufferType: { new (size: number): ArrayBufferLike }; #config: PluginConfig; #vars: Map = new Map(); #varsSize: number; @@ -69,7 +69,7 @@ export class CallContext { /** @hidden */ constructor( - type: { new(size: number): ArrayBufferLike }, + type: { new (size: number): ArrayBufferLike }, logger: Console, logLevel: LogLevelPriority, config: PluginConfig, @@ -106,10 +106,7 @@ export class CallContext { if (this.#memoryOptions.maxPages) { const pageSize = 64 * 1024; - const totalBytes = this.#blocks.reduce( - (acc, block) => acc + (block?.buffer.byteLength ?? 0), - 0, - ); + const totalBytes = this.#blocks.reduce((acc, block) => acc + (block?.buffer.byteLength ?? 0), 0); const totalPages = Math.ceil(totalBytes / pageSize); if (totalPages > this.#memoryOptions.maxPages) { @@ -139,14 +136,11 @@ export class CallContext { * Set a variable to a given string or byte array value. */ setVariable(name: string, value: string | Uint8Array) { - const buffer = typeof value === "string" - ? this.#encoder.encode(value) - : value; + const buffer = typeof value === 'string' ? this.#encoder.encode(value) : value; const variable = this.#vars.get(name); - const newSize = this.#varsSize + buffer.byteLength - - (variable?.byteLength || 0); + const newSize = this.#varsSize + buffer.byteLength - (variable?.byteLength || 0); if (newSize > (this.#memoryOptions?.maxVarBytes || Infinity)) { throw new Error( `var memory limit exceeded: ${newSize} bytes requested, ${this.#memoryOptions.maxVarBytes} allowed`, @@ -181,10 +175,10 @@ export class CallContext { return null; } - const buffer = !(block.buffer instanceof ArrayBuffer) && - !CAPABILITIES.allowSharedBufferCodec - ? new Uint8Array(block.buffer).slice().buffer - : block.buffer; + const buffer = + !(block.buffer instanceof ArrayBuffer) && !CAPABILITIES.allowSharedBufferCodec + ? new Uint8Array(block.buffer).slice().buffer + : block.buffer; return new PluginOutput(buffer); } @@ -197,7 +191,7 @@ export class CallContext { store(input: string | Uint8Array): bigint { const idx = this[STORE](input); if (!idx) { - throw new Error("failed to store output"); + throw new Error('failed to store output'); } return Block.indexToAddress(idx); } @@ -212,22 +206,20 @@ export class CallContext { } setError(err: string | Error | null = null) { - const blockIdx = err - ? this[STORE](err instanceof Error ? err.message : err) - : 0; + const blockIdx = err ? this[STORE](err instanceof Error ? err.message : err) : 0; if (!blockIdx) { - throw new Error("could not store error value"); + throw new Error('could not store error value'); } this.#stack[this.#stack.length - 1][2] = blockIdx; } get logLevel(): LogLevel { - return priorityToLogLevel(this.#logLevel) + return priorityToLogLevel(this.#logLevel); } set logLevel(v: LogLevel) { - this.#logLevel = logLevelToPriority(v) + this.#logLevel = logLevelToPriority(v); } /** @hidden */ @@ -291,14 +283,11 @@ export class CallContext { const blockIdx = Block.addressToIndex(addr); const block = this.#blocks[blockIdx]; if (!block) { - throw new Error( - `cannot assign to this block (addr=${addr.toString(16).padStart(16, "0") - }; length=${length})`, - ); + throw new Error(`cannot assign to this block (addr=${addr.toString(16).padStart(16, '0')}; length=${length})`); } if (length > block.buffer.byteLength) { - throw new Error("length longer than target block"); + throw new Error('length longer than target block'); } this.#stack[this.#stack.length - 1][1] = blockIdx; @@ -308,7 +297,7 @@ export class CallContext { const blockIdx = Block.addressToIndex(addr); const block = this.#blocks[blockIdx]; if (!block) { - throw new Error("cannot assign error to this block"); + throw new Error('cannot assign error to this block'); } this.#stack[this.#stack.length - 1][2] = blockIdx; @@ -363,10 +352,7 @@ export class CallContext { const item = this.read(addr); if (item === null) { - this.#logger.error( - `attempted to set variable using invalid key address (addr="${addr.toString(16) - }H")`, - ); + this.#logger.error(`attempted to set variable using invalid key address (addr="${addr.toString(16)}H")`); return; } @@ -380,8 +366,7 @@ export class CallContext { const valueBlock = this.#blocks[Block.addressToIndex(valueaddr)]; if (!valueBlock) { this.#logger.error( - `attempted to set variable to invalid address (key="${key}"; addr="${valueaddr.toString(16) - }H")`, + `attempted to set variable to invalid address (key="${key}"; addr="${valueaddr.toString(16)}H")`, ); return; } @@ -401,12 +386,12 @@ export class CallContext { }, http_request: (_requestOffset: bigint, _bodyOffset: bigint): bigint => { - this.#logger.error("http_request is not enabled"); + this.#logger.error('http_request is not enabled'); return 0n; }, http_status_code: (): number => { - this.#logger.error("http_status_code is not enabled"); + this.#logger.error('http_status_code is not enabled'); return 0; }, @@ -438,8 +423,7 @@ export class CallContext { const block = this.#blocks[blockIdx]; if (!block) { this.#logger.error( - `failed to log(${level}): bad block reference in addr 0x${addr.toString(16).padStart(64, "0") - }`, + `failed to log(${level}): bad block reference in addr 0x${addr.toString(16).padStart(64, '0')}`, ); return; } @@ -485,9 +469,7 @@ export class CallContext { // eslint-disable-next-line prefer-const for (let [buf, idx] of state.blocks) { if (buf && copy) { - const dst = new Uint8Array( - new this.#arrayBufferType(Number(buf.byteLength)), - ); + const dst = new Uint8Array(new this.#arrayBufferType(Number(buf.byteLength))); dst.set(new Uint8Array(buf)); buf = dst.buffer; } @@ -518,7 +500,7 @@ export class CallContext { /** @hidden */ [STORE](input?: string | Uint8Array): number | null { - if (typeof input === "string") { + if (typeof input === 'string') { input = this.#encoder.encode(input); } diff --git a/src/foreground-plugin.ts b/src/foreground-plugin.ts index e29de78..2860be4 100644 --- a/src/foreground-plugin.ts +++ b/src/foreground-plugin.ts @@ -1,21 +1,8 @@ -import { - BEGIN, - CallContext, - END, - ENV, - GET_BLOCK, - RESET, - SET_HOST_CONTEXT, - STORE, -} from "./call-context.ts"; -import { - type InternalConfig, - InternalWasi, - PluginOutput, -} from "./interfaces.ts"; -import { loadWasi } from "./polyfills/deno-wasi.ts"; - -export const EXTISM_ENV = "extism:host/env"; +import { BEGIN, CallContext, END, ENV, GET_BLOCK, RESET, SET_HOST_CONTEXT, STORE } from './call-context.ts'; +import { type InternalConfig, InternalWasi, PluginOutput } from './interfaces.ts'; +import { loadWasi } from './polyfills/deno-wasi.ts'; + +export const EXTISM_ENV = 'extism:host/env'; type InstantiatedModule = [WebAssembly.Module, WebAssembly.Instance]; @@ -26,12 +13,7 @@ export class ForegroundPlugin { #wasi: InternalWasi[]; #opts: InternalConfig; - constructor( - opts: InternalConfig, - context: CallContext, - instancePair: InstantiatedModule, - wasi: InternalWasi[], - ) { + constructor(opts: InternalConfig, context: CallContext, instancePair: InstantiatedModule, wasi: InternalWasi[]) { this.#context = context; this.#instancePair = instancePair; this.#wasi = wasi; @@ -52,22 +34,18 @@ export class ForegroundPlugin { } async functionExists(funcName: string): Promise { - return typeof this.#instancePair[1].exports[funcName] === "function"; + return typeof this.#instancePair[1].exports[funcName] === 'function'; } - async callBlock( - funcName: string, - input: number | null, - ): Promise<[number | null, number | null]> { + async callBlock(funcName: string, input: number | null): Promise<[number | null, number | null]> { this.#active = true; - const func: CallableFunction | undefined = this.#instancePair[1] - .exports[funcName] as CallableFunction; + const func: CallableFunction | undefined = this.#instancePair[1].exports[funcName] as CallableFunction; if (!func) { throw Error(`Plugin error: function "${funcName}" does not exist`); } - if (typeof func !== "function") { + if (typeof func !== 'function') { throw Error(`Plugin error: export "${funcName}" is not a function`); } @@ -83,11 +61,7 @@ export class ForegroundPlugin { } } - async call( - funcName: string, - input?: string | Uint8Array, - hostContext?: T, - ): Promise { + async call(funcName: string, input?: string | Uint8Array, hostContext?: T): Promise { this.#context[RESET](); const inputIdx = this.#context[STORE](input); @@ -135,13 +109,7 @@ export async function createForegroundPlugin( opts: InternalConfig, names: string[], modules: WebAssembly.Module[], - context: CallContext = new CallContext( - ArrayBuffer, - opts.logger, - opts.logLevel, - opts.config, - opts.memory, - ), + context: CallContext = new CallContext(ArrayBuffer, opts.logger, opts.logLevel, opts.config, opts.memory), ): Promise { const imports: Record> = { [EXTISM_ENV]: context[ENV], @@ -151,40 +119,21 @@ export async function createForegroundPlugin( for (const namespace in opts.functions) { imports[namespace] = imports[namespace] || {}; for (const func in opts.functions[namespace]) { - imports[namespace][func] = opts.functions[namespace][func].bind( - null, - context, - ); + imports[namespace][func] = opts.functions[namespace][func].bind(null, context); } } // find the "main" module and try to instantiate it. - const mainIndex = names.indexOf("main"); + const mainIndex = names.indexOf('main'); if (mainIndex === -1) { - throw new Error( - 'Unreachable: manifests must have at least one "main" module. Enforced by "src/manifest.ts")', - ); + throw new Error('Unreachable: manifests must have at least one "main" module. Enforced by "src/manifest.ts")'); } const seen: Map = new Map(); const wasiList: InternalWasi[] = []; - const instance = await instantiateModule( - ["main"], - modules[mainIndex], - imports, - opts, - wasiList, - names, - modules, - seen, - ); - - return new ForegroundPlugin( - opts, - context, - [modules[mainIndex], instance], - wasiList, - ); + const instance = await instantiateModule(['main'], modules[mainIndex], imports, opts, wasiList, names, modules, seen); + + return new ForegroundPlugin(opts, context, [modules[mainIndex], instance], wasiList); } async function instantiateModule( @@ -199,10 +148,7 @@ async function instantiateModule( ) { linked.set(module, null); - const instantiationImports: Record< - string, - Record - > = {}; + const instantiationImports: Record> = {}; const requested = WebAssembly.Module.imports(module); let wasi = null; @@ -210,11 +156,9 @@ async function instantiateModule( const nameIdx = names.indexOf(module); if (nameIdx === -1) { - if (module === "wasi_snapshot_preview1" && wasi === null) { + if (module === 'wasi_snapshot_preview1' && wasi === null) { if (!opts.wasiEnabled) { - throw new Error( - 'WASI is not enabled; see the "wasiEnabled" plugin option', - ); + throw new Error('WASI is not enabled; see the "wasiEnabled" plugin option'); } if (wasi === null) { @@ -227,38 +171,31 @@ async function instantiateModule( // lookup from "imports" if (!Object.hasOwnProperty.call(imports, module)) { throw new Error( - `from module "${ - current.join( - '"/"', - ) - }": cannot resolve import "${module}" "${name}": not provided by host imports nor linked manifest items`, + `from module "${current.join( + '"/"', + )}": cannot resolve import "${module}" "${name}": not provided by host imports nor linked manifest items`, ); } if (!Object.hasOwnProperty.call(imports[module], name)) { throw new Error( - `from module "${ - current.join( - '"/"', - ) - }": cannot resolve import "${module}" "${name}" ("${module}" is a host module, but does not contain "${name}")`, + `from module "${current.join( + '"/"', + )}": cannot resolve import "${module}" "${name}" ("${module}" is a host module, but does not contain "${name}")`, ); } switch (kind) { case `function`: { instantiationImports[module] ??= {}; - instantiationImports[module][name] = - imports[module][name] as CallableFunction; + instantiationImports[module][name] = imports[module][name] as CallableFunction; break; } default: throw new Error( - `from module "${ - current.join( - '"/"', - ) - }": in import "${module}" "${name}", "${kind}"-typed host imports are not supported yet`, + `from module "${current.join( + '"/"', + )}": in import "${module}" "${name}", "${kind}"-typed host imports are not supported yet`, ); } } else { @@ -272,55 +209,31 @@ async function instantiateModule( if (!target) { throw new Error( - `from module "${ - current.join('"/"') - }": cannot import "${module}" "${name}"; no export matched request`, + `from module "${current.join('"/"')}": cannot import "${module}" "${name}"; no export matched request`, ); } // If the dependency provides "_start", treat it as a WASI Command module; instantiate it (and its subtree) directly. - const instance = providerExports.find((xs) => xs.name === "_start") - ? await instantiateModule( - [...current, module], - provider, - imports, - opts, - wasiList, - names, - modules, - new Map(), - ) + const instance = providerExports.find((xs) => xs.name === '_start') + ? await instantiateModule([...current, module], provider, imports, opts, wasiList, names, modules, new Map()) : !linked.has(provider) - ? (await instantiateModule( - [...current, module], - provider, - imports, - opts, - wasiList, - names, - modules, - linked, - ), + ? (await instantiateModule([...current, module], provider, imports, opts, wasiList, names, modules, linked), linked.get(provider)) : linked.get(provider); if (!instance) { // circular import, either make a trampoline or bail - if (kind === "function") { + if (kind === 'function') { instantiationImports[module] = {}; let cached: CallableFunction | null = null; - instantiationImports[module][name] = ( - ...args: (number | bigint)[] - ) => { + instantiationImports[module][name] = (...args: (number | bigint)[]) => { if (cached) { return cached(...args); } const instance = linked.get(modules[nameIdx]); if (!instance) { throw new Error( - `from module instance "${ - current.join('"/"') - }": target module "${module}" was never instantiated`, + `from module instance "${current.join('"/"')}": target module "${module}" was never instantiated`, ); } cached = instance.exports[name] as CallableFunction; @@ -328,19 +241,16 @@ async function instantiateModule( }; } else { throw new Error( - `from module "${ - current.join( - '"/"', - ) - }": cannot import "${module}" "${name}"; circular imports of type="${kind}" are not supported`, + `from module "${current.join( + '"/"', + )}": cannot import "${module}" "${name}"; circular imports of type="${kind}" are not supported`, ); } } else { // Add each requested import value piecemeal, since we have to validate that _all_ import requests are satisfied by this // module. instantiationImports[module] ??= {}; - instantiationImports[module][name] = instance - .exports[name] as WebAssembly.ExportValue; + instantiationImports[module][name] = instance.exports[name] as WebAssembly.ExportValue; } } } @@ -348,12 +258,12 @@ async function instantiateModule( const instance = await WebAssembly.instantiate(module, instantiationImports); const guestType = instance.exports.hs_init - ? "haskell" + ? 'haskell' : instance.exports._initialize - ? "reactor" + ? 'reactor' : instance.exports._start - ? "command" - : "none"; + ? 'command' + : 'none'; if (wasi) { await wasi?.initialize(instance); @@ -362,17 +272,17 @@ async function instantiateModule( } } else { switch (guestType) { - case "command": + case 'command': if (instance.exports._initialize) { (instance.exports._initialize as CallableFunction)(); } (instance.exports._start as CallableFunction)(); break; - case "reactor": + case 'reactor': (instance.exports._initialize as CallableFunction)(); break; - case "haskell": + case 'haskell': (instance.exports.hs_init as CallableFunction)(); break; } diff --git a/src/interfaces.ts b/src/interfaces.ts index 918e0cf..8e6af66 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -1,4 +1,4 @@ -import { CallContext } from "./call-context.ts"; +import { CallContext } from './call-context.ts'; /** * {@link Plugin} Config @@ -46,67 +46,43 @@ export class PluginOutput extends DataView { } setInt8(_byteOffset: number, _value: number): void { - throw new Error("Cannot set values on output"); + throw new Error('Cannot set values on output'); } setInt16(_byteOffset: number, _value: number, _littleEndian?: boolean): void { - throw new Error("Cannot set values on output"); + throw new Error('Cannot set values on output'); } setInt32(_byteOffset: number, _value: number, _littleEndian?: boolean): void { - throw new Error("Cannot set values on output"); + throw new Error('Cannot set values on output'); } setUint8(_byteOffset: number, _value: number): void { - throw new Error("Cannot set values on output"); + throw new Error('Cannot set values on output'); } - setUint16( - _byteOffset: number, - _value: number, - _littleEndian?: boolean, - ): void { - throw new Error("Cannot set values on output"); + setUint16(_byteOffset: number, _value: number, _littleEndian?: boolean): void { + throw new Error('Cannot set values on output'); } - setUint32( - _byteOffset: number, - _value: number, - _littleEndian?: boolean, - ): void { - throw new Error("Cannot set values on output"); + setUint32(_byteOffset: number, _value: number, _littleEndian?: boolean): void { + throw new Error('Cannot set values on output'); } - setFloat32( - _byteOffset: number, - _value: number, - _littleEndian?: boolean, - ): void { - throw new Error("Cannot set values on output"); + setFloat32(_byteOffset: number, _value: number, _littleEndian?: boolean): void { + throw new Error('Cannot set values on output'); } - setFloat64( - _byteOffset: number, - _value: number, - _littleEndian?: boolean, - ): void { - throw new Error("Cannot set values on output"); + setFloat64(_byteOffset: number, _value: number, _littleEndian?: boolean): void { + throw new Error('Cannot set values on output'); } - setBigInt64( - _byteOffset: number, - _value: bigint, - _littleEndian?: boolean, - ): void { - throw new Error("Cannot set values on output"); + setBigInt64(_byteOffset: number, _value: bigint, _littleEndian?: boolean): void { + throw new Error('Cannot set values on output'); } - setBigUint64( - _byteOffset: number, - _value: bigint, - _littleEndian?: boolean, - ): void { - throw new Error("Cannot set values on output"); + setBigUint64(_byteOffset: number, _value: bigint, _littleEndian?: boolean): void { + throw new Error('Cannot set values on output'); } } @@ -130,11 +106,7 @@ export interface Plugin { * @param {T} hostContext Per-call context to make available to host functions * @returns {Promise} The result from the function call */ - call( - funcName: string, - input?: string | number | Uint8Array, - hostContext?: T, - ): Promise; + call(funcName: string, input?: string | number | Uint8Array, hostContext?: T): Promise; getExports(): Promise; getImports(): Promise; getInstance(): Promise; @@ -198,11 +170,13 @@ export interface ExtismPluginOptions { * } * ``` */ - functions?: { - [key: string]: { - [key: string]: (callContext: CallContext, ...args: any[]) => any; - }; - } | undefined; + functions?: + | { + [key: string]: { + [key: string]: (callContext: CallContext, ...args: any[]) => any; + }; + } + | undefined; allowedPaths?: { [key: string]: string } | undefined; /** @@ -253,27 +227,21 @@ export type MemoryOptions = { }; type CamelToSnakeCase = S extends `${infer T}${infer U}` - ? `${T extends Capitalize ? "_" : ""}${Lowercase}${CamelToSnakeCase}` + ? `${T extends Capitalize ? '_' : ''}${Lowercase}${CamelToSnakeCase}` : S; type SnakeCase> = { [K in keyof T as CamelToSnakeCase]: T[K]; }; -export interface NativeManifestOptions extends - Pick< - ExtismPluginOptions, - "allowedPaths" | "allowedHosts" | "memory" | "config" | "timeoutMs" - > { -} +export interface NativeManifestOptions + extends Pick {} /** * The subset of {@link ExtismPluginOptions} attributes available for configuration via * a {@link Manifest}. If an attribute is specified at both the {@link ExtismPluginOptions} and * `ManifestOptions` level, the plugin option will take precedence. */ -export type ManifestOptions = - & NativeManifestOptions - & SnakeCase; +export type ManifestOptions = NativeManifestOptions & SnakeCase; export interface InternalConfig extends Required { logger: Console; @@ -340,18 +308,16 @@ export interface ManifestWasmModule { * ⚠️ `module` cannot be used in conjunction with `hash`: the Web Platform does not currently provide a way to get source * bytes from a `WebAssembly.Module` in order to hash. */ -export type ManifestWasm = - & ( - | ManifestWasmUrl - | ManifestWasmData - | ManifestWasmPath - | ManifestWasmResponse - | ManifestWasmModule - ) - & { - name?: string; - hash?: string; - }; +export type ManifestWasm = ( + | ManifestWasmUrl + | ManifestWasmData + | ManifestWasmPath + | ManifestWasmResponse + | ManifestWasmModule +) & { + name?: string; + hash?: string; +}; /** * The manifest which describes the {@link Plugin} code and runtime constraints. This is passed to {@link createPlugin} @@ -401,13 +367,7 @@ export interface Manifest extends ManifestOptions { * * @see [Extism](https://extism.org/) > [Concepts](https://extism.org/docs/category/concepts) > [Manifest](https://extism.org/docs/concepts/manifest) */ -export type ManifestLike = - | Manifest - | Response - | WebAssembly.Module - | ArrayBuffer - | string - | URL; +export type ManifestLike = Manifest | Response | WebAssembly.Module | ArrayBuffer | string | URL; export interface Capabilities { /** @@ -517,42 +477,48 @@ export enum SharedArrayBufferSection { Block = 4, } -export type LogLevel = - | 'trace' - | 'debug' - | 'info' - | 'warn' - | 'error' - | 'silent' +export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'silent'; export function logLevelToPriority(level: LogLevel): LogLevelPriority { switch (level) { - case 'trace': return 0; - case 'debug': return 1; - case 'info': return 2; - case 'warn': return 3; - case 'error': return 4; - case 'silent': return 0x7fffffff; + case 'trace': + return 0; + case 'debug': + return 1; + case 'info': + return 2; + case 'warn': + return 3; + case 'error': + return 4; + case 'silent': + return 0x7fffffff; default: throw new TypeError( - `unrecognized log level "${level}"; expected one of "trace", "debug", "info", "warn", "error", "silent"` - ) + `unrecognized log level "${level}"; expected one of "trace", "debug", "info", "warn", "error", "silent"`, + ); } } -export type LogLevelPriority = 0 | 1 | 2 | 3 | 4 | 0x7fffffff +export type LogLevelPriority = 0 | 1 | 2 | 3 | 4 | 0x7fffffff; export function priorityToLogLevel(level: LogLevelPriority): LogLevel { switch (level) { - case 0: return 'trace'; - case 1: return 'debug'; - case 2: return 'info'; - case 3: return 'warn'; - case 4: return 'error'; - case 0x7fffffff: return 'silent'; + case 0: + return 'trace'; + case 1: + return 'debug'; + case 2: + return 'info'; + case 3: + return 'warn'; + case 4: + return 'error'; + case 0x7fffffff: + return 'silent'; default: throw new TypeError( - `unrecognized log level "${level}"; expected one of "trace", "debug", "info", "warn", "error", "silent"` - ) + `unrecognized log level "${level}"; expected one of "trace", "debug", "info", "warn", "error", "silent"`, + ); } } diff --git a/src/manifest.ts b/src/manifest.ts index ca09a32..43a3b91 100644 --- a/src/manifest.ts +++ b/src/manifest.ts @@ -85,11 +85,13 @@ function parseManifestFromJson(json: string): Manifest { allowedPaths: parsed.allowedPaths ?? parsed.allowed_paths, allowedHosts: parsed.allowedHosts ?? parsed.allowed_hosts, config: parsed.config, - ...(parsed.memory ? { - maxHttpResponseBytes: parsed.memory.maxHttpResponseBytes ?? parsed.memory.max_http_response_bytes, - maxPages: parsed.memory.maxPages ?? parsed.memory.max_pages, - maxVarBytes: parsed.memory.maxVarBytes ?? parsed.memory.max_var_bytes, - } : {}) + ...(parsed.memory + ? { + maxHttpResponseBytes: parsed.memory.maxHttpResponseBytes ?? parsed.memory.max_http_response_bytes, + maxPages: parsed.memory.maxPages ?? parsed.memory.max_pages, + maxVarBytes: parsed.memory.maxVarBytes ?? parsed.memory.max_var_bytes, + } + : {}), }; } @@ -110,7 +112,7 @@ export async function toWasmModuleData( allowedPaths: manifest.allowedPaths, allowedHosts: manifest.allowedHosts, config: manifest.config, - memory: manifest.memory + memory: manifest.memory, }; const manifestsWasm = await Promise.all( diff --git a/src/mod.test.ts b/src/mod.test.ts index 97adef3..e737c6f 100644 --- a/src/mod.test.ts +++ b/src/mod.test.ts @@ -1,9 +1,9 @@ -import { test } from "node:test"; -import assert from "node:assert"; -import createPlugin, { CallContext, CAPABILITIES } from "./mod.ts"; +import { test } from 'node:test'; +import assert from 'node:assert'; +import createPlugin, { CallContext, CAPABILITIES } from './mod.ts'; -if (typeof WebAssembly === "undefined") { - test("this platform lacks WebAssembly support", async () => { +if (typeof WebAssembly === 'undefined') { + test('this platform lacks WebAssembly support', async () => { // at the time of writing (2023 Oct 27), playwright webkit builds for windows // do not support webassembly. there's an open PR (https://github.com/WebKit/WebKit/pull/18184) // to fix this though. @@ -12,48 +12,36 @@ if (typeof WebAssembly === "undefined") { // The presence of `*.test.ts` files adjacent to module files is no mistake, sadly: // we have to be in the same directory in order to preserve the `__dirname` / `import.meta.url` value // between `mod.ts` and the tests in the build output. - test("createPlugin loads a module and provides lookups", async () => { - const plugin = await createPlugin("http://localhost:8124/wasm/code.wasm", { + test('createPlugin loads a module and provides lookups', async () => { + const plugin = await createPlugin('http://localhost:8124/wasm/code.wasm', { useWasi: true, }); try { - assert( - await plugin.functionExists("count_vowels"), - "count_vowels should exist", - ); - assert( - !(await plugin.functionExists("count_sheep")), - "count_sheep should not exist", - ); + assert(await plugin.functionExists('count_vowels'), 'count_vowels should exist'); + assert(!(await plugin.functionExists('count_sheep')), 'count_sheep should not exist'); } finally { await plugin.close(); } }); - test("createPlugin loads a WebAssembly.Module", async () => { - const response = await fetch("http://localhost:8124/wasm/code.wasm"); + test('createPlugin loads a WebAssembly.Module', async () => { + const response = await fetch('http://localhost:8124/wasm/code.wasm'); const arrayBuffer = await response.arrayBuffer(); const module = await WebAssembly.compile(arrayBuffer); const plugin = await createPlugin(module, { useWasi: true }); try { - assert( - await plugin.functionExists("count_vowels"), - "count_vowels should exist", - ); - assert( - !(await plugin.functionExists("count_sheep")), - "count_sheep should not exist", - ); + assert(await plugin.functionExists('count_vowels'), 'count_vowels should exist'); + assert(!(await plugin.functionExists('count_sheep')), 'count_sheep should not exist'); } finally { await plugin.close(); } }); - test("createPlugin loads a WebAssembly.Module from manifest", async () => { - const response = await fetch("http://localhost:8124/wasm/code.wasm"); + test('createPlugin loads a WebAssembly.Module from manifest', async () => { + const response = await fetch('http://localhost:8124/wasm/code.wasm'); const arrayBuffer = await response.arrayBuffer(); const plugin = await createPlugin( { wasm: [{ module: await WebAssembly.compile(arrayBuffer) }] }, @@ -61,28 +49,24 @@ if (typeof WebAssembly === "undefined") { ); try { - assert( - await plugin.functionExists("count_vowels"), - "count_vowels should exist", - ); - assert( - !(await plugin.functionExists("count_sheep")), - "count_sheep should not exist", - ); + assert(await plugin.functionExists('count_vowels'), 'count_vowels should exist'); + assert(!(await plugin.functionExists('count_sheep')), 'count_sheep should not exist'); } finally { await plugin.close(); } }); - test("createPlugin fails if provided a module and hash", async () => { - const response = await fetch("http://localhost:8124/wasm/code.wasm"); + test('createPlugin fails if provided a module and hash', async () => { + const response = await fetch('http://localhost:8124/wasm/code.wasm'); const arrayBuffer = await response.arrayBuffer(); const [err, plugin] = await createPlugin( { - wasm: [{ - module: await WebAssembly.compile(arrayBuffer), - hash: "anything", - }], + wasm: [ + { + module: await WebAssembly.compile(arrayBuffer), + hash: 'anything', + }, + ], }, { useWasi: true }, ).then( @@ -94,60 +78,46 @@ if (typeof WebAssembly === "undefined") { await plugin.close(); } assert.equal(plugin, null); - assert.equal( - err.message, - "Item specified a hash but WebAssembly.Module source data is unavailable for hashing", - ); + assert.equal(err.message, 'Item specified a hash but WebAssembly.Module source data is unavailable for hashing'); }); - test("createPlugin loads a fetch Response", async () => { - const plugin = await createPlugin( - fetch("http://localhost:8124/wasm/code.wasm"), - { useWasi: true }, - ); + test('createPlugin loads a fetch Response', async () => { + const plugin = await createPlugin(fetch('http://localhost:8124/wasm/code.wasm'), { useWasi: true }); try { - assert( - await plugin.functionExists("count_vowels"), - "count_vowels should exist", - ); - assert( - !(await plugin.functionExists("count_sheep")), - "count_sheep should not exist", - ); + assert(await plugin.functionExists('count_vowels'), 'count_vowels should exist'); + assert(!(await plugin.functionExists('count_sheep')), 'count_sheep should not exist'); } finally { await plugin.close(); } }); if (!CAPABILITIES.crossOriginChecksEnforced) { - test("can create plugin from url with hash check", async () => { + test('can create plugin from url with hash check', async () => { const plugin = await createPlugin({ wasm: [ { - url: - "https://github.com/extism/plugins/releases/download/v0.5.0/count_vowels.wasm", - hash: - "93898457953d30d016f712ccf4336ce7e9971db5f7f3aff1edd252764f75d5d7", + url: 'https://github.com/extism/plugins/releases/download/v0.5.0/count_vowels.wasm', + hash: '93898457953d30d016f712ccf4336ce7e9971db5f7f3aff1edd252764f75d5d7', }, ], }); try { - assert.equal(await plugin.functionExists("count_vowels"), true); + assert.equal(await plugin.functionExists('count_vowels'), true); } finally { await plugin.close(); } }); } - test("createPlugin fails on hash mismatch (bad hash)", async () => { + test('createPlugin fails on hash mismatch (bad hash)', async () => { const [err, plugin] = await createPlugin( { wasm: [ { - url: "http://localhost:8124/wasm/code.wasm", - hash: "not a good hash", + url: 'http://localhost:8124/wasm/code.wasm', + hash: 'not a good hash', }, ], }, @@ -164,14 +134,13 @@ if (typeof WebAssembly === "undefined") { } }); - test("createPlugin fails on hash mismatch (hash mismatch)", async () => { + test('createPlugin fails on hash mismatch (hash mismatch)', async () => { const [err, plugin] = await createPlugin( { wasm: [ { - url: "http://localhost:8124/wasm/code.wasm", - hash: - "93898457953d30d016f712ccf4336ce7e9971db5f7f3aff1edd252764f75d5d7", + url: 'http://localhost:8124/wasm/code.wasm', + hash: '93898457953d30d016f712ccf4336ce7e9971db5f7f3aff1edd252764f75d5d7', }, ], }, @@ -188,36 +157,39 @@ if (typeof WebAssembly === "undefined") { } }); - test("createPlugin loads a module and provides access to exports/imports", async () => { - const plugin = await createPlugin({ - wasm: [{ url: "http://localhost:8124/wasm/code.wasm" }], - }, { useWasi: true }); + test('createPlugin loads a module and provides access to exports/imports', async () => { + const plugin = await createPlugin( + { + wasm: [{ url: 'http://localhost:8124/wasm/code.wasm' }], + }, + { useWasi: true }, + ); try { const exports = await plugin.getExports(); assert.deepEqual( exports.map((xs) => xs.name).sort(), - ["memory", "count_vowels", "__data_end", "__heap_base"].sort(), + ['memory', 'count_vowels', '__data_end', '__heap_base'].sort(), ); const imports = await plugin.getImports(); assert.deepEqual( imports.map((xs) => xs.name).sort(), [ - "alloc", - "config_get", - "error_set", - "input_length", - "input_load_u64", - "input_load_u8", - "length", - "load_u64", - "load_u8", - "output_set", - "store_u64", - "store_u8", - "var_get", - "var_set", + 'alloc', + 'config_get', + 'error_set', + 'input_length', + 'input_load_u64', + 'input_load_u8', + 'length', + 'load_u64', + 'load_u8', + 'output_set', + 'store_u64', + 'store_u8', + 'var_get', + 'var_set', ].sort(), ); } finally { @@ -225,104 +197,104 @@ if (typeof WebAssembly === "undefined") { } }); - test("createPlugin returns an interface that can call wasm functions", async () => { - const plugin = await createPlugin({ - wasm: [{ url: "http://localhost:8124/wasm/code.wasm" }], - }, { useWasi: true }); + test('createPlugin returns an interface that can call wasm functions', async () => { + const plugin = await createPlugin( + { + wasm: [{ url: 'http://localhost:8124/wasm/code.wasm' }], + }, + { useWasi: true }, + ); try { - const result = await plugin.call("count_vowels", "hello world"); - assert(result, "result is not null"); + const result = await plugin.call('count_vowels', 'hello world'); + assert(result, 'result is not null'); assert.deepEqual(JSON.parse(new TextDecoder().decode(result.buffer)), { count: 3, total: 3, - vowels: "aeiouAEIOU", + vowels: 'aeiouAEIOU', }); } finally { await plugin.close(); } }); - test("logging works as expected", async () => { + test('logging works as expected', async () => { const intercept: Record = {}; - const logLevel = - (level: string) => (message: string) => (intercept[level] = message); + const logLevel = (level: string) => (message: string) => (intercept[level] = message); // FIXME: we're using non-blocking log functions here; to properly preserve behavior we // should invoke these and wait on the host to return. const logger = Object.fromEntries( - ["info", "debug", "warn", "error", "trace"].map(( - lvl, - ) => [lvl, logLevel(lvl)]), + ['info', 'debug', 'warn', 'error', 'trace'].map((lvl) => [lvl, logLevel(lvl)]), ) as unknown as Console; const plugin = await createPlugin( - { wasm: [{ url: "http://localhost:8124/wasm/log.wasm" }] }, + { wasm: [{ url: 'http://localhost:8124/wasm/log.wasm' }] }, { useWasi: true, logger, logLevel: 'trace' }, ); try { - await plugin.call("run_test", ""); + await plugin.call('run_test', ''); assert.deepEqual(intercept, { - trace: "this is a trace log", - debug: "this is a debug log", - error: "this is an error log", - info: "this is an info log", - warn: "this is a warning log", + trace: 'this is a trace log', + debug: 'this is a debug log', + error: 'this is an error log', + info: 'this is an info log', + warn: 'this is a warning log', }); } finally { await plugin.close(); } }); - test("host functions may read info from context and return values", async () => { + test('host functions may read info from context and return values', async () => { let executed: any; const functions = { - "extism:host/user": { + 'extism:host/user': { hello_world(context: CallContext, off: bigint) { executed = context.read(off)?.string(); - return context.store("wow okay then"); + return context.store('wow okay then'); }, }, }; const plugin = await createPlugin( - { wasm: [{ url: "http://localhost:8124/wasm/code-functions.wasm" }] }, + { wasm: [{ url: 'http://localhost:8124/wasm/code-functions.wasm' }] }, { useWasi: true, functions }, ); try { - const output = await plugin.call("count_vowels", "hello world"); - assert.equal(output?.string(), "wow okay then"); + const output = await plugin.call('count_vowels', 'hello world'); + assert.equal(output?.string(), 'wow okay then'); assert.equal(executed, '{"count": 3}'); } finally { await plugin.close(); } }); - test("resetting the plugin unsets all existing pages", async () => { + test('resetting the plugin unsets all existing pages', async () => { const offsets: bigint[] = [0n, 0n]; let callContext: CallContext | null = null; const functions = { - "extism:host/user": { + 'extism:host/user': { hello_world(context: CallContext, off: bigint) { callContext = context; offsets[0] = off; - offsets[1] = context.store("wow okay then"); + offsets[1] = context.store('wow okay then'); return offsets[1]; }, }, }; const plugin = await createPlugin( - { wasm: [{ url: "http://localhost:8124/wasm/code-functions.wasm" }] }, + { wasm: [{ url: 'http://localhost:8124/wasm/code-functions.wasm' }] }, { useWasi: true, functions }, ); try { - const output = await plugin.call("count_vowels", "hello world"); - assert.equal(output?.string(), "wow okay then"); + const output = await plugin.call('count_vowels', 'hello world'); + assert.equal(output?.string(), 'wow okay then'); await plugin.reset(); @@ -336,22 +308,22 @@ if (typeof WebAssembly === "undefined") { } }); - test("host functions reject original promise when throwing", async () => { + test('host functions reject original promise when throwing', async () => { const expected = String(Math.random()); const functions = { - "extism:host/user": { + 'extism:host/user': { hello_world(_context: CallContext, _off: bigint) { throw new Error(expected); }, }, }; const plugin = await createPlugin( - { wasm: [{ url: "http://localhost:8124/wasm/code-functions.wasm" }] }, + { wasm: [{ url: 'http://localhost:8124/wasm/code-functions.wasm' }] }, { useWasi: true, functions }, ); try { - const [err, data] = await plugin.call("count_vowels", "hello world").then( + const [err, data] = await plugin.call('count_vowels', 'hello world').then( (data) => [null, data], (err) => [err, null], ); @@ -363,66 +335,56 @@ if (typeof WebAssembly === "undefined") { } }); - test("plugin can get/set variables", async () => { - const plugin = await createPlugin("http://localhost:8124/wasm/var.wasm", { + test('plugin can get/set variables', async () => { + const plugin = await createPlugin('http://localhost:8124/wasm/var.wasm', { useWasi: true, }); try { - const [err, data] = await plugin.call("run_test").then( + const [err, data] = await plugin.call('run_test').then( (data) => [null, data], (err) => [err, null], ); assert.equal(err, null); - assert.equal(data.string(), "a: 0"); + assert.equal(data.string(), 'a: 0'); } finally { await plugin.close(); } }); - test("plugins cannot allocate more var bytes than allowed", async () => { + test('plugins cannot allocate more var bytes than allowed', async () => { const plugin = await createPlugin( { - wasm: [{ url: "http://localhost:8124/wasm/memory.wasm" }], + wasm: [{ url: 'http://localhost:8124/wasm/memory.wasm' }], memory: { maxVarBytes: 100 }, }, { useWasi: true }, ); try { - const [err, _] = await plugin.call( - "alloc_var", - JSON.stringify({ bytes: 1024 }), - ).then( + const [err, _] = await plugin.call('alloc_var', JSON.stringify({ bytes: 1024 })).then( (data) => [null, data], (err) => [err, null], ); assert(err); - assert( - /var memory limit exceeded: 1024 bytes requested, 100 allowed/.test( - err.message, - ), - ); + assert(/var memory limit exceeded: 1024 bytes requested, 100 allowed/.test(err.message)); } finally { await plugin.close(); } }); - test("plugins can allocate var bytes if allowed", async () => { + test('plugins can allocate var bytes if allowed', async () => { const plugin = await createPlugin( { - wasm: [{ url: "http://localhost:8124/wasm/memory.wasm" }], + wasm: [{ url: 'http://localhost:8124/wasm/memory.wasm' }], memory: { maxVarBytes: 1024 }, }, { useWasi: true }, ); try { - const [err, _] = await plugin.call( - "alloc_var", - JSON.stringify({ bytes: 1024 }), - ).then( + const [err, _] = await plugin.call('alloc_var', JSON.stringify({ bytes: 1024 })).then( (data) => [null, data], (err) => [err, null], ); @@ -433,47 +395,46 @@ if (typeof WebAssembly === "undefined") { } }); - test("plugins can link", async () => { + test('plugins can link', async () => { const plugin = await createPlugin({ wasm: [ - { name: "main", url: "http://localhost:8124/wasm/reflect.wasm" }, + { name: 'main', url: 'http://localhost:8124/wasm/reflect.wasm' }, { - name: "extism:host/user", - url: "http://localhost:8124/wasm/upper.wasm", + name: 'extism:host/user', + url: 'http://localhost:8124/wasm/upper.wasm', }, ], }); try { - const [err, data] = await plugin.call("reflect", "Hello, world!").then( + const [err, data] = await plugin.call('reflect', 'Hello, world!').then( (data) => [null, data], (err) => [err, null], ); assert.equal(err, null); - assert.equal(data.string(), "HELLO, WORLD!"); + assert.equal(data.string(), 'HELLO, WORLD!'); } finally { await plugin.close(); } }); - test("plugin linking: circular func deps are supported", async () => { + test('plugin linking: circular func deps are supported', async () => { const plugin = await createPlugin({ wasm: [ // these deps also share a memory - { name: "lhs", url: "http://localhost:8124/wasm/circular-lhs.wasm" }, - { name: "rhs", url: "http://localhost:8124/wasm/circular-rhs.wasm" }, - { name: "main", url: "http://localhost:8124/wasm/circular.wasm" }, + { name: 'lhs', url: 'http://localhost:8124/wasm/circular-lhs.wasm' }, + { name: 'rhs', url: 'http://localhost:8124/wasm/circular-rhs.wasm' }, + { name: 'main', url: 'http://localhost:8124/wasm/circular.wasm' }, ], }); try { // this plugin starts with 1, multiplies by two, adds one, ... recursively, until it's greater than 100. - const [err, data] = await plugin.call("encalculate", "Hello, world!") - .then( - (data) => [null, data], - (err) => [err, null], - ); + const [err, data] = await plugin.call('encalculate', 'Hello, world!').then( + (data) => [null, data], + (err) => [err, null], + ); assert.equal(err, null); assert.equal(data.getBigUint64(0, true), 127); @@ -482,11 +443,11 @@ if (typeof WebAssembly === "undefined") { } }); - test("plugin linking: missing deps are messaged", async () => { + test('plugin linking: missing deps are messaged', async () => { const [err, plugin] = await createPlugin({ wasm: [ - { name: "lhs", url: "http://localhost:8124/wasm/circular-lhs.wasm" }, - { name: "main", url: "http://localhost:8124/wasm/circular.wasm" }, + { name: 'lhs', url: 'http://localhost:8124/wasm/circular-lhs.wasm' }, + { name: 'main', url: 'http://localhost:8124/wasm/circular.wasm' }, ], }).then( (data) => [null, data], @@ -504,128 +465,123 @@ if (typeof WebAssembly === "undefined") { } }); - test("input data respects byte offsets and lengths", async () => { + test('input data respects byte offsets and lengths', async () => { const plugin = await createPlugin({ wasm: [ - { name: "main", url: "http://localhost:8124/wasm/reflect.wasm" }, + { name: 'main', url: 'http://localhost:8124/wasm/reflect.wasm' }, { - name: "extism:host/user", - url: "http://localhost:8124/wasm/upper.wasm", + name: 'extism:host/user', + url: 'http://localhost:8124/wasm/upper.wasm', }, ], }); const arrayBuffer = new ArrayBuffer(8192); - const view = new Uint8Array(arrayBuffer, 10, "Hello world!".length); - new TextEncoder().encodeInto("Hello world!", view); + const view = new Uint8Array(arrayBuffer, 10, 'Hello world!'.length); + new TextEncoder().encodeInto('Hello world!', view); try { - const [err, data] = await plugin.call("reflect", view).then( + const [err, data] = await plugin.call('reflect', view).then( (data) => [null, data], (err) => [err, null], ); assert.equal(err, null); - assert.equal(data.string(), "HELLO WORLD!"); + assert.equal(data.string(), 'HELLO WORLD!'); } finally { await plugin.close(); } }); if (CAPABILITIES.hasWorkerCapability) { - test("host functions may be async if worker is off-main-thread", async () => { + test('host functions may be async if worker is off-main-thread', async () => { const functions = { - "extism:host/user": { + 'extism:host/user': { async hello_world(context: CallContext, _off: bigint) { await new Promise((resolve) => setTimeout(resolve, 100)); - return context.store("it works"); + return context.store('it works'); }, }, }; const plugin = await createPlugin( - { wasm: [{ url: "http://localhost:8124/wasm/code-functions.wasm" }] }, + { wasm: [{ url: 'http://localhost:8124/wasm/code-functions.wasm' }] }, { useWasi: true, functions, runInWorker: true }, ); try { - const output = await plugin.call("count_vowels", "hello world"); - assert.equal(output?.string(), "it works"); + const output = await plugin.call('count_vowels', 'hello world'); + assert.equal(output?.string(), 'it works'); } finally { await plugin.close(); } }); - test("plugin callcontext reflects vars set in plugin", async () => { - let seen: string = "nope nope"; - let key: string = "nope nope"; - const plugin = await createPlugin( - "http://localhost:8124/wasm/02-var-reflected.wasm", - { - useWasi: true, - runInWorker: true, - functions: { - user: { - async test(callContext, n) { - key = callContext.read(n)!.text()!; - seen = callContext.getVariable(callContext.read(n)!.text())! - .text(); - }, + test('plugin callcontext reflects vars set in plugin', async () => { + let seen: string = 'nope nope'; + let key: string = 'nope nope'; + const plugin = await createPlugin('http://localhost:8124/wasm/02-var-reflected.wasm', { + useWasi: true, + runInWorker: true, + functions: { + user: { + async test(callContext, n) { + key = callContext.read(n)!.text()!; + seen = callContext.getVariable(callContext.read(n)!.text())!.text(); }, }, }, - ); + }); try { // This plugin has a value in memory, "hi there". It writes that variable into // extism memory, then stores that as an extism var -- mapping "hi there" => "hi there". // (This is just out of expedience so we don't have to store another value!) We then // call the host function with the result of 'var_get "hi there"'; so we're testing // that the guest and host have the _same_ view of variables. - const [err, _] = await plugin.call("test").then( + const [err, _] = await plugin.call('test').then( (data) => [null, data], (err) => [err, null], ); assert(!err); - assert.equal(key, "hi there"); - assert.equal(seen, "hi there"); + assert.equal(key, 'hi there'); + assert.equal(seen, 'hi there'); } finally { await plugin.close(); } }); if (CAPABILITIES.supportsTimeouts) { - test("timeout works on call()", async () => { + test('timeout works on call()', async () => { const plugin = await createPlugin( - { wasm: [{ url: "http://localhost:8124/wasm/loop-forever.wasm" }] }, + { wasm: [{ url: 'http://localhost:8124/wasm/loop-forever.wasm' }] }, { useWasi: true, timeoutMs: 250, runInWorker: true }, ); try { - const [err, output] = await plugin.call("loop", "hello world").then( + const [err, output] = await plugin.call('loop', 'hello world').then( (res) => [, res], (err) => [err], ); if (output) { - assert.fail("Expected no output"); + assert.fail('Expected no output'); } - assert.equal( - err!.message, - `EXTISM: call canceled due to timeout`, - ); + assert.equal(err!.message, `EXTISM: call canceled due to timeout`); } finally { await plugin.close(); } }); - test("timeout applies to initialization", async () => { + test('timeout applies to initialization', async () => { const [err, plugin] = await createPlugin( { - wasm: [{ - url: "http://localhost:8124/wasm/loop-forever-init.wasm", - }], + wasm: [ + { + url: 'http://localhost:8124/wasm/loop-forever-init.wasm', + }, + ], }, { useWasi: true, timeoutMs: 250, runInWorker: true }, ).then( @@ -635,53 +591,47 @@ if (typeof WebAssembly === "undefined") { if (plugin) { await plugin.close(); - assert.fail("Expected no output"); + assert.fail('Expected no output'); } - assert.equal( - err!.message, - `EXTISM: timed out while waiting for plugin to instantiate`, - ); + assert.equal(err!.message, `EXTISM: timed out while waiting for plugin to instantiate`); }); } - test("host functions preserve call context", async () => { - const one = { hi: "there" }; + test('host functions preserve call context', async () => { + const one = { hi: 'there' }; let seen: typeof one | null = null; const functions = { - "extism:host/user": { + 'extism:host/user': { async hello_world(context: CallContext, _off: bigint) { seen = context.hostContext<{ hi: string }>(); await new Promise((resolve) => setTimeout(resolve, 100)); - return context.store("it works"); + return context.store('it works'); }, }, }; const plugin = await createPlugin( - { wasm: [{ url: "http://localhost:8124/wasm/code-functions.wasm" }] }, + { wasm: [{ url: 'http://localhost:8124/wasm/code-functions.wasm' }] }, { useWasi: true, functions, runInWorker: true }, ); try { - const output = await plugin.call("count_vowels", "hello world", one); - assert.equal(output?.string(), "it works"); - assert.strictEqual(seen, one, "we preserved the host context"); + const output = await plugin.call('count_vowels', 'hello world', one); + assert.equal(output?.string(), 'it works'); + assert.strictEqual(seen, one, 'we preserved the host context'); } finally { await plugin.close(); } }); - test("test writes that span multiple blocks (w/small buffer)", async () => { - const value = "9:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ".repeat(18428 / 34); + test('test writes that span multiple blocks (w/small buffer)', async () => { + const value = '9:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ'.repeat(18428 / 34); const functions = { - "extism:host/user": { + 'extism:host/user': { async hello_world(context: CallContext, _off: bigint) { - context.setVariable( - "hmmm okay storing a variable", - "hello world hello.", - ); + context.setVariable('hmmm okay storing a variable', 'hello world hello.'); const result = new TextEncoder().encode(value); const ret = context.store(result); return ret; @@ -690,7 +640,7 @@ if (typeof WebAssembly === "undefined") { }; const plugin = await createPlugin( - { wasm: [{ url: "http://localhost:8124/wasm/code-functions.wasm" }] }, + { wasm: [{ url: 'http://localhost:8124/wasm/code-functions.wasm' }] }, { useWasi: true, functions, @@ -702,78 +652,76 @@ if (typeof WebAssembly === "undefined") { let i = 0; try { for (; i < 10; ++i) { - const output = await plugin.call("count_vowels", "hello world"); + const output = await plugin.call('count_vowels', 'hello world'); assert.equal(output?.string(), value); } - const again = await plugin.call("count_vowels", "hello world"); + const again = await plugin.call('count_vowels', 'hello world'); assert.equal(again?.string(), value); } finally { await plugin.close(); } }); - test("host functions may not be reentrant off-main-thread", async () => { + test('host functions may not be reentrant off-main-thread', async () => { const functions = { - "extism:host/user": { + 'extism:host/user': { async hello_world(context: CallContext, _off: bigint) { - await plugin?.call("count_vowels", "hello world"); - return context.store("it works"); + await plugin?.call('count_vowels', 'hello world'); + return context.store('it works'); }, }, }; const plugin = await createPlugin( - { wasm: [{ url: "http://localhost:8124/wasm/code-functions.wasm" }] }, + { wasm: [{ url: 'http://localhost:8124/wasm/code-functions.wasm' }] }, { useWasi: true, functions, runInWorker: true }, ); try { - const [err, data] = await plugin.call("count_vowels", "hello world") - .then( - (data) => [null, data], - (err) => [err, null], - ); + const [err, data] = await plugin.call('count_vowels', 'hello world').then( + (data) => [null, data], + (err) => [err, null], + ); assert(data === null); - assert.equal(err?.message, "plugin is not reentrant"); + assert.equal(err?.message, 'plugin is not reentrant'); } finally { await plugin.close(); } }); if (!CAPABILITIES.crossOriginChecksEnforced) { - test("http fails as expected when no allowed hosts match", async () => { + test('http fails as expected when no allowed hosts match', async () => { const functions = { - "extism:host/user": { + 'extism:host/user': { async hello_world(context: CallContext, _off: bigint) { await new Promise((resolve) => setTimeout(resolve, 100)); - return context.store("it works"); + return context.store('it works'); }, }, }; const plugin = await createPlugin( { - wasm: [{ - name: "main", - url: "http://localhost:8124/wasm/http.wasm", - }], + wasm: [ + { + name: 'main', + url: 'http://localhost:8124/wasm/http.wasm', + }, + ], }, { useWasi: true, functions, runInWorker: true, - allowedHosts: ["*.example.com"], + allowedHosts: ['*.example.com'], }, ); try { const [err, data] = await plugin - .call( - "http_get", - '{"url": "https://jsonplaceholder.typicode.com/todos/1"}', - ) + .call('http_get', '{"url": "https://jsonplaceholder.typicode.com/todos/1"}') .then( (data) => [null, data], (err) => [err, null], @@ -790,11 +738,11 @@ if (typeof WebAssembly === "undefined") { }); } - test("http works as expected when host is allowed", async () => { + test('http works as expected when host is allowed', async () => { const plugin = await createPlugin( { - wasm: [{ name: "main", url: "http://localhost:8124/wasm/http.wasm" }], - allowedHosts: ["*.typicode.com"], + wasm: [{ name: 'main', url: 'http://localhost:8124/wasm/http.wasm' }], + allowedHosts: ['*.typicode.com'], memory: { maxHttpResponseBytes: 100 * 1024 * 1024 }, }, { useWasi: true, functions: {}, runInWorker: true }, @@ -802,10 +750,7 @@ if (typeof WebAssembly === "undefined") { try { const [err, data] = await plugin - .call( - "http_get", - '{"url": "https://jsonplaceholder.typicode.com/todos/1"}', - ) + .call('http_get', '{"url": "https://jsonplaceholder.typicode.com/todos/1"}') .then( (data) => [null, data], (err) => [err, null], @@ -814,7 +759,7 @@ if (typeof WebAssembly === "undefined") { assert.deepEqual(data.json(), { userId: 1, id: 1, - title: "delectus aut autem", + title: 'delectus aut autem', completed: false, }); } finally { @@ -822,26 +767,21 @@ if (typeof WebAssembly === "undefined") { } }); - test("http fails when body is larger than allowed", async () => { + test('http fails when body is larger than allowed', async () => { const plugin = await createPlugin( { - wasm: [{ name: "main", url: "http://localhost:8124/wasm/http.wasm" }], - allowedHosts: ["*.typicode.com"], + wasm: [{ name: 'main', url: 'http://localhost:8124/wasm/http.wasm' }], + allowedHosts: ['*.typicode.com'], memory: { maxHttpResponseBytes: 1 }, }, { useWasi: true, functions: {}, runInWorker: true }, ); try { - const [err, _] = await plugin - .call( - "http_get", - '{"url": "https://jsonplaceholder.typicode.com/todos/1"}', - ) - .then( - (data) => [null, data], - (err) => [err, null], - ); + const [err, _] = await plugin.call('http_get', '{"url": "https://jsonplaceholder.typicode.com/todos/1"}').then( + (data) => [null, data], + (err) => [err, null], + ); assert(err); } finally { @@ -849,21 +789,18 @@ if (typeof WebAssembly === "undefined") { } }); - test("we fallback to Manifest.allowedHosts if ExtismPluginOptions.allowedHosts is not specified", async () => { + test('we fallback to Manifest.allowedHosts if ExtismPluginOptions.allowedHosts is not specified', async () => { const plugin = await createPlugin( { - wasm: [{ name: "main", url: "http://localhost:8124/wasm/http.wasm" }], - allowedHosts: ["*.typicode.com"], + wasm: [{ name: 'main', url: 'http://localhost:8124/wasm/http.wasm' }], + allowedHosts: ['*.typicode.com'], }, { useWasi: true, functions: {}, runInWorker: true }, ); try { const [err, data] = await plugin - .call( - "http_get", - '{"url": "https://jsonplaceholder.typicode.com/todos/1"}', - ) + .call('http_get', '{"url": "https://jsonplaceholder.typicode.com/todos/1"}') .then( (data) => [null, data], (err) => [err, null], @@ -872,7 +809,7 @@ if (typeof WebAssembly === "undefined") { assert.deepEqual(data.json(), { userId: 1, id: 1, - title: "delectus aut autem", + title: 'delectus aut autem', completed: false, }); } finally { @@ -881,41 +818,37 @@ if (typeof WebAssembly === "undefined") { }); } - test("createPlugin fails as expected when calling unknown function", async () => { - const plugin = await createPlugin("http://localhost:8124/wasm/code.wasm", { + test('createPlugin fails as expected when calling unknown function', async () => { + const plugin = await createPlugin('http://localhost:8124/wasm/code.wasm', { useWasi: true, }); try { - const [err, data] = await plugin.call("reticulate_splines", "hello world") - .then( - (data) => [null, data], - (err) => [err, null], - ); + const [err, data] = await plugin.call('reticulate_splines', 'hello world').then( + (data) => [null, data], + (err) => [err, null], + ); assert(data === null); - assert.equal( - err?.message, - 'Plugin error: function "reticulate_splines" does not exist', - ); + assert.equal(err?.message, 'Plugin error: function "reticulate_splines" does not exist'); } finally { await plugin.close(); } }); - test("plugin can allocate memory", async () => { - const plugin = await createPlugin("http://localhost:8124/wasm/alloc.wasm"); + test('plugin can allocate memory', async () => { + const plugin = await createPlugin('http://localhost:8124/wasm/alloc.wasm'); try { - await plugin.call("run_test", ""); + await plugin.call('run_test', ''); } finally { await plugin.close(); } }); - test("plugins cant allocate more memory than allowed", async () => { + test('plugins cant allocate more memory than allowed', async () => { const plugin = await createPlugin( { - wasm: [{ url: "http://localhost:8124/wasm/memory.wasm" }], + wasm: [{ url: 'http://localhost:8124/wasm/memory.wasm' }], memory: { maxPages: 2 }, }, { useWasi: true }, @@ -924,10 +857,7 @@ if (typeof WebAssembly === "undefined") { const pageSize = 64 * 1024; try { - const [err, _] = await plugin.call( - "alloc_memory", - JSON.stringify({ bytes: pageSize * 5 }), - ).then( + const [err, _] = await plugin.call('alloc_memory', JSON.stringify({ bytes: pageSize * 5 })).then( (data) => [null, data], (err) => [err, null], ); @@ -938,10 +868,10 @@ if (typeof WebAssembly === "undefined") { } }); - test("plugins can allocate memory if allowed", async () => { + test('plugins can allocate memory if allowed', async () => { const plugin = await createPlugin( { - wasm: [{ url: "http://localhost:8124/wasm/memory.wasm" }], + wasm: [{ url: 'http://localhost:8124/wasm/memory.wasm' }], memory: { maxPages: 6 }, }, { useWasi: true }, @@ -950,10 +880,7 @@ if (typeof WebAssembly === "undefined") { const pageSize = 64 * 1024; try { - const [err, _] = await plugin.call( - "alloc_memory", - JSON.stringify({ bytes: pageSize * 5 }), - ).then( + const [err, _] = await plugin.call('alloc_memory', JSON.stringify({ bytes: pageSize * 5 })).then( (data) => [null, data], (err) => [err, null], ); @@ -964,73 +891,68 @@ if (typeof WebAssembly === "undefined") { } }); - test("plugin can call input_offset", async () => { - const plugin = await createPlugin( - "http://localhost:8124/wasm/input_offset.wasm", - ); + test('plugin can call input_offset', async () => { + const plugin = await createPlugin('http://localhost:8124/wasm/input_offset.wasm'); try { - const input = "hello world"; - const hw = await plugin.call("input_offset_length", input); + const input = 'hello world'; + const hw = await plugin.call('input_offset_length', input); assert.equal(hw?.getBigUint64(0, true), input.length); } finally { await plugin.close(); } }); - test("plugin can fail gracefully", async () => { - const plugin = await createPlugin("http://localhost:8124/wasm/fail.wasm"); + test('plugin can fail gracefully', async () => { + const plugin = await createPlugin('http://localhost:8124/wasm/fail.wasm'); try { - const [err, data] = await plugin.call("run_test", "").then( + const [err, data] = await plugin.call('run_test', '').then( (data) => [null, data], (err) => [err, null], ); assert(data === null); - assert.equal(err.message, "Plugin-originated error: Some error message"); + assert.equal(err.message, 'Plugin-originated error: Some error message'); } finally { await plugin.close(); } }); if (CAPABILITIES.supportsWasiPreview1) { - test("can initialize Haskell runtime", async () => { - const plugin = await createPlugin( - "http://localhost:8124/wasm/hello_haskell.wasm", - { - config: { greeting: "Howdy" }, - useWasi: true, - }, - ); + test('can initialize Haskell runtime', async () => { + const plugin = await createPlugin('http://localhost:8124/wasm/hello_haskell.wasm', { + config: { greeting: 'Howdy' }, + useWasi: true, + }); try { - let output = await plugin.call("testing", "John"); + let output = await plugin.call('testing', 'John'); - assert.equal(output?.string(), "Howdy, John"); + assert.equal(output?.string(), 'Howdy, John'); - output = await plugin.call("testing", "Ben"); + output = await plugin.call('testing', 'Ben'); assert(output !== null); - assert.equal(output?.string(), "Howdy, Ben"); + assert.equal(output?.string(), 'Howdy, Ben'); } finally { await plugin.close(); } }); - test("we fallback to Manifest.config if ExtismPluginOptions.config is not specified", async () => { + test('we fallback to Manifest.config if ExtismPluginOptions.config is not specified', async () => { const plugin = await createPlugin( { - wasm: [{ url: "http://localhost:8124/wasm/hello_haskell.wasm" }], - config: { greeting: "Howdy" }, + wasm: [{ url: 'http://localhost:8124/wasm/hello_haskell.wasm' }], + config: { greeting: 'Howdy' }, }, { useWasi: true }, ); try { - let output = await plugin.call("testing", "John"); + let output = await plugin.call('testing', 'John'); - assert.equal(output?.string(), "Howdy, John"); + assert.equal(output?.string(), 'Howdy, John'); - output = await plugin.call("testing", "Ben"); + output = await plugin.call('testing', 'Ben'); assert(output !== null); - assert.equal(output?.string(), "Howdy, Ben"); + assert.equal(output?.string(), 'Howdy, Ben'); } finally { await plugin.close(); } @@ -1039,52 +961,40 @@ if (typeof WebAssembly === "undefined") { // TODO(chrisdickinson): this turns out to be pretty tricky to test, since // deno and node's wasi bindings bypass JS entirely and write directly to // their respective FDs. I'm settling for tests that exercise both behaviors. - test("when EXTISM_ENABLE_WASI_OUTPUT is not set, WASI output is stifled", async () => { + test('when EXTISM_ENABLE_WASI_OUTPUT is not set, WASI output is stifled', async () => { if ((globalThis as unknown as any).process) { ( - globalThis as unknown as Record< - string, - { env: Record } - > - ).process.env.EXTISM_ENABLE_WASI_OUTPUT = ""; + globalThis as unknown as Record }> + ).process.env.EXTISM_ENABLE_WASI_OUTPUT = ''; } else if ((globalThis as unknown as any).Deno) { - globalThis.Deno.env.set("EXTISM_ENABLE_WASI_OUTPUT", ""); + globalThis.Deno.env.set('EXTISM_ENABLE_WASI_OUTPUT', ''); } - const plugin = await createPlugin( - "http://localhost:8124/wasm/wasistdout.wasm", - { - useWasi: true, - }, - ); + const plugin = await createPlugin('http://localhost:8124/wasm/wasistdout.wasm', { + useWasi: true, + }); try { - await plugin.call("say_hello"); + await plugin.call('say_hello'); } finally { await plugin.close(); } }); - test("respects enableWasiOutput", async () => { + test('respects enableWasiOutput', async () => { if ((globalThis as unknown as any).process) { ( - globalThis as unknown as Record< - string, - { env: Record } - > - ).process.env.EXTISM_ENABLE_WASI_OUTPUT = ""; + globalThis as unknown as Record }> + ).process.env.EXTISM_ENABLE_WASI_OUTPUT = ''; } else if ((globalThis as unknown as any).Deno) { - globalThis.Deno.env.set("EXTISM_ENABLE_WASI_OUTPUT", ""); + globalThis.Deno.env.set('EXTISM_ENABLE_WASI_OUTPUT', ''); } - const plugin = await createPlugin( - "http://localhost:8124/wasm/wasistdout.wasm", - { - useWasi: true, - enableWasiOutput: true, - }, - ); + const plugin = await createPlugin('http://localhost:8124/wasm/wasistdout.wasm', { + useWasi: true, + enableWasiOutput: true, + }); try { - await plugin.call("say_hello"); + await plugin.call('say_hello'); } finally { await plugin.close(); } @@ -1092,81 +1002,78 @@ if (typeof WebAssembly === "undefined") { } if (CAPABILITIES.fsAccess && CAPABILITIES.supportsWasiPreview1) { - test("readonly allowed paths are not supported", async () => { + test('readonly allowed paths are not supported', async () => { try { await createPlugin( { - wasm: [{ name: "main", url: "http://localhost:8124/wasm/fs.wasm" }], - allowedPaths: { "/mnt": "ro:tests/data" }, + wasm: [{ name: 'main', url: 'http://localhost:8124/wasm/fs.wasm' }], + allowedPaths: { '/mnt': 'ro:tests/data' }, }, { useWasi: true, functions: {}, runInWorker: true }, ); - assert.fail("should not reach here"); + assert.fail('should not reach here'); } catch (err) { if (err instanceof Error) { - assert.equal( - err.message, - "Readonly dirs are not supported: ro:tests/data", - ); + assert.equal(err.message, 'Readonly dirs are not supported: ro:tests/data'); } } }); - test("can access fs", async () => { - const plugin = await createPlugin("http://localhost:8124/wasm/fs.wasm", { - allowedPaths: { "/mnt": "tests/data" }, + test('can access fs', async () => { + const plugin = await createPlugin('http://localhost:8124/wasm/fs.wasm', { + allowedPaths: { '/mnt': 'tests/data' }, useWasi: true, }); try { - const output = await plugin.call("run_test", ""); + const output = await plugin.call('run_test', ''); assert(output !== null); const result = output.string(); - assert.equal(result, "hello world!"); + assert.equal(result, 'hello world!'); } finally { await plugin.close(); } }); - test("we fallback to Manifest.allowedPaths if ExtismPluginOptions.allowedPaths is not specified", async () => { + test('we fallback to Manifest.allowedPaths if ExtismPluginOptions.allowedPaths is not specified', async () => { const plugin = await createPlugin( { - wasm: [{ url: "http://localhost:8124/wasm/fs.wasm" }], - allowedPaths: { "/mnt": "tests/data" }, + wasm: [{ url: 'http://localhost:8124/wasm/fs.wasm' }], + allowedPaths: { '/mnt': 'tests/data' }, }, { useWasi: true }, ); try { - const output = await plugin.call("run_test", ""); + const output = await plugin.call('run_test', ''); assert(output !== null); const result = output.string(); - assert.equal(result, "hello world!"); + assert.equal(result, 'hello world!'); } finally { await plugin.close(); } }); - test("linking to a wasi command side-module works", async () => { + test('linking to a wasi command side-module works', async () => { const plugin = await createPlugin( { wasm: [ - { name: "side", url: "http://localhost:8124/wasm/fs.wasm" }, - { name: "main", url: "http://localhost:8124/wasm/fs-link.wasm" }, + { name: 'side', url: 'http://localhost:8124/wasm/fs.wasm' }, + { name: 'main', url: 'http://localhost:8124/wasm/fs-link.wasm' }, ], }, { - allowedPaths: { "/mnt": "tests/data" }, + allowedPaths: { '/mnt': 'tests/data' }, useWasi: true, }, ); try { - const output = await plugin.call("run_test", ""); + const output = await plugin.call('run_test', ''); assert(output !== null); const result = output.string(); - assert.equal(result, "hello world!"); + assert.equal(result, 'hello world!'); } finally { await plugin.close(); } diff --git a/src/mod.ts b/src/mod.ts index 04f23e0..a2746bc 100644 --- a/src/mod.ts +++ b/src/mod.ts @@ -1,4 +1,4 @@ -import { CAPABILITIES } from "./polyfills/deno-capabilities.ts"; +import { CAPABILITIES } from './polyfills/deno-capabilities.ts'; import { logLevelToPriority, @@ -6,14 +6,14 @@ import { type InternalConfig, type ManifestLike, type Plugin, -} from "./interfaces.ts"; +} from './interfaces.ts'; -import { toWasmModuleData as _toWasmModuleData } from "./manifest.ts"; +import { toWasmModuleData as _toWasmModuleData } from './manifest.ts'; -import { createForegroundPlugin as _createForegroundPlugin } from "./foreground-plugin.ts"; -import { createBackgroundPlugin as _createBackgroundPlugin } from "./background-plugin.ts"; +import { createForegroundPlugin as _createForegroundPlugin } from './foreground-plugin.ts'; +import { createBackgroundPlugin as _createBackgroundPlugin } from './background-plugin.ts'; -export { CAPABILITIES } from "./polyfills/deno-capabilities.ts"; +export { CAPABILITIES } from './polyfills/deno-capabilities.ts'; export type { Capabilities, @@ -32,12 +32,9 @@ export type { PluginConfig, PluginConfigLike, PluginOutput, -} from "./interfaces.ts"; +} from './interfaces.ts'; -export type { - CallContext, - CallContext as CurrentPlugin, -} from "./call-context.ts"; +export type { CallContext, CallContext as CurrentPlugin } from './call-context.ts'; /** * Create a {@link Plugin} given a {@link ManifestLike} and {@link ExtismPluginOptions}. @@ -82,9 +79,7 @@ export async function createPlugin( ): Promise { opts = { ...opts }; opts.useWasi ??= false; - opts.enableWasiOutput ??= opts.useWasi - ? CAPABILITIES.extismStdoutEnvVarSet - : false; + opts.enableWasiOutput ??= opts.useWasi ? CAPABILITIES.extismStdoutEnvVarSet : false; opts.functions = opts.functions || {}; // TODO(chrisdickinson): reset this to `CAPABILITIES.hasWorkerCapability` once we've fixed https://github.com/extism/js-sdk/issues/46. @@ -119,14 +114,14 @@ export async function createPlugin( if (opts.runInWorker && !CAPABILITIES.hasWorkerCapability) { throw new Error( - "Cannot enable off-thread wasm; current context is not `crossOriginIsolated` (see https://mdn.io/crossOriginIsolated)", + 'Cannot enable off-thread wasm; current context is not `crossOriginIsolated` (see https://mdn.io/crossOriginIsolated)', ); } for (const guest in opts.allowedPaths) { const host = opts.allowedPaths[guest]; - if (host.startsWith("ro:")) { + if (host.startsWith('ro:')) { throw new Error(`Readonly dirs are not supported: ${host}`); } } @@ -146,11 +141,7 @@ export async function createPlugin( memory: opts.memory, }; - return (opts.runInWorker ? _createBackgroundPlugin : _createForegroundPlugin)( - ic, - names, - moduleData, - ); + return (opts.runInWorker ? _createBackgroundPlugin : _createForegroundPlugin)(ic, names, moduleData); } export { createPlugin as newPlugin }; diff --git a/src/polyfills/bun-capabilities.ts b/src/polyfills/bun-capabilities.ts index 09cda0f..5e5c884 100644 --- a/src/polyfills/bun-capabilities.ts +++ b/src/polyfills/bun-capabilities.ts @@ -1,5 +1,5 @@ import type { Capabilities } from '../interfaces.ts'; -import { platform } from 'node:os' +import { platform } from 'node:os'; export const CAPABILITIES: Capabilities = { // When false, shared buffers have to be copied to an array diff --git a/src/polyfills/deno-capabilities.ts b/src/polyfills/deno-capabilities.ts index 63583f2..29d01ab 100644 --- a/src/polyfills/deno-capabilities.ts +++ b/src/polyfills/deno-capabilities.ts @@ -21,5 +21,5 @@ export const CAPABILITIES: Capabilities = { supportsTimeouts: true, - extismStdoutEnvVarSet: Boolean(Deno.env.get('EXTISM_ENABLE_WASI_OUTPUT')) + extismStdoutEnvVarSet: Boolean(Deno.env.get('EXTISM_ENABLE_WASI_OUTPUT')), }; diff --git a/src/polyfills/deno-minimatch.ts b/src/polyfills/deno-minimatch.ts index f6a87c9..d999787 100644 --- a/src/polyfills/deno-minimatch.ts +++ b/src/polyfills/deno-minimatch.ts @@ -1,4 +1,4 @@ -import { minimatch } from "npm:minimatch@9.0.4"; +import { minimatch } from 'npm:minimatch@9.0.4'; export function matches(text: string, pattern: string): boolean { return minimatch(text, pattern); diff --git a/src/polyfills/deno-snapshot_preview1.ts b/src/polyfills/deno-snapshot_preview1.ts index 6a308a0..10e4d5d 100644 --- a/src/polyfills/deno-snapshot_preview1.ts +++ b/src/polyfills/deno-snapshot_preview1.ts @@ -73,8 +73,8 @@ * @module */ -import { relative } from "jsr:@std/path@0.223.0/relative"; -import { resolve } from "jsr:@std/path@0.223.0/resolve"; +import { relative } from 'jsr:@std/path@0.223.0/relative'; +import { resolve } from 'jsr:@std/path@0.223.0/resolve'; const CLOCKID_REALTIME = 0; const CLOCKID_MONOTONIC = 1; @@ -277,7 +277,7 @@ const _SDFLAGS_WR = 0x0002; const PREOPENTYPE_DIR = 0; function syscall(target: T) { - return function(...args: unknown[]) { + return function (...args: unknown[]) { try { return target(...args); } catch (err) { @@ -290,46 +290,46 @@ function syscall(target: T) { } switch (err.name) { - case "NotFound": + case 'NotFound': return ERRNO_NOENT; - case "PermissionDenied": + case 'PermissionDenied': return ERRNO_ACCES; - case "ConnectionRefused": + case 'ConnectionRefused': return ERRNO_CONNREFUSED; - case "ConnectionReset": + case 'ConnectionReset': return ERRNO_CONNRESET; - case "ConnectionAborted": + case 'ConnectionAborted': return ERRNO_CONNABORTED; - case "NotConnected": + case 'NotConnected': return ERRNO_NOTCONN; - case "AddrInUse": + case 'AddrInUse': return ERRNO_ADDRINUSE; - case "AddrNotAvailable": + case 'AddrNotAvailable': return ERRNO_ADDRNOTAVAIL; - case "BrokenPipe": + case 'BrokenPipe': return ERRNO_PIPE; - case "InvalidData": + case 'InvalidData': return ERRNO_INVAL; - case "TimedOut": + case 'TimedOut': return ERRNO_TIMEDOUT; - case "Interrupted": + case 'Interrupted': return ERRNO_INTR; - case "BadResource": + case 'BadResource': return ERRNO_BADF; - case "Busy": + case 'Busy': return ERRNO_BUSY; default: @@ -464,10 +464,7 @@ export default class Context { } this.exports = { - "args_get": syscall(( - argvOffset: number, - argvBufferOffset: number, - ): number => { + args_get: syscall((argvOffset: number, argvBufferOffset: number): number => { const args = this.#args; const textEncoder = new TextEncoder(); const memoryData = new Uint8Array(this.#memory.buffer); @@ -485,10 +482,7 @@ export default class Context { return ERRNO_SUCCESS; }), - "args_sizes_get": syscall(( - argcOffset: number, - argvBufferSizeOffset: number, - ): number => { + args_sizes_get: syscall((argcOffset: number, argvBufferSizeOffset: number): number => { const args = this.#args; const textEncoder = new TextEncoder(); const memoryView = new DataView(this.#memory.buffer); @@ -496,7 +490,7 @@ export default class Context { memoryView.setUint32(argcOffset, args.length, true); memoryView.setUint32( argvBufferSizeOffset, - args.reduce(function(acc, arg) { + args.reduce(function (acc, arg) { return acc + textEncoder.encode(`${arg}\0`).length; }, 0), true, @@ -505,10 +499,7 @@ export default class Context { return ERRNO_SUCCESS; }), - "environ_get": syscall(( - environOffset: number, - environBufferOffset: number, - ): number => { + environ_get: syscall((environOffset: number, environBufferOffset: number): number => { const entries = Object.entries(this.#env); const textEncoder = new TextEncoder(); const memoryData = new Uint8Array(this.#memory.buffer); @@ -526,10 +517,7 @@ export default class Context { return ERRNO_SUCCESS; }), - "environ_sizes_get": syscall(( - environcOffset: number, - environBufferSizeOffset: number, - ): number => { + environ_sizes_get: syscall((environcOffset: number, environBufferSizeOffset: number): number => { const entries = Object.entries(this.#env); const textEncoder = new TextEncoder(); const memoryView = new DataView(this.#memory.buffer); @@ -537,7 +525,7 @@ export default class Context { memoryView.setUint32(environcOffset, entries.length, true); memoryView.setUint32( environBufferSizeOffset, - entries.reduce(function(acc, [key, value]) { + entries.reduce(function (acc, [key, value]) { return acc + textEncoder.encode(`${key}=${value}\0`).length; }, 0), true, @@ -546,21 +534,14 @@ export default class Context { return ERRNO_SUCCESS; }), - "clock_res_get": syscall(( - id: number, - resolutionOffset: number, - ): number => { + clock_res_get: syscall((id: number, resolutionOffset: number): number => { const memoryView = new DataView(this.#memory.buffer); switch (id) { case CLOCKID_REALTIME: { const resolution = BigInt(1e6); - memoryView.setBigUint64( - resolutionOffset, - resolution, - true, - ); + memoryView.setBigUint64(resolutionOffset, resolution, true); break; } @@ -579,11 +560,7 @@ export default class Context { return ERRNO_SUCCESS; }), - "clock_time_get": syscall(( - id: number, - precision: bigint, - timeOffset: number, - ): number => { + clock_time_get: syscall((id: number, precision: bigint, timeOffset: number): number => { const memoryView = new DataView(this.#memory.buffer); switch (id) { @@ -613,26 +590,15 @@ export default class Context { return ERRNO_SUCCESS; }), - "fd_advise": syscall(( - _fd: number, - _offset: bigint, - _length: bigint, - _advice: number, - ): number => { + fd_advise: syscall((_fd: number, _offset: bigint, _length: bigint, _advice: number): number => { return ERRNO_NOSYS; }), - "fd_allocate": syscall(( - _fd: number, - _offset: bigint, - _length: bigint, - ): number => { + fd_allocate: syscall((_fd: number, _offset: bigint, _length: bigint): number => { return ERRNO_NOSYS; }), - "fd_close": syscall(( - fd: number, - ): number => { + fd_close: syscall((fd: number): number => { const entry = this.#fds[fd]; if (!entry) { return ERRNO_BADF; @@ -647,9 +613,7 @@ export default class Context { return ERRNO_SUCCESS; }), - "fd_datasync": syscall(( - fd: number, - ): number => { + fd_datasync: syscall((fd: number): number => { const entry = this.#fds[fd]; if (!entry) { return ERRNO_BADF; @@ -660,10 +624,7 @@ export default class Context { return ERRNO_SUCCESS; }), - "fd_fdstat_get": syscall(( - fd: number, - offset: number, - ): number => { + fd_fdstat_get: syscall((fd: number, offset: number): number => { const entry = this.#fds[fd]; if (!entry) { return ERRNO_BADF; @@ -680,25 +641,15 @@ export default class Context { return ERRNO_SUCCESS; }), - "fd_fdstat_set_flags": syscall(( - _fd: number, - _flags: number, - ): number => { + fd_fdstat_set_flags: syscall((_fd: number, _flags: number): number => { return ERRNO_NOSYS; }), - "fd_fdstat_set_rights": syscall(( - _fd: number, - _rightsBase: bigint, - _rightsInheriting: bigint, - ): number => { + fd_fdstat_set_rights: syscall((_fd: number, _rightsBase: bigint, _rightsInheriting: bigint): number => { return ERRNO_NOSYS; }), - "fd_filestat_get": syscall(( - fd: number, - offset: number, - ): number => { + fd_filestat_get: syscall((fd: number, offset: number): number => { const entry = this.#fds[fd]; if (!entry) { return ERRNO_BADF; @@ -743,34 +694,19 @@ export default class Context { memoryView.setBigUint64(offset, BigInt(info.size), true); offset += 8; - memoryView.setBigUint64( - offset, - BigInt(info.atime ? info.atime.getTime() * 1e6 : 0), - true, - ); + memoryView.setBigUint64(offset, BigInt(info.atime ? info.atime.getTime() * 1e6 : 0), true); offset += 8; - memoryView.setBigUint64( - offset, - BigInt(info.mtime ? info.mtime.getTime() * 1e6 : 0), - true, - ); + memoryView.setBigUint64(offset, BigInt(info.mtime ? info.mtime.getTime() * 1e6 : 0), true); offset += 8; - memoryView.setBigUint64( - offset, - BigInt(info.birthtime ? info.birthtime.getTime() * 1e6 : 0), - true, - ); + memoryView.setBigUint64(offset, BigInt(info.birthtime ? info.birthtime.getTime() * 1e6 : 0), true); offset += 8; return ERRNO_SUCCESS; }), - "fd_filestat_set_size": syscall(( - fd: number, - size: bigint, - ): number => { + fd_filestat_set_size: syscall((fd: number, size: bigint): number => { const entry = this.#fds[fd]; if (!entry) { return ERRNO_BADF; @@ -781,12 +717,7 @@ export default class Context { return ERRNO_SUCCESS; }), - "fd_filestat_set_times": syscall(( - fd: number, - atim: bigint, - mtim: bigint, - flags: number, - ): number => { + fd_filestat_set_times: syscall((fd: number, atim: bigint, mtim: bigint, flags: number): number => { const entry = this.#fds[fd]; if (!entry) { return ERRNO_BADF; @@ -809,47 +740,36 @@ export default class Context { return ERRNO_SUCCESS; }), - "fd_pread": syscall(( - fd: number, - iovsOffset: number, - iovsLength: number, - offset: bigint, - nreadOffset: number, - ): number => { - const entry = this.#fds[fd]; - if (entry === null) { - return ERRNO_BADF; - } + fd_pread: syscall( + (fd: number, iovsOffset: number, iovsLength: number, offset: bigint, nreadOffset: number): number => { + const entry = this.#fds[fd]; + if (entry === null) { + return ERRNO_BADF; + } - const seek = Deno.seekSync(entry.rid!, 0, Deno.SeekMode.Current); - const memoryView = new DataView(this.#memory.buffer); + const seek = Deno.seekSync(entry.rid!, 0, Deno.SeekMode.Current); + const memoryView = new DataView(this.#memory.buffer); - let nread = 0; - for (let i = 0; i < iovsLength; i++) { - const dataOffset = memoryView.getUint32(iovsOffset, true); - iovsOffset += 4; + let nread = 0; + for (let i = 0; i < iovsLength; i++) { + const dataOffset = memoryView.getUint32(iovsOffset, true); + iovsOffset += 4; - const dataLength = memoryView.getUint32(iovsOffset, true); - iovsOffset += 4; + const dataLength = memoryView.getUint32(iovsOffset, true); + iovsOffset += 4; - const data = new Uint8Array( - this.#memory.buffer, - dataOffset, - dataLength, - ); - nread += Deno.readSync(entry.rid!, data) as number; - } + const data = new Uint8Array(this.#memory.buffer, dataOffset, dataLength); + nread += Deno.readSync(entry.rid!, data) as number; + } - Deno.seekSync(entry.rid!, seek, Deno.SeekMode.Start); - memoryView.setUint32(nreadOffset, nread, true); + Deno.seekSync(entry.rid!, seek, Deno.SeekMode.Start); + memoryView.setUint32(nreadOffset, nread, true); - return ERRNO_SUCCESS; - }), + return ERRNO_SUCCESS; + }, + ), - "fd_prestat_get": syscall(( - fd: number, - prestatOffset: number, - ): number => { + fd_prestat_get: syscall((fd: number, prestatOffset: number): number => { const entry = this.#fds[fd]; if (!entry) { return ERRNO_BADF; @@ -861,20 +781,12 @@ export default class Context { const memoryView = new DataView(this.#memory.buffer); memoryView.setUint8(prestatOffset, PREOPENTYPE_DIR); - memoryView.setUint32( - prestatOffset + 4, - new TextEncoder().encode(entry.vpath).byteLength, - true, - ); + memoryView.setUint32(prestatOffset + 4, new TextEncoder().encode(entry.vpath).byteLength, true); return ERRNO_SUCCESS; }), - "fd_prestat_dir_name": syscall(( - fd: number, - pathOffset: number, - pathLength: number, - ): number => { + fd_prestat_dir_name: syscall((fd: number, pathOffset: number, pathLength: number): number => { const entry = this.#fds[fd]; if (!entry) { return ERRNO_BADF; @@ -884,59 +796,42 @@ export default class Context { return ERRNO_BADF; } - const data = new Uint8Array( - this.#memory.buffer, - pathOffset, - pathLength, - ); + const data = new Uint8Array(this.#memory.buffer, pathOffset, pathLength); data.set(new TextEncoder().encode(entry.vpath)); return ERRNO_SUCCESS; }), - "fd_pwrite": syscall(( - fd: number, - iovsOffset: number, - iovsLength: number, - offset: bigint, - nwrittenOffset: number, - ): number => { - const entry = this.#fds[fd]; - if (!entry) { - return ERRNO_BADF; - } + fd_pwrite: syscall( + (fd: number, iovsOffset: number, iovsLength: number, offset: bigint, nwrittenOffset: number): number => { + const entry = this.#fds[fd]; + if (!entry) { + return ERRNO_BADF; + } - const seek = Deno.seekSync(entry.rid!, 0, Deno.SeekMode.Current); - const memoryView = new DataView(this.#memory.buffer); + const seek = Deno.seekSync(entry.rid!, 0, Deno.SeekMode.Current); + const memoryView = new DataView(this.#memory.buffer); - let nwritten = 0; - for (let i = 0; i < iovsLength; i++) { - const dataOffset = memoryView.getUint32(iovsOffset, true); - iovsOffset += 4; + let nwritten = 0; + for (let i = 0; i < iovsLength; i++) { + const dataOffset = memoryView.getUint32(iovsOffset, true); + iovsOffset += 4; - const dataLength = memoryView.getUint32(iovsOffset, true); - iovsOffset += 4; + const dataLength = memoryView.getUint32(iovsOffset, true); + iovsOffset += 4; - const data = new Uint8Array( - this.#memory.buffer, - dataOffset, - dataLength, - ); - nwritten += Deno.writeSync(entry.rid!, data) as number; - } + const data = new Uint8Array(this.#memory.buffer, dataOffset, dataLength); + nwritten += Deno.writeSync(entry.rid!, data) as number; + } - Deno.seekSync(entry.rid!, seek, Deno.SeekMode.Start); - memoryView.setUint32(nwrittenOffset, nwritten, true); + Deno.seekSync(entry.rid!, seek, Deno.SeekMode.Start); + memoryView.setUint32(nwrittenOffset, nwritten, true); - return ERRNO_SUCCESS; - }), + return ERRNO_SUCCESS; + }, + ), - "fd_read": syscall(( - fd: number, - iovsOffset: number, - iovsLength: number, - nreadOffset: number, - ): number => { + fd_read: syscall((fd: number, iovsOffset: number, iovsLength: number, nreadOffset: number): number => { const entry = this.#fds[fd]; if (!entry) { return ERRNO_BADF; @@ -952,11 +847,7 @@ export default class Context { const dataLength = memoryView.getUint32(iovsOffset, true); iovsOffset += 4; - const data = new Uint8Array( - this.#memory.buffer, - dataOffset, - dataLength, - ); + const data = new Uint8Array(this.#memory.buffer, dataOffset, dataLength); nread += Deno.readSync(entry.rid!, data) as number; } @@ -965,80 +856,64 @@ export default class Context { return ERRNO_SUCCESS; }), - "fd_readdir": syscall(( - fd: number, - bufferOffset: number, - bufferLength: number, - cookie: bigint, - bufferUsedOffset: number, - ): number => { - const entry = this.#fds[fd]; - if (!entry) { - return ERRNO_BADF; - } + fd_readdir: syscall( + (fd: number, bufferOffset: number, bufferLength: number, cookie: bigint, bufferUsedOffset: number): number => { + const entry = this.#fds[fd]; + if (!entry) { + return ERRNO_BADF; + } - const memoryData = new Uint8Array(this.#memory.buffer); - const memoryView = new DataView(this.#memory.buffer); + const memoryData = new Uint8Array(this.#memory.buffer); + const memoryView = new DataView(this.#memory.buffer); - let bufferUsed = 0; + let bufferUsed = 0; - const entries = Array.from(Deno.readDirSync(entry.path!) as Iterable); - for (let i = Number(cookie); i < entries.length; i++) { - const nameData = new TextEncoder().encode(entries[i].name); + const entries = Array.from(Deno.readDirSync(entry.path!) as Iterable); + for (let i = Number(cookie); i < entries.length; i++) { + const nameData = new TextEncoder().encode(entries[i].name); - const entryInfo = Deno.statSync( - resolve(entry.path!, entries[i].name), - ); - const entryData = new Uint8Array(24 + nameData.byteLength); - const entryView = new DataView(entryData.buffer); + const entryInfo = Deno.statSync(resolve(entry.path!, entries[i].name)); + const entryData = new Uint8Array(24 + nameData.byteLength); + const entryView = new DataView(entryData.buffer); - entryView.setBigUint64(0, BigInt(i + 1), true); - entryView.setBigUint64( - 8, - BigInt(entryInfo.ino ? entryInfo.ino : 0), - true, - ); - entryView.setUint32(16, nameData.byteLength, true); + entryView.setBigUint64(0, BigInt(i + 1), true); + entryView.setBigUint64(8, BigInt(entryInfo.ino ? entryInfo.ino : 0), true); + entryView.setUint32(16, nameData.byteLength, true); - let type: number; - switch (true) { - case entries[i].isFile: - type = FILETYPE_REGULAR_FILE; - break; + let type: number; + switch (true) { + case entries[i].isFile: + type = FILETYPE_REGULAR_FILE; + break; - case entries[i].isDirectory: - type = FILETYPE_REGULAR_FILE; - break; + case entries[i].isDirectory: + type = FILETYPE_REGULAR_FILE; + break; - case entries[i].isSymlink: - type = FILETYPE_SYMBOLIC_LINK; - break; + case entries[i].isSymlink: + type = FILETYPE_SYMBOLIC_LINK; + break; - default: - type = FILETYPE_REGULAR_FILE; - break; - } + default: + type = FILETYPE_REGULAR_FILE; + break; + } - entryView.setUint8(20, type); - entryData.set(nameData, 24); + entryView.setUint8(20, type); + entryData.set(nameData, 24); - const data = entryData.slice( - 0, - Math.min(entryData.length, bufferLength - bufferUsed), - ); - memoryData.set(data, bufferOffset + bufferUsed); - bufferUsed += data.byteLength; - } + const data = entryData.slice(0, Math.min(entryData.length, bufferLength - bufferUsed)); + memoryData.set(data, bufferOffset + bufferUsed); + bufferUsed += data.byteLength; + } - memoryView.setUint32(bufferUsedOffset, bufferUsed, true); + memoryView.setUint32(bufferUsedOffset, bufferUsed, true); - return ERRNO_SUCCESS; - }), + return ERRNO_SUCCESS; + }, + ), - "fd_renumber": syscall(( - fd: number, - to: number, - ): number => { + fd_renumber: syscall((fd: number, to: number): number => { if (!this.#fds[fd]) { return ERRNO_BADF; } @@ -1057,12 +932,7 @@ export default class Context { return ERRNO_SUCCESS; }), - "fd_seek": syscall(( - fd: number, - offset: bigint, - whence: number, - newOffsetOffset: number, - ): number => { + fd_seek: syscall((fd: number, offset: bigint, whence: number, newOffsetOffset: number): number => { const entry = this.#fds[fd]; if (!entry) { return ERRNO_BADF; @@ -1077,9 +947,7 @@ export default class Context { return ERRNO_SUCCESS; }), - "fd_sync": syscall(( - fd: number, - ): number => { + fd_sync: syscall((fd: number): number => { const entry = this.#fds[fd]; if (!entry) { return ERRNO_BADF; @@ -1090,10 +958,7 @@ export default class Context { return ERRNO_SUCCESS; }), - "fd_tell": syscall(( - fd: number, - offsetOffset: number, - ): number => { + fd_tell: syscall((fd: number, offsetOffset: number): number => { const entry = this.#fds[fd]; if (!entry) { return ERRNO_BADF; @@ -1107,12 +972,7 @@ export default class Context { return ERRNO_SUCCESS; }), - "fd_write": syscall(( - fd: number, - iovsOffset: number, - iovsLength: number, - nwrittenOffset: number, - ): number => { + fd_write: syscall((fd: number, iovsOffset: number, iovsLength: number, nwrittenOffset: number): number => { const entry = this.#fds[fd]; if (!entry) { return ERRNO_BADF; @@ -1128,11 +988,7 @@ export default class Context { const dataLength = memoryView.getUint32(iovsOffset, true); iovsOffset += 4; - const data = new Uint8Array( - this.#memory.buffer, - dataOffset, - dataLength, - ); + const data = new Uint8Array(this.#memory.buffer, dataOffset, dataLength); nwritten += Deno.writeSync(entry.rid!, data) as number; } @@ -1141,11 +997,7 @@ export default class Context { return ERRNO_SUCCESS; }), - "path_create_directory": syscall(( - fd: number, - pathOffset: number, - pathLength: number, - ): number => { + path_create_directory: syscall((fd: number, pathOffset: number, pathLength: number): number => { const entry = this.#fds[fd]; if (!entry) { return ERRNO_BADF; @@ -1156,11 +1008,7 @@ export default class Context { } const textDecoder = new TextDecoder(); - const data = new Uint8Array( - this.#memory.buffer, - pathOffset, - pathLength, - ); + const data = new Uint8Array(this.#memory.buffer, pathOffset, pathLength); const path = resolve(entry.path!, textDecoder.decode(data)); Deno.mkdirSync(path); @@ -1168,363 +1016,313 @@ export default class Context { return ERRNO_SUCCESS; }), - "path_filestat_get": syscall(( - fd: number, - flags: number, - pathOffset: number, - pathLength: number, - bufferOffset: number, - ): number => { - const entry = this.#fds[fd]; - if (!entry) { - return ERRNO_BADF; - } - - if (!entry.path) { - return ERRNO_INVAL; - } - - const textDecoder = new TextDecoder(); - const data = new Uint8Array( - this.#memory.buffer, - pathOffset, - pathLength, - ); - const path = resolve(entry.path!, textDecoder.decode(data)); - - const memoryView = new DataView(this.#memory.buffer); + path_filestat_get: syscall( + (fd: number, flags: number, pathOffset: number, pathLength: number, bufferOffset: number): number => { + const entry = this.#fds[fd]; + if (!entry) { + return ERRNO_BADF; + } - const info = (flags & LOOKUPFLAGS_SYMLINK_FOLLOW) !== 0 - ? Deno.statSync(path) - : Deno.lstatSync(path); + if (!entry.path) { + return ERRNO_INVAL; + } - memoryView.setBigUint64( - bufferOffset, - BigInt(info.dev ? info.dev : 0), - true, - ); - bufferOffset += 8; + const textDecoder = new TextDecoder(); + const data = new Uint8Array(this.#memory.buffer, pathOffset, pathLength); + const path = resolve(entry.path!, textDecoder.decode(data)); - memoryView.setBigUint64( - bufferOffset, - BigInt(info.ino ? info.ino : 0), - true, - ); - bufferOffset += 8; + const memoryView = new DataView(this.#memory.buffer); - switch (true) { - case info.isFile: - memoryView.setUint8(bufferOffset, FILETYPE_REGULAR_FILE); - bufferOffset += 8; - break; + const info = (flags & LOOKUPFLAGS_SYMLINK_FOLLOW) !== 0 ? Deno.statSync(path) : Deno.lstatSync(path); - case info.isDirectory: - memoryView.setUint8(bufferOffset, FILETYPE_DIRECTORY); - bufferOffset += 8; - break; + memoryView.setBigUint64(bufferOffset, BigInt(info.dev ? info.dev : 0), true); + bufferOffset += 8; - case info.isSymlink: - memoryView.setUint8(bufferOffset, FILETYPE_SYMBOLIC_LINK); - bufferOffset += 8; - break; + memoryView.setBigUint64(bufferOffset, BigInt(info.ino ? info.ino : 0), true); + bufferOffset += 8; - default: - memoryView.setUint8(bufferOffset, FILETYPE_UNKNOWN); - bufferOffset += 8; - break; - } + switch (true) { + case info.isFile: + memoryView.setUint8(bufferOffset, FILETYPE_REGULAR_FILE); + bufferOffset += 8; + break; - memoryView.setUint32(bufferOffset, Number(info.nlink), true); - bufferOffset += 8; + case info.isDirectory: + memoryView.setUint8(bufferOffset, FILETYPE_DIRECTORY); + bufferOffset += 8; + break; - memoryView.setBigUint64(bufferOffset, BigInt(info.size), true); - bufferOffset += 8; + case info.isSymlink: + memoryView.setUint8(bufferOffset, FILETYPE_SYMBOLIC_LINK); + bufferOffset += 8; + break; - memoryView.setBigUint64( - bufferOffset, - BigInt(info.atime ? info.atime.getTime() * 1e6 : 0), - true, - ); - bufferOffset += 8; + default: + memoryView.setUint8(bufferOffset, FILETYPE_UNKNOWN); + bufferOffset += 8; + break; + } - memoryView.setBigUint64( - bufferOffset, - BigInt(info.mtime ? info.mtime.getTime() * 1e6 : 0), - true, - ); - bufferOffset += 8; + memoryView.setUint32(bufferOffset, Number(info.nlink), true); + bufferOffset += 8; - memoryView.setBigUint64( - bufferOffset, - BigInt(info.birthtime ? info.birthtime.getTime() * 1e6 : 0), - true, - ); - bufferOffset += 8; + memoryView.setBigUint64(bufferOffset, BigInt(info.size), true); + bufferOffset += 8; - return ERRNO_SUCCESS; - }), + memoryView.setBigUint64(bufferOffset, BigInt(info.atime ? info.atime.getTime() * 1e6 : 0), true); + bufferOffset += 8; - "path_filestat_set_times": syscall(( - fd: number, - flags: number, - pathOffset: number, - pathLength: number, - atim: bigint, - mtim: bigint, - fstflags: number, - ): number => { - const entry = this.#fds[fd]; - if (!entry) { - return ERRNO_BADF; - } + memoryView.setBigUint64(bufferOffset, BigInt(info.mtime ? info.mtime.getTime() * 1e6 : 0), true); + bufferOffset += 8; - if (!entry.path) { - return ERRNO_INVAL; - } + memoryView.setBigUint64(bufferOffset, BigInt(info.birthtime ? info.birthtime.getTime() * 1e6 : 0), true); + bufferOffset += 8; - const textDecoder = new TextDecoder(); - const data = new Uint8Array( - this.#memory.buffer, - pathOffset, - pathLength, - ); - const path = resolve(entry.path!, textDecoder.decode(data)); + return ERRNO_SUCCESS; + }, + ), + + path_filestat_set_times: syscall( + ( + fd: number, + flags: number, + pathOffset: number, + pathLength: number, + atim: bigint, + mtim: bigint, + fstflags: number, + ): number => { + const entry = this.#fds[fd]; + if (!entry) { + return ERRNO_BADF; + } - if ((fstflags & FSTFLAGS_ATIM_NOW) === FSTFLAGS_ATIM_NOW) { - atim = BigInt(Date.now()) * BigInt(1e6); - } + if (!entry.path) { + return ERRNO_INVAL; + } - if ((fstflags & FSTFLAGS_MTIM_NOW) === FSTFLAGS_MTIM_NOW) { - mtim = BigInt(Date.now()) * BigInt(1e6); - } + const textDecoder = new TextDecoder(); + const data = new Uint8Array(this.#memory.buffer, pathOffset, pathLength); + const path = resolve(entry.path!, textDecoder.decode(data)); - Deno.utimeSync(path, Number(atim), Number(mtim)); + if ((fstflags & FSTFLAGS_ATIM_NOW) === FSTFLAGS_ATIM_NOW) { + atim = BigInt(Date.now()) * BigInt(1e6); + } - return ERRNO_SUCCESS; - }), + if ((fstflags & FSTFLAGS_MTIM_NOW) === FSTFLAGS_MTIM_NOW) { + mtim = BigInt(Date.now()) * BigInt(1e6); + } - "path_link": syscall(( - oldFd: number, - oldFlags: number, - oldPathOffset: number, - oldPathLength: number, - newFd: number, - newPathOffset: number, - newPathLength: number, - ): number => { - const oldEntry = this.#fds[oldFd]; - const newEntry = this.#fds[newFd]; - if (!oldEntry || !newEntry) { - return ERRNO_BADF; - } + Deno.utimeSync(path, Number(atim), Number(mtim)); - if (!oldEntry.path || !newEntry.path) { - return ERRNO_INVAL; - } + return ERRNO_SUCCESS; + }, + ), + + path_link: syscall( + ( + oldFd: number, + oldFlags: number, + oldPathOffset: number, + oldPathLength: number, + newFd: number, + newPathOffset: number, + newPathLength: number, + ): number => { + const oldEntry = this.#fds[oldFd]; + const newEntry = this.#fds[newFd]; + if (!oldEntry || !newEntry) { + return ERRNO_BADF; + } - const textDecoder = new TextDecoder(); - const oldData = new Uint8Array( - this.#memory.buffer, - oldPathOffset, - oldPathLength, - ); - const oldPath = resolve(oldEntry.path!, textDecoder.decode(oldData)); - const newData = new Uint8Array( - this.#memory.buffer, - newPathOffset, - newPathLength, - ); - const newPath = resolve(newEntry.path!, textDecoder.decode(newData)); + if (!oldEntry.path || !newEntry.path) { + return ERRNO_INVAL; + } - Deno.linkSync(oldPath, newPath); + const textDecoder = new TextDecoder(); + const oldData = new Uint8Array(this.#memory.buffer, oldPathOffset, oldPathLength); + const oldPath = resolve(oldEntry.path!, textDecoder.decode(oldData)); + const newData = new Uint8Array(this.#memory.buffer, newPathOffset, newPathLength); + const newPath = resolve(newEntry.path!, textDecoder.decode(newData)); - return ERRNO_SUCCESS; - }), + Deno.linkSync(oldPath, newPath); - "path_open": syscall(( - fd: number, - dirflags: number, - pathOffset: number, - pathLength: number, - oflags: number, - rightsBase: bigint, - rightsInheriting: bigint, - fdflags: number, - openedFdOffset: number, - ): number => { - const entry = this.#fds[fd]; - if (!entry) { - return ERRNO_BADF; - } + return ERRNO_SUCCESS; + }, + ), + + path_open: syscall( + ( + fd: number, + dirflags: number, + pathOffset: number, + pathLength: number, + oflags: number, + rightsBase: bigint, + rightsInheriting: bigint, + fdflags: number, + openedFdOffset: number, + ): number => { + const entry = this.#fds[fd]; + if (!entry) { + return ERRNO_BADF; + } - if (!entry.path) { - return ERRNO_INVAL; - } + if (!entry.path) { + return ERRNO_INVAL; + } - const textDecoder = new TextDecoder(); - const pathData = new Uint8Array( - this.#memory.buffer, - pathOffset, - pathLength, - ); - const resolvedPath = resolve(entry.path!, textDecoder.decode(pathData)); + const textDecoder = new TextDecoder(); + const pathData = new Uint8Array(this.#memory.buffer, pathOffset, pathLength); + const resolvedPath = resolve(entry.path!, textDecoder.decode(pathData)); - if (relative(entry.path, resolvedPath).startsWith("..")) { - return ERRNO_NOTCAPABLE; - } + if (relative(entry.path, resolvedPath).startsWith('..')) { + return ERRNO_NOTCAPABLE; + } - let path; - if ( - (dirflags & LOOKUPFLAGS_SYMLINK_FOLLOW) === LOOKUPFLAGS_SYMLINK_FOLLOW - ) { - try { - path = Deno.realPathSync(resolvedPath); - if (relative(entry.path, path).startsWith("..")) { - return ERRNO_NOTCAPABLE; + let path; + if ((dirflags & LOOKUPFLAGS_SYMLINK_FOLLOW) === LOOKUPFLAGS_SYMLINK_FOLLOW) { + try { + path = Deno.realPathSync(resolvedPath); + if (relative(entry.path, path).startsWith('..')) { + return ERRNO_NOTCAPABLE; + } + } catch (_err) { + path = resolvedPath; } - } catch (_err) { + } else { path = resolvedPath; } - } else { - path = resolvedPath; - } - - if ((oflags & OFLAGS_DIRECTORY) !== 0) { - // XXX (caspervonb) this isn't ideal as we can't get a rid for the - // directory this way so there's no native fstat but Deno.open - // doesn't work with directories on windows so we'll have to work - // around it for now. - const entries = Array.from(Deno.readDirSync(path) as Iterable); - const openedFd = this.#fds.push({ - flags: fdflags, - path, - entries, - }) - 1; - - const memoryView = new DataView(this.#memory.buffer); - memoryView.setUint32(openedFdOffset, openedFd, true); - - return ERRNO_SUCCESS; - } - const options = { - read: false, - write: false, - append: false, - truncate: false, - create: false, - createNew: false, - }; - - if ((oflags & OFLAGS_CREAT) !== 0) { - options.create = true; - options.write = true; - } + if ((oflags & OFLAGS_DIRECTORY) !== 0) { + // XXX (caspervonb) this isn't ideal as we can't get a rid for the + // directory this way so there's no native fstat but Deno.open + // doesn't work with directories on windows so we'll have to work + // around it for now. + const entries = Array.from(Deno.readDirSync(path) as Iterable); + const openedFd = + this.#fds.push({ + flags: fdflags, + path, + entries, + }) - 1; + + const memoryView = new DataView(this.#memory.buffer); + memoryView.setUint32(openedFdOffset, openedFd, true); + + return ERRNO_SUCCESS; + } - if ((oflags & OFLAGS_EXCL) !== 0) { - options.createNew = true; - } + const options = { + read: false, + write: false, + append: false, + truncate: false, + create: false, + createNew: false, + }; + + if ((oflags & OFLAGS_CREAT) !== 0) { + options.create = true; + options.write = true; + } - if ((oflags & OFLAGS_TRUNC) !== 0) { - options.truncate = true; - options.write = true; - } + if ((oflags & OFLAGS_EXCL) !== 0) { + options.createNew = true; + } - const read = RIGHTS_FD_READ | - RIGHTS_FD_READDIR; + if ((oflags & OFLAGS_TRUNC) !== 0) { + options.truncate = true; + options.write = true; + } - if ((rightsBase & read) !== 0n) { - options.read = true; - } + const read = RIGHTS_FD_READ | RIGHTS_FD_READDIR; - const write = RIGHTS_FD_DATASYNC | - RIGHTS_FD_WRITE | - RIGHTS_FD_ALLOCATE | - RIGHTS_FD_FILESTAT_SET_SIZE; + if ((rightsBase & read) !== 0n) { + options.read = true; + } - if ((rightsBase & write) !== 0n) { - options.write = true; - } + const write = RIGHTS_FD_DATASYNC | RIGHTS_FD_WRITE | RIGHTS_FD_ALLOCATE | RIGHTS_FD_FILESTAT_SET_SIZE; - if ((fdflags & FDFLAGS_APPEND) !== 0) { - options.append = true; - } + if ((rightsBase & write) !== 0n) { + options.write = true; + } - if ((fdflags & FDFLAGS_DSYNC) !== 0) { - // TODO(caspervonb): review if we can emulate this. - } + if ((fdflags & FDFLAGS_APPEND) !== 0) { + options.append = true; + } - if ((fdflags & FDFLAGS_NONBLOCK) !== 0) { - // TODO(caspervonb): review if we can emulate this. - } + if ((fdflags & FDFLAGS_DSYNC) !== 0) { + // TODO(caspervonb): review if we can emulate this. + } - if ((fdflags & FDFLAGS_RSYNC) !== 0) { - // TODO(caspervonb): review if we can emulate this. - } + if ((fdflags & FDFLAGS_NONBLOCK) !== 0) { + // TODO(caspervonb): review if we can emulate this. + } - if ((fdflags & FDFLAGS_SYNC) !== 0) { - // TODO(caspervonb): review if we can emulate this. - } + if ((fdflags & FDFLAGS_RSYNC) !== 0) { + // TODO(caspervonb): review if we can emulate this. + } - if (!options.read && !options.write && !options.truncate) { - options.read = true; - } + if ((fdflags & FDFLAGS_SYNC) !== 0) { + // TODO(caspervonb): review if we can emulate this. + } - const { rid } = Deno.openSync(path, options); - const openedFd = this.#fds.push({ - rid, - flags: fdflags, - path, - }) - 1; + if (!options.read && !options.write && !options.truncate) { + options.read = true; + } - const memoryView = new DataView(this.#memory.buffer); - memoryView.setUint32(openedFdOffset, openedFd, true); + const { rid } = Deno.openSync(path, options); + const openedFd = + this.#fds.push({ + rid, + flags: fdflags, + path, + }) - 1; - return ERRNO_SUCCESS; - }), + const memoryView = new DataView(this.#memory.buffer); + memoryView.setUint32(openedFdOffset, openedFd, true); - "path_readlink": syscall(( - fd: number, - pathOffset: number, - pathLength: number, - bufferOffset: number, - bufferLength: number, - bufferUsedOffset: number, - ): number => { - const entry = this.#fds[fd]; - if (!entry) { - return ERRNO_BADF; - } + return ERRNO_SUCCESS; + }, + ), + + path_readlink: syscall( + ( + fd: number, + pathOffset: number, + pathLength: number, + bufferOffset: number, + bufferLength: number, + bufferUsedOffset: number, + ): number => { + const entry = this.#fds[fd]; + if (!entry) { + return ERRNO_BADF; + } - if (!entry.path) { - return ERRNO_INVAL; - } + if (!entry.path) { + return ERRNO_INVAL; + } - const memoryData = new Uint8Array(this.#memory.buffer); - const memoryView = new DataView(this.#memory.buffer); + const memoryData = new Uint8Array(this.#memory.buffer); + const memoryView = new DataView(this.#memory.buffer); - const pathData = new Uint8Array( - this.#memory.buffer, - pathOffset, - pathLength, - ); - const path = resolve(entry.path!, new TextDecoder().decode(pathData)); + const pathData = new Uint8Array(this.#memory.buffer, pathOffset, pathLength); + const path = resolve(entry.path!, new TextDecoder().decode(pathData)); - const link = Deno.readLinkSync(path); - const linkData = new TextEncoder().encode(link); - memoryData.set(new Uint8Array(linkData, 0, bufferLength), bufferOffset); + const link = Deno.readLinkSync(path); + const linkData = new TextEncoder().encode(link); + memoryData.set(new Uint8Array(linkData, 0, bufferLength), bufferOffset); - const bufferUsed = Math.min(linkData.byteLength, bufferLength); - memoryView.setUint32(bufferUsedOffset, bufferUsed, true); + const bufferUsed = Math.min(linkData.byteLength, bufferLength); + memoryView.setUint32(bufferUsedOffset, bufferUsed, true); - return ERRNO_SUCCESS; - }), + return ERRNO_SUCCESS; + }, + ), - "path_remove_directory": syscall(( - fd: number, - pathOffset: number, - pathLength: number, - ): number => { + path_remove_directory: syscall((fd: number, pathOffset: number, pathLength: number): number => { const entry = this.#fds[fd]; if (!entry) { return ERRNO_BADF; @@ -1535,11 +1333,7 @@ export default class Context { } const textDecoder = new TextDecoder(); - const data = new Uint8Array( - this.#memory.buffer, - pathOffset, - pathLength, - ); + const data = new Uint8Array(this.#memory.buffer, pathOffset, pathLength); const path = resolve(entry.path!, textDecoder.decode(data)); if (!Deno.statSync(path).isDirectory) { @@ -1551,83 +1345,67 @@ export default class Context { return ERRNO_SUCCESS; }), - "path_rename": syscall(( - fd: number, - oldPathOffset: number, - oldPathLength: number, - newFd: number, - newPathOffset: number, - newPathLength: number, - ): number => { - const oldEntry = this.#fds[fd]; - const newEntry = this.#fds[newFd]; - if (!oldEntry || !newEntry) { - return ERRNO_BADF; - } - - if (!oldEntry.path || !newEntry.path) { - return ERRNO_INVAL; - } + path_rename: syscall( + ( + fd: number, + oldPathOffset: number, + oldPathLength: number, + newFd: number, + newPathOffset: number, + newPathLength: number, + ): number => { + const oldEntry = this.#fds[fd]; + const newEntry = this.#fds[newFd]; + if (!oldEntry || !newEntry) { + return ERRNO_BADF; + } - const textDecoder = new TextDecoder(); - const oldData = new Uint8Array( - this.#memory.buffer, - oldPathOffset, - oldPathLength, - ); - const oldPath = resolve(oldEntry.path!, textDecoder.decode(oldData)); - const newData = new Uint8Array( - this.#memory.buffer, - newPathOffset, - newPathLength, - ); - const newPath = resolve(newEntry.path!, textDecoder.decode(newData)); + if (!oldEntry.path || !newEntry.path) { + return ERRNO_INVAL; + } - Deno.renameSync(oldPath, newPath); + const textDecoder = new TextDecoder(); + const oldData = new Uint8Array(this.#memory.buffer, oldPathOffset, oldPathLength); + const oldPath = resolve(oldEntry.path!, textDecoder.decode(oldData)); + const newData = new Uint8Array(this.#memory.buffer, newPathOffset, newPathLength); + const newPath = resolve(newEntry.path!, textDecoder.decode(newData)); - return ERRNO_SUCCESS; - }), + Deno.renameSync(oldPath, newPath); - "path_symlink": syscall(( - oldPathOffset: number, - oldPathLength: number, - fd: number, - newPathOffset: number, - newPathLength: number, - ): number => { - const entry = this.#fds[fd]; - if (!entry) { - return ERRNO_BADF; - } + return ERRNO_SUCCESS; + }, + ), + + path_symlink: syscall( + ( + oldPathOffset: number, + oldPathLength: number, + fd: number, + newPathOffset: number, + newPathLength: number, + ): number => { + const entry = this.#fds[fd]; + if (!entry) { + return ERRNO_BADF; + } - if (!entry.path) { - return ERRNO_INVAL; - } + if (!entry.path) { + return ERRNO_INVAL; + } - const textDecoder = new TextDecoder(); - const oldData = new Uint8Array( - this.#memory.buffer, - oldPathOffset, - oldPathLength, - ); - const oldPath = textDecoder.decode(oldData); - const newData = new Uint8Array( - this.#memory.buffer, - newPathOffset, - newPathLength, - ); - const newPath = resolve(entry.path!, textDecoder.decode(newData)); + const textDecoder = new TextDecoder(); + const oldData = new Uint8Array(this.#memory.buffer, oldPathOffset, oldPathLength); + const oldPath = textDecoder.decode(oldData); + const newData = new Uint8Array(this.#memory.buffer, newPathOffset, newPathLength); + const newPath = resolve(entry.path!, textDecoder.decode(newData)); - Deno.symlinkSync(oldPath, newPath); + Deno.symlinkSync(oldPath, newPath); - return ERRNO_SUCCESS; - }), + return ERRNO_SUCCESS; + }, + ), - "path_unlink_file": syscall(( - fd: number, - pathOffset: number, - pathLength: number, - ): number => { + path_unlink_file: syscall((fd: number, pathOffset: number, pathLength: number): number => { const entry = this.#fds[fd]; if (!entry) { return ERRNO_BADF; @@ -1638,11 +1416,7 @@ export default class Context { } const textDecoder = new TextDecoder(); - const data = new Uint8Array( - this.#memory.buffer, - pathOffset, - pathLength, - ); + const data = new Uint8Array(this.#memory.buffer, pathOffset, pathLength); const path = resolve(entry.path!, textDecoder.decode(data)); Deno.removeSync(path); @@ -1650,18 +1424,13 @@ export default class Context { return ERRNO_SUCCESS; }), - "poll_oneoff": syscall(( - _inOffset: number, - _outOffset: number, - _nsubscriptions: number, - _neventsOffset: number, - ): number => { - return ERRNO_NOSYS; - }), + poll_oneoff: syscall( + (_inOffset: number, _outOffset: number, _nsubscriptions: number, _neventsOffset: number): number => { + return ERRNO_NOSYS; + }, + ), - "proc_exit": syscall(( - rval: number, - ): never => { + proc_exit: syscall((rval: number): never => { if (this.#exitOnReturn) { Deno.exit(rval); } @@ -1669,55 +1438,47 @@ export default class Context { throw new ExitStatus(rval); }), - "proc_raise": syscall(( - _sig: number, - ): number => { + proc_raise: syscall((_sig: number): number => { return ERRNO_NOSYS; }), - "sched_yield": syscall((): number => { + sched_yield: syscall((): number => { return ERRNO_SUCCESS; }), - "random_get": syscall(( - bufferOffset: number, - bufferLength: number, - ): number => { - const buffer = new Uint8Array( - this.#memory.buffer, - bufferOffset, - bufferLength, - ); + random_get: syscall((bufferOffset: number, bufferLength: number): number => { + const buffer = new Uint8Array(this.#memory.buffer, bufferOffset, bufferLength); crypto.getRandomValues(buffer); return ERRNO_SUCCESS; }), - "sock_recv": syscall(( - _fd: number, - _riDataOffset: number, - _riDataLength: number, - _riFlags: number, - _roDataLengthOffset: number, - _roFlagsOffset: number, - ): number => { - return ERRNO_NOSYS; - }), - - "sock_send": syscall(( - _fd: number, - _siDataOffset: number, - _siDataLength: number, - _siFlags: number, - _soDataLengthOffset: number, - ): number => { - return ERRNO_NOSYS; - }), - - "sock_shutdown": syscall(( - _fd: number, - _how: number, - ): number => { + sock_recv: syscall( + ( + _fd: number, + _riDataOffset: number, + _riDataLength: number, + _riFlags: number, + _roDataLengthOffset: number, + _roFlagsOffset: number, + ): number => { + return ERRNO_NOSYS; + }, + ), + + sock_send: syscall( + ( + _fd: number, + _siDataOffset: number, + _siDataLength: number, + _siFlags: number, + _soDataLengthOffset: number, + ): number => { + return ERRNO_NOSYS; + }, + ), + + sock_shutdown: syscall((_fd: number, _how: number): number => { return ERRNO_NOSYS; }), }; @@ -1738,7 +1499,7 @@ export default class Context { */ start(instance: WebAssembly.Instance): null | number | never { if (this.#started) { - throw new Error("WebAssembly.Instance has already started"); + throw new Error('WebAssembly.Instance has already started'); } this.#started = true; @@ -1746,21 +1507,17 @@ export default class Context { const { _start, _initialize, memory } = instance.exports; if (!(memory instanceof WebAssembly.Memory)) { - throw new TypeError("WebAssembly.instance must provide a memory export"); + throw new TypeError('WebAssembly.instance must provide a memory export'); } this.#memory = memory; - if (typeof _initialize === "function") { - throw new TypeError( - "WebAssembly.instance export _initialize must not be a function", - ); + if (typeof _initialize === 'function') { + throw new TypeError('WebAssembly.instance export _initialize must not be a function'); } - if (typeof _start !== "function") { - throw new TypeError( - "WebAssembly.Instance export _start must be a function", - ); + if (typeof _start !== 'function') { + throw new TypeError('WebAssembly.Instance export _start must be a function'); } try { @@ -1787,7 +1544,7 @@ export default class Context { */ initialize(instance: WebAssembly.Instance) { if (this.#started) { - throw new Error("WebAssembly.Instance has already started"); + throw new Error('WebAssembly.Instance has already started'); } this.#started = true; @@ -1795,21 +1552,17 @@ export default class Context { const { _start, _initialize, memory } = instance.exports; if (!(memory instanceof WebAssembly.Memory)) { - throw new TypeError("WebAssembly.instance must provide a memory export"); + throw new TypeError('WebAssembly.instance must provide a memory export'); } this.#memory = memory; - if (typeof _start === "function") { - throw new TypeError( - "WebAssembly.Instance export _start must not be a function", - ); + if (typeof _start === 'function') { + throw new TypeError('WebAssembly.Instance export _start must not be a function'); } - if (_initialize && typeof _initialize !== "function") { - throw new TypeError( - "WebAssembly.Instance export _initialize must be a function or not be defined", - ); + if (_initialize && typeof _initialize !== 'function') { + throw new TypeError('WebAssembly.Instance export _initialize must be a function or not be defined'); } _initialize?.(); } diff --git a/src/polyfills/node-capabilities.ts b/src/polyfills/node-capabilities.ts index 81e0eea..2170193 100644 --- a/src/polyfills/node-capabilities.ts +++ b/src/polyfills/node-capabilities.ts @@ -19,6 +19,5 @@ export const CAPABILITIES: Capabilities = { supportsTimeouts: true, - - extismStdoutEnvVarSet: Boolean(process.env.EXTISM_ENABLE_WASI_OUTPUT) + extismStdoutEnvVarSet: Boolean(process.env.EXTISM_ENABLE_WASI_OUTPUT), }; diff --git a/src/utils.ts b/src/utils.ts index e58ba6e..1b119fc 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,30 +1,30 @@ export async function readBodyUpTo(response: Response, maxBytes: number): Promise { - const reader = response.body?.getReader(); - if (!reader) { - return new Uint8Array(0); - } - - let receivedLength = 0; - const chunks = []; - - while (receivedLength < maxBytes) { - const { done, value } = await reader.read(); - if (done) { - break; - } - chunks.push(value); - receivedLength += value.length; - if (receivedLength >= maxBytes) { - throw new Error(`Response body exceeded ${maxBytes} bytes`); - } + const reader = response.body?.getReader(); + if (!reader) { + return new Uint8Array(0); + } + + let receivedLength = 0; + const chunks = []; + + while (receivedLength < maxBytes) { + const { done, value } = await reader.read(); + if (done) { + break; } - - const limitedResponseBody = new Uint8Array(receivedLength); - let position = 0; - for (const chunk of chunks) { - limitedResponseBody.set(chunk, position); - position += chunk.length; + chunks.push(value); + receivedLength += value.length; + if (receivedLength >= maxBytes) { + throw new Error(`Response body exceeded ${maxBytes} bytes`); } - - return limitedResponseBody; } + + const limitedResponseBody = new Uint8Array(receivedLength); + let position = 0; + for (const chunk of chunks) { + limitedResponseBody.set(chunk, position); + position += chunk.length; + } + + return limitedResponseBody; +} diff --git a/src/worker-url.ts b/src/worker-url.ts index 7998d6d..e272c1b 100644 --- a/src/worker-url.ts +++ b/src/worker-url.ts @@ -10,7 +10,7 @@ // In Node, Bun, and browser environments, this entire file is *ignored*: the esbuild config // replaces it with a prebuilt base64'd inline javascript URL. See `build_worker_node` in // the `justfile`. -const relativeUrl = (await (import.meta.resolve as any)('./worker.ts')) as string +const relativeUrl = (await (import.meta.resolve as any)('./worker.ts')) as string; export const WORKER_URL = `data:text/javascript;base64,${btoa(` export * from ${JSON.stringify(relativeUrl)}; -`)}` +`)}`; diff --git a/src/worker.ts b/src/worker.ts index 4502e4e..d57d71f 100644 --- a/src/worker.ts +++ b/src/worker.ts @@ -1,20 +1,8 @@ -import { parentPort } from "node:worker_threads"; - -import { - createForegroundPlugin as _createForegroundPlugin, - ForegroundPlugin, -} from "./foreground-plugin.ts"; -import { - CallContext, - CallState, - EXPORT_STATE, - IMPORT_STATE, -} from "./call-context.ts"; -import { - type InternalConfig, - SAB_BASE_OFFSET, - SharedArrayBufferSection, -} from "./interfaces.ts"; +import { parentPort } from 'node:worker_threads'; + +import { createForegroundPlugin as _createForegroundPlugin, ForegroundPlugin } from './foreground-plugin.ts'; +import { CallContext, CallState, EXPORT_STATE, IMPORT_STATE } from './call-context.ts'; +import { type InternalConfig, SAB_BASE_OFFSET, SharedArrayBufferSection } from './interfaces.ts'; class Reactor { hostFlag: Int32Array | null; @@ -27,80 +15,68 @@ class Reactor { constructor(port: typeof parentPort) { if (!port) { - throw new Error( - "This should be unreachable: this module should only be invoked as a web worker.", - ); + throw new Error('This should be unreachable: this module should only be invoked as a web worker.'); } this.sharedData = null; this.sharedDataView = null; this.hostFlag = null; this.port = port; - this.port.on("message", (ev: any) => this.handleMessage(ev)); - this.port.postMessage({ type: "initialized" }); + this.port.on('message', (ev: any) => this.handleMessage(ev)); + this.port.postMessage({ type: 'initialized' }); this.dynamicHandlers = new Map(); - this.dynamicHandlers.set( - "call", - async ( - transfer: any[], - name: string, - input: number | null, - state: CallState, - ) => { - if (!this.context) { - throw new Error( - "invalid state: no context available to worker reactor", - ); - } + this.dynamicHandlers.set('call', async (transfer: any[], name: string, input: number | null, state: CallState) => { + if (!this.context) { + throw new Error('invalid state: no context available to worker reactor'); + } - this.context[IMPORT_STATE](state); + this.context[IMPORT_STATE](state); - const results: any = await this.plugin?.callBlock(name, input).then( - (indices) => [null, indices], - (err) => [err, null], - ); + const results: any = await this.plugin?.callBlock(name, input).then( + (indices) => [null, indices], + (err) => [err, null], + ); - state = this.context[EXPORT_STATE](); - for (const [block] of state.blocks) { - if (block) { - transfer.push(block); - } + state = this.context[EXPORT_STATE](); + for (const [block] of state.blocks) { + if (block) { + transfer.push(block); } + } - if (results[0]) { - results[0] = { - originalStack: results[0]?.stack, - message: results[0]?.message, - }; - } + if (results[0]) { + results[0] = { + originalStack: results[0]?.stack, + message: results[0]?.message, + }; + } - return { results, state }; - }, - ); + return { results, state }; + }); - this.dynamicHandlers.set("reset", async (_txf) => { + this.dynamicHandlers.set('reset', async (_txf) => { return this.plugin?.reset(); }); - this.dynamicHandlers.set("getExports", async (_txf) => { + this.dynamicHandlers.set('getExports', async (_txf) => { return this.plugin?.getExports(); }); - this.dynamicHandlers.set("getImports", async (_txf) => { + this.dynamicHandlers.set('getImports', async (_txf) => { return this.plugin?.getImports(); }); - this.dynamicHandlers.set("functionExists", async (_txf, name) => { + this.dynamicHandlers.set('functionExists', async (_txf, name) => { return this.plugin?.functionExists(name); }); } async handleMessage(ev: any) { switch (ev.type) { - case "init": + case 'init': return await this.handleInit(ev); - case "invoke": + case 'invoke': return await this.handleInvoke(ev); } } @@ -109,7 +85,7 @@ class Reactor { const handler = this.dynamicHandlers.get(ev.handler); if (!handler) { return this.port.postMessage({ - type: "return", + type: 'return', result: [`no handler registered for ${ev.handler}`, null], }); } @@ -129,7 +105,7 @@ class Reactor { return this.port.postMessage( { - type: "return", + type: 'return', results, }, transfer, @@ -157,8 +133,7 @@ class Reactor { funcs.map((funcName) => { return [ funcName, - (context: CallContext, ...args: any[]) => - this.callHost(context, namespace, funcName, args), + (context: CallContext, ...args: any[]) => this.callHost(context, namespace, funcName, args), ]; }), ), @@ -168,24 +143,15 @@ class Reactor { const { type: _, modules, functions: __, ...opts } = ev; - const logLevel = (level: string) => (message: string) => - this.port.postMessage({ type: "log", level, message }); + const logLevel = (level: string) => (message: string) => this.port.postMessage({ type: 'log', level, message }); // TODO: we're using non-blocking log functions here; to properly preserve behavior we // should invoke these and wait on the host to return. const logger = Object.fromEntries( - ["info", "debug", "warn", "error", "trace"].map(( - lvl, - ) => [lvl, logLevel(lvl)]), + ['info', 'debug', 'warn', 'error', 'trace'].map((lvl) => [lvl, logLevel(lvl)]), ) as unknown as Console; - this.context = new CallContext( - ArrayBuffer, - logger, - ev.logLevel, - ev.config, - ev.memory, - ); + this.context = new CallContext(ArrayBuffer, logger, ev.logLevel, ev.config, ev.memory); // TODO: replace our internal fetch and logger this.plugin = await _createForegroundPlugin( @@ -195,25 +161,18 @@ class Reactor { this.context, ); - this.port.postMessage({ type: "ready" }); + this.port.postMessage({ type: 'ready' }); } - callHost( - context: CallContext, - namespace: string, - func: string, - args: (number | bigint)[], - ): number | bigint | void { + callHost(context: CallContext, namespace: string, func: string, args: (number | bigint)[]): number | bigint | void { if (!this.hostFlag) { - throw new Error( - "attempted to call host before receiving shared array buffer", - ); + throw new Error('attempted to call host before receiving shared array buffer'); } Atomics.store(this.hostFlag, 0, SAB_BASE_OFFSET); const state = context[EXPORT_STATE](); this.port.postMessage({ - type: "invoke", + type: 'invoke', namespace, func, args, @@ -311,7 +270,7 @@ class RingBufferReader { value = Atomics.load(this.flag, 0); if (value === SAB_BASE_OFFSET) { const result = Atomics.wait(this.flag, 0, SAB_BASE_OFFSET, MAX_WAIT); - if (result === "timed-out") { + if (result === 'timed-out') { continue; } } @@ -339,12 +298,7 @@ class RingBufferReader { read(output: Uint8Array) { this.position += output.byteLength; if (output.byteLength < this.available) { - output.set( - new Uint8Array(this.input).subarray( - this.inputOffset, - this.inputOffset + output.byteLength, - ), - ); + output.set(new Uint8Array(this.input).subarray(this.inputOffset, this.inputOffset + output.byteLength)); this.inputOffset += output.byteLength; return; } @@ -354,13 +308,7 @@ class RingBufferReader { // read ::= [outputoffset, inputoffset, extent] // firstread = [this.outputOffset, 0, this.available - this.outputOffset] do { - output.set( - new Uint8Array(this.input).subarray( - this.inputOffset, - this.inputOffset + extent, - ), - outputOffset, - ); + output.set(new Uint8Array(this.input).subarray(this.inputOffset, this.inputOffset + extent), outputOffset); outputOffset += extent; this.inputOffset += extent; if (outputOffset === output.byteLength) { @@ -372,10 +320,7 @@ class RingBufferReader { } this.pull(); - extent = Math.min( - Math.max(this.available, 0), - output.byteLength - outputOffset, - ); + extent = Math.min(Math.max(this.available, 0), output.byteLength - outputOffset); } while (outputOffset !== output.byteLength); }