This release is 7 versions behind 4.5.0 — the latest version of @dalbit-yaksok/quickjs. Jump to latest
@dalbit-yaksok/quickjs@3.10.0Built and signed on GitHub ActionsBuilt and signed on GitHub Actions
Built and signed on GitHub Actions
Works with
•JSR Score47%•It is unknown whether this package works with Cloudflare Workers, Node.js, Deno, Bun, Browsers




Downloads19/wk
•Publisheda month ago (3.10.0)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217import { newQuickJSWASMModuleFromVariant, newVariant, RELEASE_SYNC, } from 'npm:quickjs-emscripten@^0.31.0' import type { QuickJSContext, QuickJSWASMModule } from 'npm:quickjs-emscripten-core@^0.31.0' import { ErrorInFFIExecution, ListValue, NumberValue, PrimitiveValue, StringValue, ValueType, type Extension, type ExtensionManifest, type FunctionInvokingParams, } from 'jsr:@dalbit-yaksok/core@^3.10.0' import { dim } from './util.ts' export class QuickJS implements Extension { public manifest: ExtensionManifest = { ffiRunner: { runtimeName: 'QuickJS', }, } private instance: QuickJSWASMModule | null = null constructor( private functions: Record<string, (...args: any[]) => any> = {}, ) {} async init(): Promise<void> { const wasmPath = 'https://unpkg.com/@jitl/quickjs-wasmfile-release-sync@0.31.0/dist/emscripten-module.wasm' const wasmModule = await WebAssembly.compileStreaming(fetch(wasmPath)) const variant = newVariant(RELEASE_SYNC, { wasmModule, }) this.instance = await newQuickJSWASMModuleFromVariant(variant) } public executeFFI( bodyCode: string, args: FunctionInvokingParams, ): ValueType { const wrappedCode = createWrapperCodeFromFFICall(bodyCode, args) const vm = this.createContext() const result = vm.evalCode(wrappedCode) if (result.error) { const error = vm.dump(result.error) as QuickJSErrorData result.error.dispose() throw new QuickJSInternalError(error) } else { const resultValue = vm.dump(result.value) result.value.dispose() const yaksokValue = convertJSDataIntoYaksok(resultValue) return yaksokValue } } private createContext() { if (!this.instance) { throw new QuickJSNotInitializedError() } const context = this.instance.newContext() for (const [name, func] of Object.entries(this.functions)) { const handle = context.newFunction(name, (...args: any[]) => { const nativeArgs = args.map(context.dump) const result = func(nativeArgs) return convertJSDataIntoQuickJSData(result, context) }) context.setProp(context.global, name, handle) } return context } } function createWrapperCodeFromFFICall( bodyCode: string, args: Record<string, any>, ) { const parameters = Object.keys(args) const parameterValues = Object.values(args).map( convertYaksokDataIntoQuickJSData, ) return `((${parameters.join( ', ', )}) => {${bodyCode}})(${parameterValues.join(', ')})` } function convertYaksokDataIntoQuickJSData(data: ValueType) { if (data instanceof StringValue) { return `"${data.value}"` } else if (data instanceof ListValue) { if (data.entries.size === 0) { return '[]' } const keys = Array.from(data.entries.keys()) const maxKey = Math.max(...keys) const targetArrayElements = new Array(maxKey + 1) for (let i = 0; i <= maxKey; i++) { const value = data.entries.get(i) if (value !== undefined) { targetArrayElements[i] = convertYaksokDataIntoQuickJSData(value) } else { targetArrayElements[i] = 'null' } } return `[${targetArrayElements.join(', ')}]` } else if (data instanceof PrimitiveValue) { return data.value } else { throw new Error( `Cannot convert unsupported data type to JS String Literal: ${data.constructor.name}`, ) } } function convertJSDataIntoQuickJSData(data: any, context: QuickJSContext) { if (typeof data === 'string') { return context.newString(data) } else if (typeof data === 'number') { return context.newNumber(data) } else if (Array.isArray(data)) { const arrayData = [...data] const array = context.newArray() for (const item in arrayData) { context.setProp( array, item, convertJSDataIntoQuickJSData(arrayData[item], context), ) } return array } else if (typeof data === 'object') { const object = context.newObject() for (const [key, value] of Object.entries(data)) { context.setProp( object, key, convertJSDataIntoQuickJSData(value, context), ) } return object } throw new Error('Unsupported data type: ' + typeof data) } function convertJSDataIntoYaksok(data: unknown): ValueType { if (typeof data === 'string') { return new StringValue(data) } else if (typeof data === 'number') { return new NumberValue(data) } else if (Array.isArray(data)) { return new ListValue(data.map(convertJSDataIntoYaksok)) } throw new Error('Unsupported data type: ' + typeof data) } interface QuickJSErrorData { name: string message: string stack: string } export class QuickJSInternalError extends ErrorInFFIExecution { constructor(error: QuickJSErrorData) { let output = '' output += error.message + '\n\n' output += '┌─────\n' output += error.stack .split('\n') .filter((line) => line.length) .map((line, index) => `│ ${dim(index + 1)}${line}`) .join('\n') output += '\n└─────' super({ message: output, }) this.name = error.name this.stack = error.stack } } export class QuickJSNotInitializedError extends Error { constructor() { super('QuickJS instance is not initialized yet') } }