├── .github
├── CODEOWNERS
├── dependabot.yml
└── workflows
│ ├── ci_install.yml
│ ├── ci.yml
│ └── publish.yml
├── .vscode
└── ltex.dictionary.en-US.txt
├── examples
├── kitchen-sink
│ ├── .gitignore
│ ├── src
│ │ ├── index.d.ts
│ │ └── index.ts
│ ├── tsconfig.json
│ ├── README.md
│ ├── esbuild.js
│ ├── package.json
│ ├── LICENSE
│ └── package-lock.json
├── react
│ ├── .gitignore
│ ├── src
│ │ ├── index.d.ts
│ │ └── index.tsx
│ ├── tsconfig.json
│ ├── esbuild.js
│ ├── package.json
│ └── package-lock.json
├── bundled
│ ├── .gitignore
│ ├── src
│ │ ├── index.d.ts
│ │ └── index.ts
│ ├── tsconfig.json
│ ├── esbuild.js
│ ├── package.json
│ └── package-lock.json
├── base64
│ ├── script.d.ts
│ ├── jsconfig.json
│ └── script.js
├── console
│ ├── script.d.ts
│ ├── jsconfig.json
│ └── script.js
├── exception
│ ├── script.d.ts
│ ├── script.js
│ └── jsconfig.json
├── try-catch
│ ├── script.d.ts
│ ├── jsconfig.json
│ └── script.js
├── simple_js
│ ├── script.d.ts
│ ├── jsconfig.json
│ └── script.js
├── exports
│ ├── jsconfig.json
│ ├── script.d.ts
│ └── script.js
└── host_funcs
│ ├── jsconfig.json
│ ├── script.d.ts
│ ├── go.mod
│ ├── script.js
│ ├── go.sum
│ └── main.go
├── .gitignore
├── .cargo
└── config.toml
├── crates
├── cli
│ ├── script.js
│ ├── Cargo.toml
│ ├── src
│ │ ├── options.rs
│ │ ├── opt.rs
│ │ ├── main.rs
│ │ ├── ts_parser.rs
│ │ └── shims.rs
│ └── build.rs
└── core
│ ├── build.rs
│ ├── src
│ ├── prelude
│ │ ├── src
│ │ │ ├── config.ts
│ │ │ ├── var.ts
│ │ │ ├── date.ts
│ │ │ ├── index.ts
│ │ │ ├── text-encoder.ts
│ │ │ ├── host.ts
│ │ │ ├── http.ts
│ │ │ ├── memory-handle.ts
│ │ │ ├── text-decoder.ts
│ │ │ ├── console.ts
│ │ │ └── memory.ts
│ │ ├── tsconfig.json
│ │ ├── types
│ │ │ ├── plugin-interface.d.ts
│ │ │ └── polyfills.d.ts
│ │ ├── esbuild.js
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── .gitignore
│ │ └── package-lock.json
│ └── lib.rs
│ └── Cargo.toml
├── Cargo.toml
├── example-schema.yaml
├── LICENSE
├── install-wasi-sdk.sh
├── install-wasi-sdk.ps1
├── install-windows.ps1
├── Makefile
├── install.sh
└── README.md
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @bhelx @zshipko
2 |
--------------------------------------------------------------------------------
/.vscode/ltex.dictionary.en-US.txt:
--------------------------------------------------------------------------------
1 | Extism
2 |
--------------------------------------------------------------------------------
/examples/kitchen-sink/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
--------------------------------------------------------------------------------
/examples/react/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
3 |
--------------------------------------------------------------------------------
/examples/bundled/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /Cargo.lock
3 | wasi-sdk
4 | *.wasm
5 | .venv
6 |
--------------------------------------------------------------------------------
/.cargo/config.toml:
--------------------------------------------------------------------------------
1 | [target.aarch64-unknown-linux-gnu]
2 | linker = "aarch64-linux-gnu-gcc"
3 |
--------------------------------------------------------------------------------
/crates/cli/script.js:
--------------------------------------------------------------------------------
1 | function add(a, b) {
2 | return 'Hello'
3 | }
4 |
5 | exports = {add}
6 |
--------------------------------------------------------------------------------
/examples/base64/script.d.ts:
--------------------------------------------------------------------------------
1 | declare module "main" {
2 | export function greet(): I32;
3 | }
4 |
--------------------------------------------------------------------------------
/examples/console/script.d.ts:
--------------------------------------------------------------------------------
1 | declare module "main" {
2 | export function greet(): I32;
3 | }
4 |
--------------------------------------------------------------------------------
/examples/bundled/src/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module "main" {
2 | export function greet(): I32;
3 | }
4 |
--------------------------------------------------------------------------------
/examples/exception/script.d.ts:
--------------------------------------------------------------------------------
1 | declare module "main" {
2 | export function greet(): I32;
3 | }
4 |
--------------------------------------------------------------------------------
/examples/try-catch/script.d.ts:
--------------------------------------------------------------------------------
1 | declare module "main" {
2 | export function greet(): I32;
3 | }
4 |
--------------------------------------------------------------------------------
/examples/kitchen-sink/src/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module "main" {
2 | export function greet(): I32;
3 | }
4 |
--------------------------------------------------------------------------------
/crates/core/build.rs:
--------------------------------------------------------------------------------
1 | fn main() {
2 | println!("cargo:rerun-if-changed=src/prelude/dist/index.js");
3 | }
4 |
--------------------------------------------------------------------------------
/examples/simple_js/script.d.ts:
--------------------------------------------------------------------------------
1 | declare module "main" {
2 | export function goodbye(): I32;
3 | export function greet(): I32;
4 | }
5 |
--------------------------------------------------------------------------------
/examples/react/src/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module "main" {
2 | export function setState(): I32;
3 | export function render(): I32;
4 | }
5 |
--------------------------------------------------------------------------------
/examples/console/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": [],
4 | "types": ["../../crates/core/src/prelude"]
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/examples/exports/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": [],
4 | "types": ["../../crates/core/src/prelude"]
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/examples/host_funcs/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": [],
4 | "types": ["../../crates/core/src/prelude"]
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/examples/simple_js/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": [],
4 | "types": ["../../crates/core/src/prelude"]
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/crates/core/src/prelude/src/config.ts:
--------------------------------------------------------------------------------
1 | declare global {
2 | var Config: {
3 | get(key: string): string | null;
4 | };
5 | }
6 |
7 | export {};
8 |
--------------------------------------------------------------------------------
/examples/exception/script.js:
--------------------------------------------------------------------------------
1 |
2 | function greet() {
3 | throw new Error("I am a JS exception shibboleth")
4 | }
5 |
6 | module.exports = { greet };
7 |
--------------------------------------------------------------------------------
/examples/base64/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": [],
4 | "types": [
5 | "../../crates/core/src/prelude"
6 | ]
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/examples/exception/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": [],
4 | "types": [
5 | "../../crates/core/src/prelude"
6 | ]
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/examples/try-catch/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": [],
4 | "types": [
5 | "../../crates/core/src/prelude"
6 | ]
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/examples/exports/script.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'main' {
2 | export function add3(a: I32, b: I32, c: I32): I32;
3 | export function appendString(a: PTR, b: PTR): PTR;
4 | }
5 |
6 |
--------------------------------------------------------------------------------
/examples/bundled/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": [],
4 | "types": ["@extism/js-pdk"],
5 | "noEmit": true
6 | },
7 | "include": ["src/**/*"]
8 | }
9 |
--------------------------------------------------------------------------------
/examples/react/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": [],
4 | "types": ["@extism/js-pdk"],
5 | "noEmit": true
6 | },
7 | "include": ["src/**/*"]
8 | }
9 |
--------------------------------------------------------------------------------
/examples/kitchen-sink/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": [],
4 | "types": ["@extism/js-pdk"],
5 | "noEmit": true
6 | },
7 | "include": ["src/**/*"]
8 | }
9 |
--------------------------------------------------------------------------------
/examples/kitchen-sink/README.md:
--------------------------------------------------------------------------------
1 | # Extism JS PDK Plugin
2 |
3 | See more documentation at https://github.com/extism/js-pdk and
4 | [join us on Discord](https://extism.org/discord) for more help.
5 |
--------------------------------------------------------------------------------
/crates/core/src/prelude/src/var.ts:
--------------------------------------------------------------------------------
1 | declare global {
2 | var Var: {
3 | set(name: string, value: string | ArrayBufferLike): void;
4 | getBytes(name: string): ArrayBufferLike | null;
5 | getString(name: string): string | null;
6 | };
7 | }
8 |
9 | export {};
10 |
--------------------------------------------------------------------------------
/crates/core/src/prelude/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outFile": "dist/index.d.ts",
4 | "declaration": true,
5 | "emitDeclarationOnly": true,
6 | "stripInternal": true,
7 | "lib": [],
8 | "types": []
9 | },
10 | "include": ["src/**/*", "types/**/*"]
11 | }
12 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | resolver = "2"
3 | members = [
4 | "crates/core",
5 | "crates/cli",
6 | ]
7 |
8 | [workspace.package]
9 | version = "1.5.1"
10 | edition = "2021"
11 | authors = ["The Extism Authors"]
12 | license = "BSD-Clause-3"
13 |
14 | [workspace.dependencies]
15 | anyhow = "^1.0.68"
16 |
--------------------------------------------------------------------------------
/crates/core/src/prelude/types/plugin-interface.d.ts:
--------------------------------------------------------------------------------
1 | declare type I32 = number;
2 | declare type I64 = number | bigint;
3 | declare type F32 = number;
4 | declare type F64 = number;
5 | declare type PTR = I64;
6 |
7 | declare module "extism:host" {
8 | interface user { }
9 | }
10 |
11 | declare module "main" { }
12 |
--------------------------------------------------------------------------------
/crates/core/src/prelude/esbuild.js:
--------------------------------------------------------------------------------
1 | const esbuild = require("esbuild");
2 |
3 | esbuild
4 | .build({
5 | entryPoints: ["src/index.ts"],
6 | outdir: "dist",
7 | bundle: true,
8 | minify: true,
9 | format: "iife",
10 | target: ["es2020"], // don't go over es2020 because quickjs doesn't support it
11 | });
12 |
--------------------------------------------------------------------------------
/examples/simple_js/script.js:
--------------------------------------------------------------------------------
1 | /**
2 | * A simple example of a JavaScript, CJS flavored plug-in:
3 | */
4 |
5 | function greet() {
6 | Host.outputString(`Hello, ${Host.inputString()}!`);
7 | }
8 |
9 | function goodbye() {
10 | Host.outputString(`Goodbye, ${Host.inputString()}!`);
11 | }
12 |
13 | module.exports = { greet, goodbye };
14 |
--------------------------------------------------------------------------------
/examples/host_funcs/script.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'main' {
2 | export function greet(): I32;
3 | }
4 |
5 | declare module 'extism:host' {
6 | interface user {
7 | capitalize(ptr: PTR): PTR;
8 | floatInputs(p1: F64, p2: F32): I32;
9 | floatOutput(p1: I32): F64;
10 | voidInputs(p1: I32, p2: I64, p3: F32, p4: F64, p5: I32): void;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/exports/script.js:
--------------------------------------------------------------------------------
1 | /**
2 | * A simple example of generate non-plugin function exports
3 | */
4 |
5 | function add3(a, b, c) {
6 | return a + b + c;
7 | }
8 |
9 | function appendString(a, b) {
10 | a = Memory.find(a).readString();
11 | b = Memory.find(b).readString();
12 | return Memory.fromString(a + b).offset;
13 | }
14 |
15 | module.exports = { add3, appendString };
16 |
--------------------------------------------------------------------------------
/examples/bundled/esbuild.js:
--------------------------------------------------------------------------------
1 | const esbuild = require('esbuild');
2 |
3 | esbuild
4 | .build({
5 | entryPoints: ['src/index.ts'],
6 | outdir: 'dist',
7 | bundle: true,
8 | sourcemap: true,
9 | minify: false, // might want to use true for production build
10 | format: 'cjs', // needs to be CJS for now
11 | target: ['es2020'] // don't go over es2020 because quickjs doesn't support it
12 | })
13 |
--------------------------------------------------------------------------------
/examples/kitchen-sink/esbuild.js:
--------------------------------------------------------------------------------
1 | const esbuild = require('esbuild');
2 |
3 | esbuild
4 | .build({
5 | entryPoints: ['src/index.ts'],
6 | outdir: 'dist',
7 | bundle: true,
8 | sourcemap: true,
9 | minify: false, // might want to use true for production build
10 | format: 'cjs', // needs to be CJS for now
11 | target: ['es2020'] // don't go over es2020 because quickjs doesn't support it
12 | })
--------------------------------------------------------------------------------
/examples/react/esbuild.js:
--------------------------------------------------------------------------------
1 | const esbuild = require('esbuild');
2 |
3 | esbuild
4 | .build({
5 | entryPoints: ['src/index.tsx'],
6 | outdir: 'dist',
7 | bundle: true,
8 | sourcemap: true,
9 | minify: false, // might want to use true for production build
10 | format: 'cjs', // needs to be CJS for now
11 | target: ['es2020'] // don't go over es2020 because quickjs doesn't support it
12 | })
13 |
--------------------------------------------------------------------------------
/crates/core/src/prelude/src/date.ts:
--------------------------------------------------------------------------------
1 | declare global {
2 | /**
3 | * @internal
4 | */
5 | function __getTime(): string;
6 | }
7 |
8 | globalThis.Date = new Proxy(Date, {
9 | apply() {
10 | return __getTime();
11 | },
12 | construct(target, args) {
13 | if (args.length === 0) return new target(__getTime());
14 |
15 | return Reflect.construct(target, args);
16 | },
17 | });
18 |
19 | export {};
20 |
--------------------------------------------------------------------------------
/examples/try-catch/script.js:
--------------------------------------------------------------------------------
1 | function greet() {
2 | callListeners();
3 | }
4 |
5 | const LISTENERS = {
6 | hello: async () => {
7 | console.log("inside closure");
8 | throw new Error("hello world");
9 | },
10 | };
11 |
12 | async function callListeners() {
13 | try {
14 | await LISTENERS["hello"]();
15 | } catch (error) {
16 | console.log("got error", error);
17 | }
18 | }
19 |
20 | module.exports = { greet };
21 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "cargo" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "weekly"
12 |
--------------------------------------------------------------------------------
/examples/bundled/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bundled-plugin",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "src/index.ts",
6 | "scripts": {
7 | "build": "cross-env NODE_ENV=production node esbuild.js && cross-env ../../target/release/extism-js dist/index.js -i src/index.d.ts -o ../bundled.wasm"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "BSD-3-Clause",
12 | "devDependencies": {
13 | "@extism/js-pdk": "../../crates/core/src/prelude",
14 | "cross-env": "^7.0.3",
15 | "esbuild": "^0.19.6",
16 | "typescript": "^5.3.2"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/crates/cli/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "js-pdk-cli"
3 | authors.workspace = true
4 | version.workspace = true
5 | edition.workspace = true
6 | license.workspace = true
7 | build = "build.rs"
8 |
9 | [[bin]]
10 | name = "extism-js"
11 | path = "src/main.rs"
12 |
13 | [dependencies]
14 | anyhow = { workspace = true }
15 | wizer = "4"
16 | structopt = "0.3"
17 | swc_atoms = "0.6.5"
18 | swc_common = "0.33.10"
19 | swc_ecma_ast = "0.112"
20 | swc_ecma_parser = "0.143"
21 | serde = { version = "= 1.0.219", features = ["derive"] }
22 | wagen = "0.1"
23 | log = "0.4"
24 | tempfile = "3"
25 | env_logger = "0.11"
26 |
--------------------------------------------------------------------------------
/examples/kitchen-sink/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "kitchen-sink",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "src/index.ts",
6 | "scripts": {
7 | "build": "cross-env NODE_ENV=production node esbuild.js && cross-env ../../target/release/extism-js dist/index.js -i src/index.d.ts -o ../kitchen-sink.wasm"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "BSD-3-Clause",
12 | "devDependencies": {
13 | "@extism/js-pdk": "../../crates/core/src/prelude",
14 | "cross-env": "^7.0.3",
15 | "esbuild": "^0.19.6",
16 | "typescript": "^5.3.2"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/crates/core/src/prelude/README.md:
--------------------------------------------------------------------------------
1 | # extism js-pdk types
2 |
3 | TypeScript definitions for the Extism [JS Plugin Development Kit](https://github.com/extism/js-pdk).
4 |
5 | To install these types, add them to your `tsconfig.json`'s `types`:
6 |
7 | ```json
8 | {
9 | "compilerOptions": {
10 | "lib": [], // this ensures unsupported globals aren't suggested
11 | "types": ["@extism/js-pdk"], // while this makes the IDE aware of the ones that are
12 | }
13 | }
14 | ```
15 |
16 | ## Development
17 |
18 | The JavaScript prelude is defined in `crates/core/prelude`.
19 |
20 | ## License
21 |
22 | BSD-Clause-3
23 |
--------------------------------------------------------------------------------
/crates/core/src/prelude/src/index.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 | ///
4 | ///
5 |
6 | import "core-js/actual/url";
7 | import "core-js/actual/url/to-json";
8 | import "core-js/actual/url-search-params";
9 | import "urlpattern-polyfill";
10 |
11 | import "./config";
12 | import "./date";
13 | import "./text-decoder";
14 | import "./text-encoder";
15 | import "./host";
16 | import "./http";
17 | import "./memory";
18 | import "./memory-handle";
19 | import "./var";
20 | import "./console";
--------------------------------------------------------------------------------
/crates/core/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "js-pdk-core"
3 | version.workspace = true
4 | authors.workspace = true
5 | edition.workspace = true
6 | license.workspace = true
7 |
8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
9 |
10 | [dependencies]
11 | extism-pdk = "1.3"
12 | once_cell = "1.16"
13 | anyhow = { workspace = true }
14 | quickjs-wasm-rs = "3"
15 | chrono = { version = "0.4", default-features = false, features = ["clock"] }
16 | rquickjs = { version = "0.8", features = ["array-buffer", "bindgen"]}
17 | base64 = "0.22.1"
18 |
19 | [lib]
20 | crate-type = ["cdylib"]
21 |
--------------------------------------------------------------------------------
/examples/host_funcs/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/extism/js-pdk/examples/host_funcs
2 |
3 | go 1.23.4
4 |
5 | require github.com/extism/go-sdk v1.6.1
6 |
7 | require (
8 | github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a // indirect
9 | github.com/gobwas/glob v0.2.3 // indirect
10 | github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca // indirect
11 | github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 // indirect
12 | github.com/tetratelabs/wazero v1.8.1-0.20240916092830-1353ca24fef0 // indirect
13 | go.opentelemetry.io/proto/otlp v1.3.1 // indirect
14 | google.golang.org/protobuf v1.34.2 // indirect
15 | )
16 |
--------------------------------------------------------------------------------
/examples/react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-plugin",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "src/index.tsx",
6 | "scripts": {
7 | "build": "cross-env NODE_ENV=production node esbuild.js && cross-env ../../target/release/extism-js dist/index.js -i src/index.d.ts -o ../react.wasm"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "BSD-3-Clause",
12 | "devDependencies": {
13 | "@extism/js-pdk": "../../crates/core/src/prelude",
14 | "@types/react": "^18.3.1",
15 | "cross-env": "^7.0.3",
16 | "esbuild": "^0.19.6",
17 | "typescript": "^5.3.2"
18 | },
19 | "dependencies": {
20 | "react-dom": "^18.3.1"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/crates/core/src/prelude/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@extism/js-pdk",
3 | "version": "1.1.1",
4 | "description": "typescript definitions for the extism js-pdk",
5 | "types": "dist/index.d.ts",
6 | "files": ["dist/*.d.ts", "types/*.d.ts"],
7 | "scripts": {
8 | "build": "node esbuild.js && tsc"
9 | },
10 | "author": "The Extism Authors ",
11 | "license": "BSD-Clause-3",
12 | "dependencies": {
13 | "urlpattern-polyfill": "^8.0.2"
14 | },
15 | "devDependencies": {
16 | "core-js": "^3.30.2",
17 | "esbuild": "^0.17.19",
18 | "typescript": "^5.4.5"
19 | },
20 | "exports": {
21 | ".": {
22 | "default": null,
23 | "types": "./dist/index.d.ts"
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/base64/script.js:
--------------------------------------------------------------------------------
1 | function greet() {
2 | try {
3 | const base64String = "SGVsbG8g8J+MjSBXb3JsZCHwn4yN";
4 |
5 | console.log('decoding string:', base64String);
6 |
7 | const decodedBuffer = Host.base64ToArrayBuffer(base64String);
8 | const decodedString = new TextDecoder().decode(decodedBuffer);
9 |
10 | console.log('decoded string:', decodedString);
11 |
12 | const encodedBuffer = Host.arrayBufferToBase64(decodedBuffer);
13 |
14 | console.log('encoded string:', encodedBuffer);
15 |
16 | Host.outputString(`Hello, ${Host.inputString()}`)
17 | } catch (error) {
18 | console.error('Base64 test failed, Error:', JSON.stringify(error));
19 | }
20 | }
21 |
22 | module.exports = { greet };
23 |
--------------------------------------------------------------------------------
/examples/bundled/src/index.ts:
--------------------------------------------------------------------------------
1 | export function greet() {
2 | Var.set("name", "MAYBESteve");
3 | let extra = new TextEncoder().encode("aaa");
4 | let decoded = new TextDecoder().decode(extra);
5 | const res = Http.request({ url: "http://example.com", method: "GET" });
6 | const name = Var.getString("name") || "unknown";
7 | const apiKey = Config.get("SOME_API_KEY") || "unknown";
8 |
9 | Host.outputString(
10 | `Hello, ${Host.inputString()} (or is it ${name}???) ${decoded} ${new Date().toString()}\n\n${
11 | res.body
12 | }\n\n ==== KEY: ${apiKey}`
13 | );
14 | }
15 |
16 | // test this bundled.wasm like so:
17 | // extism call ../bundled.wasm greet --input "steve" --wasi --allow-host "*" --config SOME_API_KEY=123456789
18 |
--------------------------------------------------------------------------------
/crates/core/src/prelude/src/text-encoder.ts:
--------------------------------------------------------------------------------
1 | declare global {
2 | /**
3 | * @internal
4 | */
5 | function __encodeStringToUtf8Buffer(input: string): ArrayBufferLike;
6 | }
7 |
8 | class TextEncoder implements globalThis.TextEncoder {
9 | readonly encoding: string;
10 |
11 | constructor() {
12 | this.encoding = "utf-8";
13 | }
14 |
15 | encode(input: string = ""): Uint8Array {
16 | input = input.toString(); // non-string inputs are converted to strings
17 | return new Uint8Array(__encodeStringToUtf8Buffer(input));
18 | }
19 |
20 | encodeInto(
21 | _source: string,
22 | _destination: Uint8Array
23 | ): TextEncoderEncodeIntoResult {
24 | throw new Error("encodeInto is not supported");
25 | }
26 | }
27 |
28 | globalThis.TextEncoder = TextEncoder;
29 |
30 | export {};
31 |
--------------------------------------------------------------------------------
/.github/workflows/ci_install.yml:
--------------------------------------------------------------------------------
1 | name: "CI Install Script"
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | test:
7 | runs-on: ${{ matrix.os }}
8 | strategy:
9 | matrix:
10 | include:
11 | - name: linux
12 | os: ubuntu-24.04
13 | - name: windows
14 | os: windows-latest
15 | steps:
16 | - uses: actions/checkout@v2
17 |
18 | - name: Test Install Script (Linux)
19 | run: |
20 | ./install.sh
21 | extism-js --version
22 | if: runner.os != 'Windows'
23 |
24 | - name: Test Install Script Part1 (Windows)
25 | run: |
26 | powershell -executionpolicy bypass -File .\install-windows.ps1
27 | if: runner.os == 'Windows'
28 |
29 | - name: Test Install Script Part2 (Windows)
30 | run: |
31 | $env:Path = "C:\Program Files\Extism\;C:\Program Files\Binaryen\;" + $env:Path
32 | extism-js --version
33 | if: runner.os == 'Windows'
34 |
--------------------------------------------------------------------------------
/crates/cli/src/options.rs:
--------------------------------------------------------------------------------
1 | use std::path::PathBuf;
2 | use structopt::StructOpt;
3 |
4 | #[derive(Debug, StructOpt)]
5 | #[structopt(name = "extism-js", about = "Extism JavaScript PDK Plugin Compiler")]
6 | pub struct Options {
7 | #[structopt(
8 | parse(from_os_str),
9 | about = "Input JS program for the plugin. Needs to be a single file."
10 | )]
11 | pub input_js: PathBuf,
12 |
13 | #[structopt(
14 | short = "i",
15 | parse(from_os_str),
16 | default_value = "index.d.ts",
17 | about = "d.ts file describing the plug-in interface."
18 | )]
19 | pub interface_file: PathBuf,
20 |
21 | #[structopt(
22 | short = "o",
23 | parse(from_os_str),
24 | default_value = "index.wasm",
25 | about = "Ouput wasm file."
26 | )]
27 | pub output: PathBuf,
28 |
29 | #[structopt(short = "c", about = "Include the core")]
30 | pub core: bool,
31 |
32 | #[structopt(long = "--skip-opt", about = "Skip final optimization pass")]
33 | pub skip_opt: bool,
34 | }
35 |
--------------------------------------------------------------------------------
/example-schema.yaml:
--------------------------------------------------------------------------------
1 | # yaml-language-server: $schema=https://xtp.dylibso.com/assets/wasm/schema.json
2 | # Learn more at https://docs.xtp.dylibso.com/docs/concepts/xtp-schema
3 | version: v1-draft
4 | exports:
5 | CountVowels:
6 | input:
7 | type: string
8 | contentType: text/plain; charset=utf-8
9 | output:
10 | $ref: "#/components/schemas/VowelReport"
11 | contentType: application/json
12 | components:
13 | schemas:
14 | VowelReport:
15 | description: The result of counting vowels on the Vowels input.
16 | properties:
17 | count:
18 | type: integer
19 | format: int32
20 | description: The count of vowels for input string.
21 | total:
22 | type: integer
23 | format: int32
24 | description: The cumulative amount of vowels counted, if this keeps state across multiple function calls.
25 | nullable: true
26 | vowels:
27 | type: string
28 | description: The set of vowels used to get the count, e.g. "aAeEiIoOuU"
29 |
--------------------------------------------------------------------------------
/crates/core/src/prelude/src/host.ts:
--------------------------------------------------------------------------------
1 | declare global {
2 | interface Host {
3 | /**
4 | * @internal
5 | */
6 | __hostFunctions: Array<{ name: string; results: number }>;
7 |
8 | invokeFunc(id: number, ...args: unknown[]): number;
9 | inputBytes(): ArrayBufferLike;
10 | inputString(): string;
11 | outputBytes(output: ArrayBufferLike): boolean;
12 | outputString(output: string): boolean;
13 | getFunctions(): import("extism:host").user;
14 | arrayBufferToBase64(input: ArrayBuffer): string;
15 | base64ToArrayBuffer(input: string): ArrayBuffer;
16 | }
17 |
18 | var Host: Host;
19 | }
20 |
21 | Host.getFunctions = function () {
22 | return Host.__hostFunctions.reduce((funcs, meta, id) => {
23 | funcs[meta.name] = (...args: unknown[]) => {
24 | const sanitizedArgs = args.map(arg =>
25 | arg === undefined || arg === null ? 0 : arg
26 | );
27 | const result = Host.invokeFunc(id, ...sanitizedArgs);
28 | return meta.results === 0 ? undefined : result;
29 | };
30 | return funcs;
31 | }, {});
32 | };
33 |
34 | export { };
--------------------------------------------------------------------------------
/examples/host_funcs/script.js:
--------------------------------------------------------------------------------
1 | // Extract host functions by name.
2 | // Note: these must be declared in the d.ts file
3 | const { capitalize, floatInputs, floatOutput, voidInputs } = Host.getFunctions()
4 |
5 | function greet() {
6 | const name = Host.inputString();
7 | const mem = Memory.fromString(name);
8 | const capNameOffset = capitalize(mem.offset);
9 | const capName = mem.readString(capNameOffset);
10 | console.log(`Hello, ${capName}!`);
11 |
12 | const f32 = 314_567.5;
13 | const i32 = 2_147_483_647;
14 | const f64 = 9_007_199_254_740.125;
15 | const i64 = 9_223_372_036_854_775_807;
16 |
17 | const num1 = floatInputs(f64, f32);
18 | console.log(`floatInputs result: ${num1}`);
19 | if (num1 != i32) {
20 | throw new Error(`Unexpected floatInputs result: ${num1}. Expected: ${i32}`);
21 | }
22 |
23 | const num2 = floatOutput(i32);
24 | console.log(`floatOutput result: ${num2}`);
25 | if (Math.abs(num2 - f64) >= 0.001) {
26 | throw new Error(`Unexpected floatOutput result: ${num2}. Expected: ${f64}`);
27 | }
28 |
29 | voidInputs(i32, i64, f32, f64, i32);
30 |
31 | console.log("All tests passed!");
32 | Host.outputString(`Hello, ${capName}!`);
33 | }
34 |
35 | module.exports = { greet }
36 |
--------------------------------------------------------------------------------
/examples/kitchen-sink/src/index.ts:
--------------------------------------------------------------------------------
1 | export function greet() {
2 | let input = Host.inputString();
3 | Var.set("input", input);
4 | const req: HttpRequest = {
5 | url: "https://postman-echo.com/post",
6 | method: "POST",
7 | };
8 | let varInput = Var.getString("input");
9 | if (!varInput) {
10 | Host.outputString("failed to get var: input");
11 | return -1;
12 | }
13 | let resp = Http.request(req, varInput);
14 | if (resp.status !== 200) {
15 | return -2;
16 | }
17 | const body = JSON.parse(resp.body);
18 | if (body.data !== input) {
19 | Host.outputString("got unexpected output: " + body.data);
20 | return -3;
21 | }
22 |
23 | const configLastName = Config.get("last_name");
24 | if (!configLastName) {
25 | Host.outputString("failed to get config: last_name");
26 | return -4;
27 | }
28 |
29 | if (`${body.data} ${configLastName}` !== "Steve Manuel") {
30 | Host.outputString(`got unexpected output: ${body.data} ${configLastName}`);
31 | return -5;
32 | }
33 |
34 | const mem = Memory.fromString("Hello, " + body.data + " " + configLastName);
35 | Host.outputString(mem.readString()); // TODO: ideally have a way to output memory directly
36 | mem.free();
37 |
38 | return 0;
39 | }
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2022 Dylibso, Inc.
2 |
3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4 |
5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6 |
7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8 |
9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10 |
11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/crates/core/src/prelude/src/http.ts:
--------------------------------------------------------------------------------
1 | declare global {
2 | interface HttpRequest {
3 | url: string;
4 | // method defaults to "GET" if not provided
5 | method?:
6 | | "GET"
7 | | "HEAD"
8 | | "POST"
9 | | "PUT"
10 | | "DELETE"
11 | | "CONNECT"
12 | | "OPTIONS"
13 | | "TRACE"
14 | | "PATCH";
15 | headers?: Record;
16 | }
17 |
18 | interface HttpResponse {
19 | body: string;
20 | status: number;
21 | /**
22 | * the host needs to enable allow_http_response_headers for this to be present
23 | */
24 | headers?: Record;
25 | }
26 |
27 | var Http: {
28 | request(req: HttpRequest, body?: string | ArrayBufferLike): HttpResponse;
29 | };
30 | }
31 |
32 | Http.request = new Proxy(Http.request, {
33 | apply(target, thisArg, [req, body]) {
34 | // convert body to string if it's an arraybuffer
35 | if (typeof body === "object" && "byteLength" in body) {
36 | body = new Uint8Array(body).toString();
37 | }
38 |
39 | if (req.method === undefined) {
40 | req.method = "GET";
41 | }
42 |
43 | return Reflect.apply(
44 | target,
45 | thisArg,
46 | // TODO: We need to completely avoid passing a second argument due to a bug in the runtime,
47 | // which converts `undefined` to `"undefined"`. This is also the case for req.method.
48 | body !== undefined ? [req, body] : [req],
49 | );
50 | },
51 | });
52 |
53 | export {};
54 |
--------------------------------------------------------------------------------
/install-wasi-sdk.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | #set -euo pipefail
4 |
5 | if [[ "$(basename $(pwd))" != "js-pdk" ]]; then
6 | echo "Run this inside in the root of the js-pdk repo" 1>&2
7 | exit 1
8 | fi
9 |
10 | # Don't try and install the wasi-sdk if the user has specified the wasi-sdk is installed elsewhere
11 | set +u
12 | if [[ -n "$QUICKJS_WASM_SYS_WASI_SDK_PATH" ]]; then
13 | # Check that something is present where the user says the wasi-sdk is located
14 | if [[ ! -d "$QUICKJS_WASM_SYS_WASI_SDK_PATH" ]]; then
15 | echo "Download the wasi-sdk to $QUICKJS_WASM_SYS_WASI_SDK_PATH" 1>&2
16 | exit 1
17 | fi
18 | exit 0
19 | fi
20 | set -u
21 |
22 | ARCH=`uname -m`
23 | case "$ARCH" in
24 | ix86*|x86_64*) ARCH="x86_64" ;;
25 | arm64*|aarch64*) ARCH="arm64" ;;
26 | *) echo "unknown arch: $ARCH" && exit 1 ;;
27 | esac
28 |
29 | PATH_TO_SDK="wasi-sdk"
30 | if [[ ! -d $PATH_TO_SDK ]]; then
31 | TMPGZ=$(mktemp)
32 | VERSION_MAJOR="24"
33 | VERSION_MINOR="0"
34 | if [[ "$(uname -s)" == "Darwin" ]]; then
35 | curl --fail --location https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${VERSION_MAJOR}/wasi-sdk-${VERSION_MAJOR}.${VERSION_MINOR}-${ARCH}-macos.tar.gz --output $TMPGZ
36 | else
37 | curl --fail --location https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${VERSION_MAJOR}/wasi-sdk-${VERSION_MAJOR}.${VERSION_MINOR}-${ARCH}-linux.tar.gz --output $TMPGZ
38 | fi
39 | mkdir $PATH_TO_SDK
40 | tar xf $TMPGZ -C $PATH_TO_SDK --strip-components=1
41 | fi
42 |
--------------------------------------------------------------------------------
/examples/kitchen-sink/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2024, The Extism Authors.
2 |
3 | Redistribution and use in source and binary forms, with or without modification,
4 | are permitted provided that the following conditions are met:
5 |
6 | 1. Redistributions of source code must retain the above copyright notice, this
7 | list of conditions and the following disclaimer.
8 |
9 | 2. Redistributions in binary form must reproduce the above copyright notice,
10 | this list of conditions and the following disclaimer in the documentation
11 | and/or other materials provided with the distribution.
12 |
13 | 3. Neither the name of the copyright holder nor the names of its contributors
14 | may be used to endorse or promote products derived from this software without
15 | specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
21 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
24 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 |
--------------------------------------------------------------------------------
/crates/core/src/prelude/src/memory-handle.ts:
--------------------------------------------------------------------------------
1 | declare global {
2 | interface MemoryHandle {
3 | offset: PTR;
4 | len: I64;
5 |
6 | readString(): string;
7 | readUInt32(): number;
8 | readUInt64(): bigint;
9 | readFloat32(): number;
10 | readUFloat64(): number;
11 | readBytes(): ArrayBuffer;
12 | readJsonObject(): T;
13 | free(): void;
14 | }
15 |
16 | var MemoryHandle: {
17 | prototype: MemoryHandle;
18 | new(offset: PTR, len: I64): MemoryHandle;
19 | };
20 | }
21 |
22 | class MemoryHandle implements globalThis.MemoryHandle {
23 | offset: PTR;
24 | len: I64;
25 |
26 | constructor(offset: PTR, len: I64) {
27 | this.offset = offset;
28 | this.len = len;
29 | }
30 |
31 | readString() {
32 | return new TextDecoder().decode(this.readBytes());
33 | }
34 |
35 | readUInt32() {
36 | const bytes = this.readBytes();
37 | const arr = new Uint32Array(bytes);
38 | return arr[0];
39 | }
40 |
41 | readUInt64() {
42 | const bytes = this.readBytes();
43 | const arr = new BigUint64Array(bytes);
44 | return arr[0];
45 | }
46 |
47 | readFloat32() {
48 | const bytes = this.readBytes();
49 | const arr = new Float32Array(bytes);
50 | return arr[0];
51 | }
52 |
53 | readUFloat64() {
54 | const bytes = this.readBytes();
55 | const arr = new Float64Array(bytes);
56 | return arr[0];
57 | }
58 |
59 | readBytes() {
60 | return Memory._readBytes(this.offset);
61 | }
62 |
63 | readJsonObject() {
64 | return JSON.parse(this.readString());
65 | }
66 |
67 | free() {
68 | Memory._free(this.offset);
69 | }
70 | }
71 |
72 | globalThis.MemoryHandle = MemoryHandle;
73 |
74 | export { };
75 |
--------------------------------------------------------------------------------
/examples/console/script.js:
--------------------------------------------------------------------------------
1 | /**
2 | * An example of a plugin that uses Console extensively, CJS flavored plug-in:
3 | */
4 |
5 | function greet() {
6 | const n = 1;
7 | tryPrint('n + 1', n + 1);
8 | tryPrint('multiple string args', 'one', 'two', 'three');
9 | tryPrint('single n', n);
10 | tryPrint('three ns', n, n, n);
11 | tryPrint('n with label', 'n', n);
12 | tryPrint('boolean', true);
13 | tryPrint('null', null);
14 | tryPrint('undefined', undefined);
15 | tryPrint('empty object', {});
16 | tryPrint('empty array', []);
17 | tryPrint('object with key', { key: 'value' });
18 | console.warn('This is a warning', 123);
19 | console.error('This is an error', 456);
20 | console.info('This is an info', 789);
21 | console.debug('This is a debug', 101112);
22 | console.trace('This is a trace', 131415);
23 |
24 | console.log('This is an object', { key: 'value' });
25 | console.log('This is an array', [1, 2, 3]);
26 | console.log('This is a string', 'Hello, World!');
27 | console.log('This is a number', 123);
28 | console.log('This is a boolean', true);
29 | console.log('This is a null', null);
30 | console.log('This is an undefined', undefined);
31 | console.log('This is a function', function() {});
32 | console.log('This is a symbol', Symbol('test'));
33 | console.log('This is a date', new Date());
34 | console.log('This is an error', new Error('Hi there!'));
35 | console.log('This is a map', new Map([[1, 'one'], [2, 'two']] ));
36 | }
37 |
38 | function tryPrint(text, ...args) {
39 | try {
40 | console.log(...args);
41 | console.log(`${text} - ✅`);
42 | } catch (e) {
43 | console.log(`${text} - ❌ - ${e.message}`);
44 | }
45 | console.log('------')
46 | }
47 |
48 |
49 | module.exports = { greet };
50 |
--------------------------------------------------------------------------------
/crates/cli/build.rs:
--------------------------------------------------------------------------------
1 | use std::env;
2 | use std::fs;
3 | use std::path::PathBuf;
4 |
5 | fn main() {
6 | if let Ok("cargo-clippy") = env::var("CARGO_CFG_FEATURE").as_ref().map(String::as_str) {
7 | stub_engine_for_clippy();
8 | } else {
9 | copy_engine_binary();
10 | }
11 | }
12 |
13 | // When using clippy, we need to write a stubbed engine.wasm file to ensure compilation succeeds. This
14 | // skips building the actual engine.wasm binary that would be injected into the CLI binary.
15 | fn stub_engine_for_clippy() {
16 | let engine_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("engine.wasm");
17 |
18 | if !engine_path.exists() {
19 | std::fs::write(engine_path, []).expect("failed to write empty engine.wasm stub");
20 | println!("cargo:warning=using stubbed engine.wasm for static analysis purposes...");
21 | }
22 | }
23 |
24 | // Copy the engine binary build from the `core` crate
25 | fn copy_engine_binary() {
26 | let override_engine_path = env::var("EXTISM_ENGINE_PATH");
27 | let is_override = override_engine_path.is_ok();
28 | let mut engine_path = PathBuf::from(
29 | override_engine_path.unwrap_or_else(|_| env::var("CARGO_MANIFEST_DIR").unwrap()),
30 | );
31 |
32 | if !is_override {
33 | engine_path.pop();
34 | engine_path.pop();
35 | engine_path = engine_path.join("target/wasm32-wasip1/release/js_pdk_core.wasm");
36 | }
37 |
38 | println!("cargo:rerun-if-changed={}", engine_path.to_str().unwrap());
39 | println!("cargo:rerun-if-changed=build.rs");
40 |
41 | if engine_path.exists() {
42 | let copied_engine_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("engine.wasm");
43 | fs::copy(&engine_path, copied_engine_path).unwrap();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/install-wasi-sdk.ps1:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env pwsh
2 |
3 | $7z= "7z"
4 | if (-not (Get-Command $7z -ErrorAction SilentlyContinue)){
5 | $7z= "$env:Programfiles\7-Zip\7z.exe"
6 | }
7 | $tempPath= "$env:TEMP"
8 |
9 |
10 | if ((Split-Path -Leaf (Get-Location)) -ne "js-pdk") {
11 | Write-Error "Run this inside the root of the js-pdk repo"
12 | exit 1
13 | }
14 |
15 | if ($env:QUICKJS_WASM_SYS_WASI_SDK_PATH) {
16 | if (-Not (Test-Path -Path $env:QUICKJS_WASM_SYS_WASI_SDK_PATH)) {
17 | Write-Error "Download the wasi-sdk to $env:QUICKJS_WASM_SYS_WASI_SDK_PATH"
18 | exit 1
19 | }
20 | exit 0
21 | }
22 |
23 | $PATH_TO_SDK = "wasi-sdk"
24 | if (-Not (Test-Path -Path $PATH_TO_SDK)) {
25 | $VERSION_MAJOR = "24"
26 | $VERSION_MINOR = "0"
27 | $url = "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-$VERSION_MAJOR/wasi-sdk-$VERSION_MAJOR.$VERSION_MINOR-mingw.tar.gz"
28 | Invoke-WebRequest -Uri $url -OutFile "$tempPath\wasi-sdk-$VERSION_MAJOR.$VERSION_MINOR-mingw.tar.gz"
29 |
30 | New-Item -ItemType Directory -Path $PATH_TO_SDK | Out-Null
31 |
32 | & $7z x "$tempPath\wasi-sdk-$VERSION_MAJOR.$VERSION_MINOR-mingw.tar.gz" -o"$tempPath" | Out-Null
33 | & $7z x -ttar "$tempPath\wasi-sdk-$VERSION_MAJOR.$VERSION_MINOR-mingw.tar" -o"$tempPath" | Out-Null
34 |
35 | Get-ChildItem -Path "$tempPath\wasi-sdk-$VERSION_MAJOR.$VERSION_MINOR" | ForEach-Object {
36 | Move-Item -Path $_.FullName -Destination $PATH_TO_SDK -Force | Out-Null
37 | }
38 |
39 | Remove-Item -Path "$tempPath\wasi-sdk-$VERSION_MAJOR.$VERSION_MINOR" -Recurse -Force | Out-Null
40 | Remove-Item -Path "$tempPath\wasi-sdk-$VERSION_MAJOR.$VERSION_MINOR-mingw.tar" -Recurse -Force | Out-Null
41 | Remove-Item -Path "$tempPath\wasi-sdk-$VERSION_MAJOR.$VERSION_MINOR-mingw.tar.gz" -Recurse -Force | Out-Null
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/crates/cli/src/opt.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{Error, Result};
2 | use std::{
3 | path::Path,
4 | process::{Command, Stdio},
5 | };
6 | use wizer::Wizer;
7 |
8 | pub(crate) struct Optimizer<'a> {
9 | wizen: bool,
10 | optimize: bool,
11 | wasm: &'a [u8],
12 | }
13 |
14 | impl<'a> Optimizer<'a> {
15 | pub fn new(wasm: &'a [u8]) -> Self {
16 | Self {
17 | wasm,
18 | optimize: false,
19 | wizen: false,
20 | }
21 | }
22 |
23 | #[allow(unused)]
24 | pub fn optimize(self, optimize: bool) -> Self {
25 | Self { optimize, ..self }
26 | }
27 |
28 | pub fn wizen(self, wizen: bool) -> Self {
29 | Self { wizen, ..self }
30 | }
31 |
32 | pub fn write_optimized_wasm(self, dest: impl AsRef) -> Result<(), Error> {
33 | if self.wizen {
34 | let wasm = Wizer::new()
35 | .allow_wasi(true)?
36 | .inherit_stdio(true)
37 | .wasm_bulk_memory(true)
38 | .run(self.wasm)?;
39 | std::fs::write(&dest, wasm)?;
40 | } else {
41 | std::fs::write(&dest, self.wasm)?;
42 | }
43 |
44 | if self.optimize {
45 | optimize_wasm_file(dest)?;
46 | }
47 |
48 | Ok(())
49 | }
50 | }
51 |
52 | pub(crate) fn optimize_wasm_file(dest: impl AsRef) -> Result<(), Error> {
53 | let output = Command::new("wasm-opt")
54 | .arg("--version")
55 | .stdout(Stdio::null())
56 | .stderr(Stdio::null())
57 | .status();
58 | if output.is_err() {
59 | anyhow::bail!("Failed to detect wasm-opt. Please install binaryen and make sure wasm-opt is on your path: https://github.com/WebAssembly/binaryen");
60 | }
61 | Command::new("wasm-opt")
62 | .arg("--enable-reference-types")
63 | .arg("--enable-bulk-memory")
64 | .arg("--strip")
65 | .arg("-O3")
66 | .arg(dest.as_ref())
67 | .arg("-o")
68 | .arg(dest.as_ref())
69 | .status()?;
70 | Ok(())
71 | }
72 |
--------------------------------------------------------------------------------
/crates/core/src/prelude/src/text-decoder.ts:
--------------------------------------------------------------------------------
1 | declare global {
2 | /**
3 | * @internal
4 | */
5 | function __decodeUtf8BufferToString(
6 | input: ArrayBufferLike,
7 | byteOffset: number,
8 | byteLength: number,
9 | fatal: boolean,
10 | ignoreBOM: boolean
11 | ): string;
12 | }
13 |
14 | class TextDecoder implements globalThis.TextDecoder {
15 | readonly encoding: string;
16 | readonly fatal: boolean;
17 | readonly ignoreBOM: boolean;
18 |
19 | constructor(label: string = "utf-8", options: TextDecoderOptions = {}) {
20 | label = label.trim().toLowerCase();
21 | const acceptedLabels = [
22 | "utf-8",
23 | "utf8",
24 | "unicode-1-1-utf-8",
25 | "unicode11utf8",
26 | "unicode20utf8",
27 | "x-unicode20utf8",
28 | ];
29 | if (!acceptedLabels.includes(label)) {
30 | // Not spec-compliant behaviour
31 | throw new RangeError("The encoding label provided must be utf-8");
32 | }
33 |
34 | this.encoding = "utf-8";
35 | this.fatal = !!options.fatal;
36 | this.ignoreBOM = !!options.ignoreBOM;
37 | }
38 |
39 | decode(
40 | input?: AllowSharedBufferSource,
41 | options: TextDecodeOptions = {}
42 | ): string {
43 | if (input === undefined) {
44 | return "";
45 | }
46 |
47 | if (options.stream) {
48 | throw new Error("Streaming decode is not supported");
49 | }
50 |
51 | // backing buffer would not have byteOffset and may have different byteLength
52 | let byteOffset = 0;
53 | let byteLength = input.byteLength;
54 | if (ArrayBuffer.isView(input)) {
55 | byteOffset = input.byteOffset;
56 | input = input.buffer;
57 | }
58 |
59 | if (!(input instanceof ArrayBuffer)) {
60 | throw new TypeError(
61 | "The provided value is not of type '(ArrayBuffer or ArrayBufferView)'"
62 | );
63 | }
64 |
65 | return __decodeUtf8BufferToString(
66 | input,
67 | byteOffset,
68 | byteLength,
69 | this.fatal,
70 | this.ignoreBOM
71 | );
72 | }
73 | }
74 |
75 | globalThis.TextDecoder = TextDecoder;
76 |
77 | export {};
78 |
--------------------------------------------------------------------------------
/crates/core/src/prelude/src/console.ts:
--------------------------------------------------------------------------------
1 |
2 | declare global {
3 | interface Console {
4 | debug(...data: any[]): void;
5 | error(...data: any[]): void;
6 | info(...data: any[]): void;
7 | log(...data: any[]): void;
8 | warn(...data: any[]): void;
9 | }
10 |
11 | /**
12 | * @internal
13 | */
14 | var __consoleWrite: (level: string, message: string) => void;
15 | }
16 |
17 | function stringifyArg(arg: any): string {
18 | if (arg === null) return 'null';
19 | if (arg === undefined) return 'undefined';
20 |
21 | if (typeof arg === 'symbol') return arg.toString();
22 | if (typeof arg === 'bigint') return `${arg}n`;
23 | if (typeof arg === 'function') return `[Function ${arg.name ? `${arg.name}` : '(anonymous)'}]`;
24 |
25 | if (typeof arg === 'object') {
26 | if (arg instanceof Error) {
27 | return `${arg.name}: ${arg.message}${arg.stack ? '\n' : ''}${arg.stack}`;
28 | }
29 | if (arg instanceof Set) {
30 | return `Set(${arg.size}) { ${Array.from(arg).map(String).join(', ')} }`;
31 | }
32 | if (arg instanceof Map) {
33 | return `Map(${arg.size}) { ${Array.from(arg).map(([k, v]) => `${k} => ${v}`).join(', ')} }`;
34 | }
35 | if (Array.isArray(arg)) {
36 | const items = [];
37 | for (let i = 0; i < arg.length; i++) {
38 | items.push(i in arg ? stringifyArg(arg[i]) : '');
39 | }
40 | return `[ ${items.join(', ')} ]`;
41 | }
42 |
43 | // For regular objects, use JSON.stringify first for clean output
44 | try {
45 | return JSON.stringify(arg);
46 | } catch {
47 | // For objects that can't be JSON stringified (circular refs etc)
48 | // fall back to Object.prototype.toString behavior
49 | return Object.prototype.toString.call(arg);
50 | }
51 | }
52 |
53 | return String(arg);
54 | }
55 |
56 | function createLogFunction(level: string) {
57 | return function (...args: any[]) {
58 | const message = args.map(stringifyArg).join(' ');
59 | __consoleWrite(level, message);
60 | };
61 | }
62 |
63 | const console = {
64 | trace: createLogFunction('trace'),
65 | debug: createLogFunction('debug'),
66 | log: createLogFunction('info'),
67 | info: createLogFunction('info'),
68 | warn: createLogFunction('warn'),
69 | error: createLogFunction('error'),
70 | };
71 |
72 | globalThis.console = console;
73 |
74 | export { };
75 |
--------------------------------------------------------------------------------
/crates/core/src/prelude/src/memory.ts:
--------------------------------------------------------------------------------
1 | declare global {
2 | interface Memory {
3 | fromString(str: string): MemoryHandle;
4 | fromBuffer(bytes: ArrayBufferLike): MemoryHandle;
5 | fromJsonObject(obj: unknown): MemoryHandle;
6 | allocUInt32(i: number): MemoryHandle;
7 | allocUInt64(i: bigint): MemoryHandle;
8 | allocFloat32(i: number): MemoryHandle;
9 | allocFloat64(i: number): MemoryHandle;
10 | find(offset: PTR): MemoryHandle;
11 |
12 | /**
13 | * @internal
14 | */
15 | _fromBuffer(buffer: ArrayBuffer): { offset: PTR; len: I64 };
16 | /**
17 | * @internal
18 | */
19 | _find(offset: PTR): { offset: PTR; len: I64 };
20 | /**
21 | * @internal
22 | */
23 | _free(offset: PTR): void;
24 | /**
25 | * @internal
26 | */
27 | _readBytes(offset: PTR): ArrayBuffer;
28 | }
29 | var Memory: Memory;
30 | }
31 |
32 | const Memory = globalThis.Memory;
33 | Memory.fromString = function(this: Memory, str) {
34 | let bytes = new TextEncoder().encode(str).buffer;
35 | const memData = Memory.fromBuffer(bytes);
36 | return new MemoryHandle(memData.offset, memData.len);
37 | };
38 |
39 | Memory.fromBuffer = function(this: Memory, bytes) {
40 | const memData = Memory._fromBuffer(bytes);
41 | return new MemoryHandle(memData.offset, memData.len);
42 | };
43 |
44 | Memory.fromJsonObject = function(this: Memory, obj) {
45 | const memData = Memory.fromString(JSON.stringify(obj));
46 | return new MemoryHandle(memData.offset, memData.len);
47 | };
48 |
49 | Memory.allocUInt32 = function(this: Memory, i) {
50 | const buffer = new ArrayBuffer(4);
51 | const arr = new Uint32Array(buffer);
52 | arr[0] = i;
53 | return Memory.fromBuffer(buffer);
54 | };
55 |
56 | Memory.allocUInt64 = function(this: Memory, i) {
57 | const buffer = new ArrayBuffer(8);
58 | const arr = new BigUint64Array(buffer);
59 | arr[0] = i;
60 | return Memory.fromBuffer(buffer);
61 | };
62 |
63 | Memory.allocFloat32 = function(this: Memory, i) {
64 | const buffer = new ArrayBuffer(4);
65 | const arr = new Float32Array(buffer);
66 | arr[0] = i;
67 | return Memory.fromBuffer(buffer);
68 | };
69 |
70 | Memory.allocFloat64 = function(this: Memory, i) {
71 | const buffer = new ArrayBuffer(8);
72 | const arr = new Float64Array(buffer);
73 | arr[0] = i;
74 | return Memory.fromBuffer(buffer);
75 | };
76 |
77 | Memory.find = function(offset) {
78 | const memData = Memory._find(offset);
79 | if (memData === undefined) {
80 | return undefined;
81 | }
82 | return new MemoryHandle(memData.offset, memData.len);
83 | };
84 |
85 | export { };
86 |
--------------------------------------------------------------------------------
/install-windows.ps1:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env pwsh
2 |
3 | $TAG= "v1.2.0"
4 | $BINARYEN_TAG= "version_116"
5 | $extismPath="$env:Programfiles\Extism"
6 | $binaryenPath="$env:Programfiles\Binaryen"
7 | $7z= "7z"
8 | if (-not (Get-Command $7z -ErrorAction SilentlyContinue)){
9 | $7z= "$env:Programfiles\7-Zip\7z.exe"
10 | }
11 | $tempPath= "$env:TEMP"
12 |
13 | try {
14 |
15 | $ARCH = (Get-CimInstance Win32_Processor).Architecture
16 | switch ($ARCH) {
17 | 9 { $ARCH = "x86_64" }
18 | 12 { $ARCH = "aarch64" }
19 | default { Write-Host "unknown arch: $ARCH" -ForegroundColor Red; exit 1 }
20 | }
21 | Write-Host "ARCH is $ARCH."
22 |
23 | Write-Host "Downloading extism-js version $TAG."
24 | $TMPGZ = [System.IO.Path]::GetTempFileName()
25 | Invoke-WebRequest -Uri "https://github.com/extism/js-pdk/releases/download/$TAG/extism-js-$ARCH-windows-$TAG.gz" -OutFile "$TMPGZ"
26 |
27 | Write-Host "Installing extism-js."
28 | Remove-Item -Recurse -Path "$extismPath" -Force -ErrorAction SilentlyContinue | Out-Null
29 | New-Item -ItemType Directory -Force -Path $extismPath -ErrorAction Stop | Out-Null
30 | & $7z x "$TMPGZ" -o"$extismPath" >$null 2>&1
31 |
32 | if (-not (Get-Command "wasm-merge" -ErrorAction SilentlyContinue) -or -not (Get-Command "wasm-opt" -ErrorAction SilentlyContinue)) {
33 |
34 | Write-Output "Missing Binaryen tool(s)."
35 | Remove-Item -Recurse -Path "$binaryenPath" -Force -ErrorAction SilentlyContinue | Out-Null
36 | New-Item -ItemType Directory -Force -Path $binaryenPath -ErrorAction Stop | Out-Null
37 |
38 | Write-Output "Downloading Binaryen version $BINARYEN_TAG."
39 | Remove-Item -Recurse -Path "$tempPath\binaryen-*" -Force -ErrorAction SilentlyContinue | Out-Null
40 | Invoke-WebRequest -Uri "https://github.com/WebAssembly/binaryen/releases/download/$BINARYEN_TAG/binaryen-$BINARYEN_TAG-$ARCH-windows.tar.gz" -OutFile "$tempPath\binaryen-$BINARYEN_TAG-$ARCH-windows.tar.gz"
41 |
42 |
43 | Write-Output "Installing Binaryen."
44 | & $7z x "$tempPath\binaryen-$BINARYEN_TAG-$ARCH-windows.tar.gz" -o"$tempPath" >$null 2>&1
45 | & $7z x -ttar "$tempPath\binaryen-$BINARYEN_TAG-$ARCH-windows.tar" -o"$tempPath" >$null 2>&1
46 | Copy-Item "$tempPath\binaryen-$BINARYEN_TAG\bin\wasm-opt.exe" -Destination "$binaryenPath" -ErrorAction Stop | Out-Null
47 | Copy-Item "$tempPath\binaryen-$BINARYEN_TAG\bin\wasm-merge.exe" -Destination "$binaryenPath" -ErrorAction Stop | Out-Null
48 | }
49 |
50 | Write-Output "Install done !"
51 | }catch {
52 | Write-Output "Install Failed: $_.Exception.Message"
53 | exit 1
54 | }
55 |
--------------------------------------------------------------------------------
/examples/host_funcs/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3 | github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a h1:UwSIFv5g5lIvbGgtf3tVwC7Ky9rmMFBp0RMs+6f6YqE=
4 | github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a/go.mod h1:C8DzXehI4zAbrdlbtOByKX6pfivJTBiV9Jjqv56Yd9Q=
5 | github.com/extism/go-sdk v1.6.1 h1:gkbkG5KzYKrv8mLggw5ojg/JulXfEbLIRVhbw9Ot7S0=
6 | github.com/extism/go-sdk v1.6.1/go.mod h1:yRolc4PvIUQ9J/BBB3QZ5EY1MtXAN2jqBGDGR3Sk54M=
7 | github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
8 | github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
9 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
10 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
11 | github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca h1:T54Ema1DU8ngI+aef9ZhAhNGQhcRTrWxVeG07F+c/Rw=
12 | github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
13 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
14 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
15 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
16 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
17 | github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 h1:ZF+QBjOI+tILZjBaFj3HgFonKXUcwgJ4djLb6i42S3Q=
18 | github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834/go.mod h1:m9ymHTgNSEjuxvw8E7WWe4Pl4hZQHXONY8wE6dMLaRk=
19 | github.com/tetratelabs/wazero v1.8.1-0.20240916092830-1353ca24fef0 h1:NCRnJ+X6eZt3awiReoHCcDuC6Wf+CgWk6p4IDkIuxTo=
20 | github.com/tetratelabs/wazero v1.8.1-0.20240916092830-1353ca24fef0/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
21 | go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
22 | go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
23 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
24 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
25 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
26 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
27 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
28 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
29 |
--------------------------------------------------------------------------------
/crates/core/src/prelude/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 | .pnpm-debug.log*
9 |
10 | # Diagnostic reports (https://nodejs.org/api/report.html)
11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 | *.pid.lock
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 | *.lcov
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30 | .grunt
31 |
32 | # Bower dependency directory (https://bower.io/)
33 | bower_components
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules/
43 | jspm_packages/
44 |
45 | # Snowpack dependency directory (https://snowpack.dev/)
46 | web_modules/
47 |
48 | # TypeScript cache
49 | *.tsbuildinfo
50 |
51 | # Optional npm cache directory
52 | .npm
53 |
54 | # Optional eslint cache
55 | .eslintcache
56 |
57 | # Optional stylelint cache
58 | .stylelintcache
59 |
60 | # Microbundle cache
61 | .rpt2_cache/
62 | .rts2_cache_cjs/
63 | .rts2_cache_es/
64 | .rts2_cache_umd/
65 |
66 | # Optional REPL history
67 | .node_repl_history
68 |
69 | # Output of 'npm pack'
70 | *.tgz
71 |
72 | # Yarn Integrity file
73 | .yarn-integrity
74 |
75 | # dotenv environment variable files
76 | .env
77 | .env.development.local
78 | .env.test.local
79 | .env.production.local
80 | .env.local
81 |
82 | # parcel-bundler cache (https://parceljs.org/)
83 | .cache
84 | .parcel-cache
85 |
86 | # Next.js build output
87 | .next
88 | out
89 |
90 | # Nuxt.js build / generate output
91 | .nuxt
92 | dist
93 |
94 | # Gatsby files
95 | .cache/
96 | # Comment in the public line in if your project uses Gatsby and not Next.js
97 | # https://nextjs.org/blog/next-9-1#public-directory-support
98 | # public
99 |
100 | # vuepress build output
101 | .vuepress/dist
102 |
103 | # vuepress v2.x temp and cache directory
104 | .temp
105 | .cache
106 |
107 | # Docusaurus cache and generated files
108 | .docusaurus
109 |
110 | # Serverless directories
111 | .serverless/
112 |
113 | # FuseBox cache
114 | .fusebox/
115 |
116 | # DynamoDB Local files
117 | .dynamodb/
118 |
119 | # TernJS port file
120 | .tern-port
121 |
122 | # Stores VSCode versions used for testing VSCode extensions
123 | .vscode-test
124 |
125 | # yarn v2
126 | .yarn/cache
127 | .yarn/unplugged
128 | .yarn/build-state.yml
129 | .yarn/install-state.gz
130 | .pnp.*
131 |
132 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | test:
7 | runs-on: ${{ matrix.os }}
8 | strategy:
9 | matrix:
10 | include:
11 | - name: linux
12 | os: ubuntu-24.04
13 | # Re-enable once we can build on Windows again
14 | # - name: windows
15 | # os: windows-latest
16 | steps:
17 | - uses: actions/checkout@v2
18 | - uses: actions-rs/toolchain@v1
19 | with:
20 | profile: default
21 | toolchain: stable
22 | target: wasm32-wasip1
23 | default: true
24 |
25 | - name: Setup Go
26 | uses: actions/setup-go@v4
27 | with:
28 | go-version: '1.x'
29 |
30 | - name: Setup Python
31 | uses: actions/setup-python@v5
32 | with:
33 | python-version: '3.12'
34 |
35 | - name: Install Extism CLI
36 | run: |
37 | go install github.com/extism/cli/extism@v1.6.0
38 | extism --version
39 |
40 | - name: Update deps (Linux)
41 | run: |
42 | ./install-wasi-sdk.sh
43 | cd /tmp
44 | # get just wasm-merge and wasm-opt
45 | curl -L https://github.com/WebAssembly/binaryen/releases/download/version_116/binaryen-version_116-x86_64-linux.tar.gz > binaryen.tar.gz
46 | tar xvzf binaryen.tar.gz
47 | sudo cp binaryen-version_116/bin/wasm-merge /usr/local/bin
48 | sudo cp binaryen-version_116/bin/wasm-opt /usr/local/bin
49 | if: runner.os != 'Windows'
50 |
51 | - name: Update deps (Windows)
52 | run: |
53 | powershell -executionpolicy bypass -File .\install-wasi-sdk.ps1
54 | go install github.com/extism/cli/extism@latest
55 | Remove-Item -Recurse -Path "c:\Program files\Binaryen" -Force -ErrorAction SilentlyContinue > $null 2>&1
56 | New-Item -ItemType Directory -Force -Path "c:\Program files\Binaryen" -ErrorAction Stop > $null 2>&1
57 | Invoke-WebRequest -Uri "https://github.com/WebAssembly/binaryen/releases/download/version_116/binaryen-version_116-x86_64-windows.tar.gz" -OutFile "$env:TMP\binaryen-version_116-x86_64-windows.tar.gz"
58 | 7z x "$env:TMP\binaryen-version_116-x86_64-windows.tar.gz" -o"$env:TMP\" >$null 2>&1
59 | 7z x -ttar "$env:TMP\binaryen-version_116-x86_64-windows.tar" -o"$env:TMP\" >$null 2>&1
60 | Copy-Item -Path "$env:TMP\binaryen-version_116\bin\wasm-opt.exe" -Destination "c:\Program files\Binaryen" -ErrorAction Stop > $null 2>&1
61 | Copy-Item -Path "$env:TMP\binaryen-version_116\bin\wasm-merge.exe" -Destination "c:\Program files\Binaryen" -ErrorAction Stop > $null 2>&1
62 | if: runner.os == 'Windows'
63 |
64 | - name: Run Tests (Linux)
65 | env:
66 | QUICKJS_WASM_SYS_WASI_SDK_PATH: "${{ github.workspace }}/wasi-sdk"
67 | run: |
68 | make
69 | make test
70 | if: runner.os != 'Windows'
71 |
72 | - name: Run Tests (Windows)
73 | env:
74 | QUICKJS_WASM_SYS_WASI_SDK_PATH: "${{ github.workspace }}/wasi-sdk"
75 | run: |
76 | set PATH="c:\Program files\Binaryen\";%PATH%
77 | make
78 | make test
79 | shell: cmd
80 | if: runner.os == 'Windows'
81 |
--------------------------------------------------------------------------------
/examples/react/src/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * This example shows how to use react and jsx in a plug-in.
3 | * This is a slightly more complex example to demonstrate a common
4 | * pattern for these types of UI plug-ins.
5 | *
6 | * It's helpful to model the plug-in as a state machine. You only need two exports:
7 | *
8 | * + 1. `setState` takes an `Action` which steps or mutates the state machine
9 | * + 2. `render` renders the current state
10 | *
11 | * You can store all the application's state in a single global variable.
12 | * Make sure it can only be mutated through incoming `Action` messages.
13 | * Use JSX to build up your application view and render with `renderToString`.
14 | *
15 | * With this programming model you can build pretty complex plug-ins, like a
16 | * working chat application.
17 | *
18 | * The one downside of course is the in-memory state. However, you could just
19 | * simply offer your plug-in developers a host function to mutate (or persist)
20 | * and fetch it.
21 | *
22 | * This can be tested out with the `shell` command in the extism CLI
23 | *
24 | * $ make compile-examples
25 | * $ extism shell
26 | *
27 | * > extism call examples/react.wasm render --wasi
28 | *
29 | * > extism call examples/react.wasm setState --input='{"action": "SET_SETTING", "payload": { "backgroundColor": "tomato" }}' --wasi
30 | *
31 | * > extism call examples/react.wasm render --wasi
32 | *
33 | */
34 | import { renderToString } from 'react-dom/server';
35 | import React from 'react'
36 |
37 | interface Settings {
38 | backgroundColor: string;
39 | }
40 |
41 | // We can store all our application's state here
42 | interface AppState {
43 | settings: Settings;
44 | }
45 |
46 | // We provide a number of "actions" that can be passed to setState to mutate the state machine
47 | enum ActionType {
48 | SetSetting = "SET_SETTING"
49 | }
50 |
51 | interface Action {
52 | action: ActionType;
53 | }
54 |
55 | // We just have one action that can set properties in `settings`
56 | interface SetSettingAction extends Action {
57 | action: ActionType.SetSetting;
58 | payload: Settings;
59 | }
60 |
61 | // Let's just make our application state a global. This should
62 | // be okay as people can only mutate this through setState.
63 | const APP_STATE = { settings: {} }
64 |
65 | // Send an action to this export to step the state machine
66 | // For convenience, this returns the newly rendered view but it could
67 | // return a list of errors or other useful things
68 | export function setState() {
69 | const action: Action = JSON.parse(Host.inputString())
70 | switch (action.action) {
71 | case ActionType.SetSetting:
72 | const setSettingAction = action as SetSettingAction;
73 | Object.assign(APP_STATE.settings, setSettingAction.payload)
74 | break;
75 | default:
76 | throw new Error(`Uknown action ${action}`)
77 | }
78 |
79 | Host.outputString(renderApp())
80 | }
81 |
82 |
83 | // Simply render the whole app
84 | // Note: we could accept props here
85 | export function render() {
86 | Host.outputString(renderApp())
87 | }
88 |
89 | function renderApp() {
90 | const view = (
91 |
94 | )
95 |
96 | // use react-dom to render the app component
97 | return renderToString(view)
98 | }
99 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: cli core fmt clean
2 | .DEFAULT_GOAL := cli
3 |
4 | download-wasi-sdk:
5 | ifeq ($(OS),Windows_NT)
6 | powershell -executionpolicy bypass -File .\install-wasi-sdk.ps1
7 | else
8 | sh install-wasi-sdk.sh
9 | endif
10 |
11 | install:
12 | cargo install --path crates/cli
13 |
14 | cli: core
15 | cd crates/cli && cargo build --release && cd -
16 |
17 | core:
18 | cd crates/core \
19 | && cd src/prelude \
20 | && npm install \
21 | && npm run build \
22 | && npx -y -p typescript tsc src/index.ts --lib es2020 --declaration --emitDeclarationOnly --outDir dist \
23 | && cd ../.. \
24 | && cargo build --release --target=wasm32-wasip1 \
25 | && wasm-opt --enable-reference-types --enable-bulk-memory --strip -O3 ../../target/wasm32-wasip1/release/js_pdk_core.wasm -o ../../target/wasm32-wasip1/release/js_pdk_core.wasm \
26 | && cd -
27 |
28 | fmt: fmt-core fmt-cli
29 |
30 | fmt-core:
31 | cd crates/core/ \
32 | && cargo fmt -- --check \
33 | && cargo clippy --target=wasm32-wasip1 -- -D warnings \
34 | && cd -
35 |
36 | fmt-cli:
37 | cd crates/cli/ \
38 | && cargo fmt -- --check \
39 | && cargo clippy -- -D warnings \
40 | && cd -
41 |
42 | clean: clean-wasi-sdk clean-cargo
43 |
44 | clean-cargo:
45 | cargo clean
46 |
47 | clean-wasi-sdk:
48 | rm -r wasi-sdk 2> /dev/null || true
49 |
50 | test: compile-examples
51 | @extism call examples/simple_js.wasm greet --wasi --input="Benjamin"
52 | @extism call examples/bundled.wasm greet --wasi --input="Benjamin" --allow-host "example.com"
53 | cd ./examples/host_funcs && go run . ../host_funcs.wasm
54 | @extism call examples/react.wasm render --wasi
55 | @extism call examples/react.wasm setState --input='{"action": "SET_SETTING", "payload": { "backgroundColor": "tomato" }}' --wasi
56 | @error_msg=$$(extism call examples/exception.wasm greet --wasi --input="Benjamin" 2>&1); \
57 | if echo "$$error_msg" | grep -q "shibboleth"; then \
58 | echo "Test passed - found expected error"; \
59 | else \
60 | echo "Test failed - did not find expected error message"; \
61 | echo "Got: $$error_msg"; \
62 | exit 1; \
63 | fi
64 | @extism call examples/console.wasm greet --wasi --input="Benjamin" --log-level=debug
65 | @extism call examples/base64.wasm greet --wasi --input="Benjamin" --log-level=debug
66 | @error_msg=$$(extism call examples/try-catch.wasm greet --wasi --input="Benjamin" --log-level debug 2>&1); \
67 | if echo "$$error_msg" | grep -q "got error"; then \
68 | echo "Test passed - found expected error"; \
69 | else \
70 | echo "Test failed - did not find expected error message"; \
71 | echo "Got: $$error_msg"; \
72 | exit 1; \
73 | fi
74 |
75 | compile-examples: cli
76 | cd examples/react && npm install && npm run build && cd ../..
77 | ./target/release/extism-js examples/simple_js/script.js -i examples/simple_js/script.d.ts -o examples/simple_js.wasm
78 | cd examples/bundled && npm install && npm run build && cd ../..
79 | ./target/release/extism-js examples/host_funcs/script.js -i examples/host_funcs/script.d.ts -o examples/host_funcs.wasm
80 | ./target/release/extism-js examples/exports/script.js -i examples/exports/script.d.ts -o examples/exports.wasm
81 | ./target/release/extism-js examples/exception/script.js -i examples/exception/script.d.ts -o examples/exception.wasm
82 | ./target/release/extism-js examples/console/script.js -i examples/console/script.d.ts -o examples/console.wasm
83 | ./target/release/extism-js examples/base64/script.js -i examples/base64/script.d.ts -o examples/base64.wasm
84 | ./target/release/extism-js examples/try-catch/script.js -i examples/try-catch/script.d.ts -o examples/try-catch.wasm
85 |
86 | kitchen:
87 | cd examples/kitchen-sink && npm install && npm run build && cd ../..
88 | ./target/release/extism-js examples/kitchen-sink/dist/index.js -i examples/kitchen-sink/src/index.d.ts -o examples/kitchen-sink.wasm
89 | @extism call examples/kitchen-sink.wasm greet --input "Steve" --wasi --allow-host "*" --config "last_name=Manuel"
90 |
--------------------------------------------------------------------------------
/examples/host_funcs/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "math"
7 | "os"
8 | "strings"
9 |
10 | extism "github.com/extism/go-sdk"
11 | )
12 |
13 | // Capitalize a string (Host function for `capitalize`)
14 | func capitalize(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
15 | input, err := p.ReadString(stack[0])
16 | if err != nil {
17 | panic(err)
18 | }
19 |
20 | capitalized := strings.Title(input)
21 | s, err := p.WriteString(capitalized)
22 | if err != nil {
23 | panic(err)
24 | }
25 |
26 | stack[0] = s
27 | }
28 |
29 | // Handle floating point inputs (Host function for `floatInputs`)
30 | func floatInputs(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
31 | f64 := extism.DecodeF64(stack[0])
32 | f32 := float32(extism.DecodeF32(stack[1]))
33 |
34 | fmt.Println("Go Host: floatInputs received:", f64, f32)
35 |
36 | // Return a fixed integer (expected behavior based on JavaScript code)
37 | stack[0] = uint64(2_147_483_647) // Max int32 value
38 | }
39 |
40 | // Handle integer input, return a floating point (Host function for `floatOutput`)
41 | func floatOutput(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
42 | i32 := int32(stack[0])
43 |
44 | fmt.Println("Go Host: floatOutput received:", i32)
45 |
46 | // Return the expected float value
47 | result := 9_007_199_254_740.125
48 | stack[0] = extism.EncodeF64(result)
49 | }
50 |
51 | // Handle multiple arguments but return nothing (Host function for `voidInputs`)
52 | func voidInputs(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
53 | fmt.Printf("Go Host: voidInputs stack: %v, %v, %v, %v, %v\n", stack[0], stack[1], stack[2], stack[3], stack[4])
54 |
55 | i32 := int32(stack[0])
56 | i64 := int64(stack[1])
57 | f32 := float32(extism.DecodeF32(stack[2]))
58 | f64 := extism.DecodeF64(stack[3])
59 | extra := int32(stack[4])
60 |
61 | fmt.Printf("Go Host: voidInputs received: i32=%d, i64=%d, f32=%f, f64=%f, extra=%d\n", i32, i64, f32, f64, extra)
62 |
63 | if i32 != 2_147_483_647 {
64 | panic("Unexpected i32 value: " + fmt.Sprint(i32))
65 | }
66 |
67 | if i64 != 9_223_372_036_854_775_807 {
68 | panic("Unexpected i64 value: " + fmt.Sprint(i64))
69 | }
70 |
71 | if math.Abs(float64(f32-314_567.5)) > 0.0001 {
72 | panic("Unexpected f32 value: " + fmt.Sprint(f32))
73 | }
74 |
75 | if math.Abs(f64-9_007_199_254_740.125) > 0.0001 {
76 | panic("Unexpected f64 value: " + fmt.Sprint(f64))
77 | }
78 | }
79 |
80 | func main() {
81 | if len(os.Args) < 2 {
82 | fmt.Println("Usage: go run main.go ")
83 | os.Exit(1)
84 | }
85 |
86 | wasmFile := os.Args[1]
87 | data, err := os.ReadFile(wasmFile)
88 | if err != nil {
89 | fmt.Printf("Failed to read wasm file: %v\n", err)
90 | os.Exit(1)
91 | }
92 |
93 | manifest := extism.Manifest{
94 | Wasm: []extism.Wasm{extism.WasmData{Data: data}},
95 | }
96 |
97 | extism.SetLogLevel(extism.LogLevelDebug)
98 |
99 | ctx := context.Background()
100 | config := extism.PluginConfig{EnableWasi: true}
101 | plugin, err := extism.NewPlugin(ctx, manifest, config, []extism.HostFunction{
102 | extism.NewHostFunctionWithStack("capitalize", capitalize, []extism.ValueType{extism.ValueTypePTR}, []extism.ValueType{extism.ValueTypePTR}),
103 | extism.NewHostFunctionWithStack("floatInputs", floatInputs, []extism.ValueType{extism.ValueTypeF64, extism.ValueTypeF32}, []extism.ValueType{extism.ValueTypeI32}),
104 | extism.NewHostFunctionWithStack("floatOutput", floatOutput, []extism.ValueType{extism.ValueTypeI32}, []extism.ValueType{extism.ValueTypeF64}),
105 | extism.NewHostFunctionWithStack("voidInputs", voidInputs, []extism.ValueType{extism.ValueTypeI32, extism.ValueTypeI64, extism.ValueTypeF32, extism.ValueTypeF64, extism.ValueTypeI32}, []extism.ValueType{}),
106 | })
107 |
108 | if err != nil {
109 | fmt.Printf("Failed to initialize plugin: %v\n", err)
110 | os.Exit(1)
111 | }
112 |
113 | exit, result, err := plugin.Call("greet", []byte("Benjamin"))
114 | if err != nil {
115 | fmt.Printf("Plugin call failed: %v\n", err)
116 | os.Exit(int(exit))
117 | }
118 |
119 | fmt.Println(string(result))
120 | }
121 |
--------------------------------------------------------------------------------
/crates/cli/src/main.rs:
--------------------------------------------------------------------------------
1 | mod opt;
2 | mod options;
3 | mod shims;
4 | mod ts_parser;
5 |
6 | use crate::options::Options;
7 | use crate::ts_parser::parse_interface_file;
8 | use anyhow::{bail, Result};
9 | use log::LevelFilter;
10 | use shims::generate_wasm_shims;
11 | use std::env;
12 | use std::path::PathBuf;
13 | use std::process::Stdio;
14 | use std::{fs, io::Write, process::Command};
15 | use structopt::StructOpt;
16 | use tempfile::TempDir;
17 |
18 | const CORE: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/engine.wasm"));
19 |
20 | fn main() -> Result<()> {
21 | let mut builder = env_logger::Builder::new();
22 | builder
23 | .filter(None, LevelFilter::Info)
24 | .target(env_logger::Target::Stdout)
25 | .init();
26 |
27 | let opts = Options::from_args();
28 |
29 | if opts.core {
30 | opt::Optimizer::new(CORE)
31 | .wizen(true)
32 | .write_optimized_wasm(opts.output)?;
33 | return Ok(());
34 | }
35 |
36 | // We need to parse the interface.d.ts file
37 | let interface_path = PathBuf::from(&opts.interface_file);
38 | if !interface_path.exists() {
39 | bail!(
40 | "Could not find interface file {}. Set to a valid d.ts file with the -i flag",
41 | &interface_path.to_str().unwrap()
42 | );
43 | }
44 | let plugin_interface = parse_interface_file(&interface_path)?;
45 |
46 | // Copy in the user's js code from the configured file
47 | let mut user_code = fs::read(&opts.input_js)?;
48 |
49 | // If we have imports, we need to inject some state needed for host function support
50 | let mut contents = Vec::new();
51 | let mut names = Vec::new();
52 | let mut sorted_names = Vec::new();
53 | for ns in &plugin_interface.imports {
54 | sorted_names.extend(ns.functions.iter().map(|s| (&s.name, s.results.len())));
55 | }
56 | sorted_names.sort_by_key(|x| x.0.as_str());
57 |
58 | for (name, results) in sorted_names {
59 | names.push(format!("{{ name: '{}', results: {} }}", &name, results));
60 | }
61 |
62 | contents
63 | .extend_from_slice(format!("Host.__hostFunctions = [{}];\n", names.join(", ")).as_bytes());
64 | contents.append(&mut user_code);
65 |
66 | // Create a tmp dir to hold all the library objects
67 | // This can go away once we do all the wasm-merge stuff in process
68 | let tmp_dir = TempDir::new()?;
69 | let core_path = tmp_dir.path().join("core.wasm");
70 | let shim_path = tmp_dir.path().join("shim.wasm");
71 |
72 | // First wizen the core module
73 | let self_cmd = env::args().next().expect("Expected a command argument");
74 | {
75 | let mut command = Command::new(self_cmd)
76 | .arg("-c")
77 | .arg(&opts.input_js)
78 | .arg("-o")
79 | .arg(&core_path)
80 | .stdin(Stdio::piped())
81 | .spawn()?;
82 | command
83 | .stdin
84 | .take()
85 | .expect("Expected to get writeable stdin")
86 | .write_all(&contents)?;
87 | let status = command.wait()?;
88 | if !status.success() {
89 | bail!("Couldn't create wasm from input");
90 | }
91 | }
92 |
93 | // Create our shim file given our parsed TS module object
94 | generate_wasm_shims(
95 | &shim_path,
96 | &plugin_interface.exports,
97 | &plugin_interface.imports,
98 | )?;
99 |
100 | let output = Command::new("wasm-merge")
101 | .arg("--version")
102 | .stdout(Stdio::null())
103 | .stderr(Stdio::null())
104 | .status();
105 | if output.is_err() {
106 | bail!("Failed to detect wasm-merge. Please install binaryen and make sure wasm-merge is on your path: https://github.com/WebAssembly/binaryen");
107 | }
108 |
109 | // Merge the shim with the core module
110 | let status = Command::new("wasm-merge")
111 | .arg(&core_path)
112 | .arg("core")
113 | .arg(&shim_path)
114 | .arg("shim")
115 | .arg("-o")
116 | .arg(&opts.output)
117 | .arg("--enable-reference-types")
118 | .arg("--enable-bulk-memory")
119 | .status()?;
120 | if !status.success() {
121 | bail!("wasm-merge failed. Couldn't merge shim");
122 | }
123 |
124 | if !opts.skip_opt {
125 | opt::optimize_wasm_file(opts.output)?;
126 | }
127 |
128 | Ok(())
129 | }
130 |
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -eou pipefail
3 |
4 | # Check if a specific tag was provided
5 | if [ $# -eq 1 ]; then
6 | LATEST_TAG="$1"
7 | echo "Using specified tag: $LATEST_TAG"
8 | else
9 | # Get the latest release
10 | RELEASE_API_URL="https://api.github.com/repos/extism/js-pdk/releases/latest"
11 | response=$(curl -s "$RELEASE_API_URL")
12 | if [ -z "$response" ]; then
13 | echo "Error: Failed to fetch the latest release from GitHub API."
14 | exit 1
15 | fi
16 |
17 | # try to parse tag
18 | LATEST_TAG=$(echo "$response" | grep -m 1 '"tag_name":' | sed -E 's/.*"tag_name": *"([^"]+)".*/\1/')
19 |
20 | if [ -z "$LATEST_TAG" ]; then
21 | echo "Error: Could not find the latest release tag."
22 | exit 1
23 | fi
24 |
25 | echo "Installing extism-js latest release with tag: $LATEST_TAG"
26 | fi
27 |
28 | OS=''
29 | case `uname` in
30 | Darwin*) OS="macos" ;;
31 | Linux*) OS="linux" ;;
32 | *) echo "unknown os: $OSTYPE" && exit 1 ;;
33 | esac
34 |
35 | ARCH=`uname -m`
36 | case "$ARCH" in
37 | ix86*|x86_64*) ARCH="x86_64" ;;
38 | arm64*|aarch64*) ARCH="aarch64" ;;
39 | *) echo "unknown arch: $ARCH" && exit 1 ;;
40 | esac
41 |
42 | BINARYEN_TAG="version_116"
43 | DOWNLOAD_URL="https://github.com/extism/js-pdk/releases/download/$LATEST_TAG/extism-js-$ARCH-$OS-$LATEST_TAG.gz"
44 |
45 | # Function to check if a directory is in PATH and writable
46 | is_valid_install_dir() {
47 | [[ ":$PATH:" == *":$1:"* ]] && [ -w "$1" ]
48 | }
49 |
50 | INSTALL_DIR=""
51 | USE_SUDO=""
52 |
53 | # Check for common user-writable directories in PATH
54 | for dir in "$HOME/bin" "$HOME/.local/bin" "$HOME/.bin"; do
55 | if is_valid_install_dir "$dir"; then
56 | INSTALL_DIR="$dir"
57 | break
58 | fi
59 | done
60 |
61 | # If no user-writable directory found, use system directory
62 | if [ -z "$INSTALL_DIR" ]; then
63 | INSTALL_DIR="/usr/local/bin"
64 | USE_SUDO=1
65 | fi
66 |
67 | echo "Checking for binaryen..."
68 |
69 | if ! which "wasm-merge" > /dev/null || ! which "wasm-opt" > /dev/null; then
70 | echo "Missing binaryen tool(s)"
71 |
72 | # binaryen use arm64 instead where as extism-js uses aarch64 for release file naming
73 | case "$ARCH" in
74 | aarch64*) ARCH="arm64" ;;
75 | esac
76 |
77 | # matches the case where the user installs extism-pdk in a Linux-based Docker image running on mac m1
78 | # binaryen didn't have arm64 release file for linux
79 | if [ $ARCH = "arm64" ] && [ $OS = "linux" ]; then
80 | ARCH="x86_64"
81 | fi
82 |
83 | if [ $OS = "macos" ]; then
84 | echo "Installing binaryen and wasm-merge using homebrew"
85 | brew install binaryen
86 | else
87 | if [ ! -e "binaryen-$BINARYEN_TAG-$ARCH-$OS.tar.gz" ]; then
88 | echo 'Downloading binaryen...'
89 | curl -L -O "https://github.com/WebAssembly/binaryen/releases/download/$BINARYEN_TAG/binaryen-$BINARYEN_TAG-$ARCH-$OS.tar.gz"
90 | fi
91 | rm -rf 'binaryen' "binaryen-$BINARYEN_TAG"
92 | tar xf "binaryen-$BINARYEN_TAG-$ARCH-$OS.tar.gz"
93 | mv "binaryen-$BINARYEN_TAG"/ binaryen/
94 | sudo mkdir -p /usr/local/binaryen
95 | if ! which 'wasm-merge' > /dev/null; then
96 | echo "Installing wasm-merge..."
97 | rm -f /usr/local/binaryen/wasm-merge
98 | sudo mv binaryen/bin/wasm-merge /usr/local/binaryen/wasm-merge
99 | sudo ln -s /usr/local/binaryen/wasm-merge /usr/local/bin/wasm-merge
100 | else
101 | echo "wasm-merge is already installed"
102 | fi
103 | if ! which 'wasm-opt' > /dev/null; then
104 | echo "Installing wasm-opt..."
105 | rm -f /usr/local/bin/wasm-opt
106 | sudo mv binaryen/bin/wasm-opt /usr/local/binaryen/wasm-opt
107 | sudo ln -s /usr/local/binaryen/wasm-opt /usr/local/bin/wasm-opt
108 | else
109 | echo "wasm-opt is already installed"
110 | fi
111 | fi
112 | else
113 | echo "binaryen tools are already installed"
114 | fi
115 |
116 | TARGET="$INSTALL_DIR/extism-js"
117 | echo "Downloading extism-js from: $DOWNLOAD_URL"
118 |
119 | if curl -fsSL --output /tmp/extism-js.gz "$DOWNLOAD_URL"; then
120 | gunzip /tmp/extism-js.gz
121 |
122 | if [ "$USE_SUDO" = "1" ]; then
123 | echo "No user-writable bin directory found in PATH. Using sudo to install in $INSTALL_DIR"
124 | sudo mv /tmp/extism-js "$TARGET"
125 | else
126 | mv /tmp/extism-js "$TARGET"
127 | fi
128 | chmod +x "$TARGET"
129 |
130 | echo "Successfully installed extism-js to $TARGET"
131 | else
132 | echo "Failed to download or install extism-js. Curl exit code: $?"
133 | exit
134 | fi
135 |
136 | # Warn the user if the chosen path is not in the path
137 | if [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then
138 | echo "Note: $INSTALL_DIR is not in your PATH. You may need to add it to your PATH or use the full path to run extism-js."
139 | fi
140 |
141 | echo "Installation complete. Try to run 'extism-js --version' to ensure it was correctly installed."
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on:
4 | workflow_dispatch:
5 | release:
6 | types:
7 | - published
8 |
9 | jobs:
10 | compile_core:
11 | name: Compile Core
12 | runs-on: ubuntu-22.04
13 | steps:
14 | - uses: actions/checkout@v1
15 |
16 | - name: Install Rust
17 | uses: actions-rs/toolchain@v1
18 | with:
19 | profile: default
20 | toolchain: stable
21 | target: wasm32-wasip1
22 | default: true
23 |
24 | - name: Get wasm-opt
25 | run: |
26 | curl -L https://github.com/WebAssembly/binaryen/releases/download/version_116/binaryen-version_116-x86_64-linux.tar.gz > binaryen.tar.gz
27 | tar xvzf binaryen.tar.gz
28 | sudo cp binaryen-version_116/bin/wasm-opt /usr/local/bin
29 | sudo chmod +x /usr/local/bin/wasm-opt
30 |
31 | - name: Install wasi-sdk
32 | run: make download-wasi-sdk
33 |
34 | - name: Make core
35 | run: make core
36 |
37 | - name: Opt core
38 | run: wasm-opt --enable-reference-types --enable-bulk-memory --strip -O3 target/wasm32-wasip1/release/js_pdk_core.wasm -o target/wasm32-wasip1/release/js_pdk_core.wasm
39 |
40 | - name: Upload core binary to artifacts
41 | uses: actions/upload-artifact@v4
42 | with:
43 | name: engine
44 | path: target/wasm32-wasip1/release/js_pdk_core.wasm
45 |
46 | compile_cli:
47 | name: Compile CLI
48 | needs: compile_core
49 | runs-on: ${{ matrix.os }}
50 | strategy:
51 | matrix:
52 | include:
53 | - name: linux
54 | os: ubuntu-latest
55 | path: target/x86_64-unknown-linux-gnu/release/extism-js
56 | asset_name: extism-js-x86_64-linux-${{ github.event.release.tag_name }}
57 | shasum_cmd: sha256sum
58 | target: x86_64-unknown-linux-gnu
59 | - name: linux-arm64
60 | os: ubuntu-latest
61 | path: target/aarch64-unknown-linux-gnu/release/extism-js
62 | asset_name: extism-js-aarch64-linux-${{ github.event.release.tag_name }}
63 | shasum_cmd: sha256sum
64 | target: aarch64-unknown-linux-gnu
65 | - name: macos
66 | os: macos-latest
67 | path: target/x86_64-apple-darwin/release/extism-js
68 | asset_name: extism-js-x86_64-macos-${{ github.event.release.tag_name }}
69 | shasum_cmd: shasum -a 256
70 | target: x86_64-apple-darwin
71 | - name: macos-arm64
72 | os: macos-latest
73 | path: target/aarch64-apple-darwin/release/extism-js
74 | asset_name: extism-js-aarch64-macos-${{ github.event.release.tag_name }}
75 | shasum_cmd: shasum -a 256
76 | target: aarch64-apple-darwin
77 | - name: windows
78 | os: windows-latest
79 | path: target\x86_64-pc-windows-msvc\release\extism-js.exe
80 | asset_name: extism-js-x86_64-windows-${{ github.event.release.tag_name }}
81 | target: x86_64-pc-windows-msvc
82 | - name: windows-arm64
83 | os: windows-latest
84 | path: target\aarch64-pc-windows-msvc\release\extism-js.exe
85 | asset_name: extism-js-aarch64-windows-${{ github.event.release.tag_name }}
86 | target: aarch64-pc-windows-msvc
87 |
88 | steps:
89 | - uses: actions/checkout@v1
90 |
91 | - uses: actions/download-artifact@v4
92 | with:
93 | name: engine
94 | path: crates/cli/
95 |
96 | - name: ls
97 | run: ls -R
98 | working-directory: crates/cli/
99 |
100 | - name: Install Rust
101 | uses: actions-rs/toolchain@v1
102 | with:
103 | profile: default
104 | toolchain: stable
105 | target: ${{ matrix.target }}
106 | default: true
107 |
108 | - name: Install gnu gcc
109 | run: |
110 | sudo apt-get update
111 | sudo apt-get install g++-aarch64-linux-gnu
112 | sudo apt-get install gcc-aarch64-linux-gnu
113 | if: matrix.os == 'ubuntu-latest'
114 |
115 | - name: Build CLI ${{ matrix.os }}
116 | env:
117 | EXTISM_ENGINE_PATH: js_pdk_core.wasm
118 | run: cargo build --release --target ${{ matrix.target }} --package js-pdk-cli
119 |
120 | - name: Archive assets
121 | run: gzip -k -f ${{ matrix.path }} && mv ${{ matrix.path }}.gz ${{ matrix.asset_name }}.gz
122 |
123 | - name: Upload assets to artifacts
124 | uses: actions/upload-artifact@v4
125 | with:
126 | name: ${{ matrix.asset_name }}.gz
127 | path: ${{ matrix.asset_name }}.gz
128 |
129 | - name: Upload assets to release
130 | uses: actions/upload-release-asset@v1
131 | env:
132 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
133 | with:
134 | upload_url: ${{ github.event.release.upload_url }}
135 | asset_path: ./${{ matrix.asset_name }}.gz
136 | asset_name: ${{ matrix.asset_name }}.gz
137 | asset_content_type: application/gzip
138 |
139 | - name: Generate asset hash (Linux/MacOS)
140 | run: ${{ matrix.shasum_cmd }} ${{ matrix.asset_name }}.gz | awk '{ print $1 }' > ${{ matrix.asset_name }}.gz.sha256
141 | if: runner.os != 'Windows'
142 |
143 | - name: Generate asset hash (Windows)
144 | run: Get-FileHash -Path ${{ matrix.asset_name }}.gz -Algorithm SHA256 | Select-Object -ExpandProperty Hash > ${{ matrix.asset_name }}.gz.sha256
145 | shell: pwsh
146 | if: runner.os == 'Windows'
147 |
148 | - name: Upload asset hash to artifacts
149 | uses: actions/upload-artifact@v4
150 | with:
151 | name: ${{ matrix.asset_name }}.gz.sha256
152 | path: ${{ matrix.asset_name }}.gz.sha256
153 |
154 | - name: Upload asset hash to release
155 | uses: actions/upload-release-asset@v1
156 | env:
157 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
158 | with:
159 | upload_url: ${{ github.event.release.upload_url }}
160 | asset_path: ./${{ matrix.asset_name }}.gz.sha256
161 | asset_name: ${{ matrix.asset_name }}.gz.sha256
162 | asset_content_type: plain/text
163 |
--------------------------------------------------------------------------------
/crates/core/src/lib.rs:
--------------------------------------------------------------------------------
1 | use rquickjs::{
2 | function::Args, object::ObjectKeysIter, Context, Ctx, Function, Object, Runtime, Undefined,
3 | Value,
4 | };
5 | use std::io;
6 | use std::io::Read;
7 |
8 | mod globals;
9 |
10 | struct Cx(Context);
11 |
12 | unsafe impl Send for Cx {}
13 | unsafe impl Sync for Cx {}
14 |
15 | static CONTEXT: std::sync::OnceLock = std::sync::OnceLock::new();
16 | static CALL_ARGS: std::sync::Mutex>> = std::sync::Mutex::new(vec![]);
17 |
18 | fn check_exception(this: &Ctx, err: rquickjs::Error) -> anyhow::Error {
19 | let s = match err {
20 | rquickjs::Error::Exception => {
21 | let err = this.catch().into_exception().unwrap();
22 | let msg = err.message().unwrap_or_default();
23 | format!("Exception: {}\n{}", msg, err.stack().unwrap_or_default())
24 | }
25 | err => err.to_string(),
26 | };
27 |
28 | let mem = extism_pdk::Memory::from_bytes(&s).unwrap();
29 | unsafe {
30 | extism_pdk::extism::error_set(mem.offset());
31 | }
32 | anyhow::Error::msg(s)
33 | }
34 |
35 | #[export_name = "wizer.initialize"]
36 | extern "C" fn init() {
37 | let runtime = Runtime::new().expect("Couldn't make a runtime");
38 | let context = Context::full(&runtime).expect("Couldnt make a context");
39 | globals::inject_globals(&context).expect("Failed to initialize globals");
40 |
41 | let mut code = String::new();
42 | io::stdin().read_to_string(&mut code).unwrap();
43 |
44 | context
45 | .with(|this| -> Result {
46 | match this.eval(code) {
47 | Ok(()) => (),
48 | Err(err) => return Err(check_exception(&this, err)),
49 | }
50 |
51 | Ok(Undefined)
52 | })
53 | .unwrap();
54 | let _ = CONTEXT.set(Cx(context));
55 | }
56 |
57 | fn js_context() -> Context {
58 | if CONTEXT.get().is_none() {
59 | init()
60 | }
61 | let context = CONTEXT.get().unwrap();
62 | context.0.clone()
63 | }
64 |
65 | fn invoke<'a, T, F: for<'b> Fn(Ctx<'b>, Value<'b>) -> T>(
66 | idx: i32,
67 | conv: F,
68 | ) -> Result {
69 | let call_args = CALL_ARGS.lock().unwrap().pop();
70 | let context = js_context();
71 | context.with(|ctx| {
72 | let call_args = call_args.unwrap();
73 | let args: Args = call_args.iter().fold(
74 | Args::new(ctx.clone(), call_args.len()),
75 | |mut args, rust_arg| {
76 | match rust_arg {
77 | ArgType::I32(v) => args
78 | .push_arg(v)
79 | .expect("Should be able to convert i32 to JS arg"),
80 | ArgType::I64(v) => args
81 | .push_arg(rquickjs::BigInt::from_i64(ctx.clone(), *v))
82 | .expect("Should be able to convert i64 to JS arg"),
83 | ArgType::F32(v) => args
84 | .push_arg(v)
85 | .expect("Should be able to convert f32 to JS arg"),
86 | ArgType::F64(v) => args
87 | .push_arg(v)
88 | .expect("Should be able to convert f64 to JS arg"),
89 | }
90 | args
91 | },
92 | );
93 | let global = ctx.globals();
94 |
95 | let module: Object = global.get("module")?;
96 | let exports: Object = module.get("exports")?;
97 |
98 | let export_names = export_names(exports.clone()).unwrap();
99 |
100 | let function: Function = exports.get(export_names[idx as usize].as_str()).unwrap();
101 |
102 | let function_invocation_result = function.call_arg(args);
103 |
104 | while ctx.execute_pending_job() {
105 | continue
106 | }
107 |
108 | match function_invocation_result {
109 | Ok(r) => {
110 | let res = conv(ctx.clone(), r);
111 | Ok(res)
112 | }
113 | Err(err) => Err(check_exception(&ctx, err)),
114 | }
115 | })
116 | }
117 |
118 | #[no_mangle]
119 | pub extern "C" fn __arg_start() {
120 | CALL_ARGS.lock().unwrap().push(vec![]);
121 | }
122 |
123 | #[no_mangle]
124 | pub extern "C" fn __arg_i32(arg: i32) {
125 | CALL_ARGS
126 | .lock()
127 | .unwrap()
128 | .last_mut()
129 | .unwrap()
130 | .push(ArgType::I32(arg));
131 | }
132 |
133 | #[no_mangle]
134 | pub extern "C" fn __arg_i64(arg: i64) {
135 | CALL_ARGS
136 | .lock()
137 | .unwrap()
138 | .last_mut()
139 | .unwrap()
140 | .push(ArgType::I64(arg));
141 | }
142 |
143 | #[no_mangle]
144 | pub extern "C" fn __arg_f32(arg: f32) {
145 | CALL_ARGS
146 | .lock()
147 | .unwrap()
148 | .last_mut()
149 | .unwrap()
150 | .push(ArgType::F32(arg));
151 | }
152 |
153 | #[no_mangle]
154 | pub extern "C" fn __arg_f64(arg: f64) {
155 | CALL_ARGS
156 | .lock()
157 | .unwrap()
158 | .last_mut()
159 | .unwrap()
160 | .push(ArgType::F64(arg));
161 | }
162 |
163 | #[no_mangle]
164 | pub extern "C" fn __invoke_i32(idx: i32) -> i32 {
165 | invoke(idx, |_ctx, r| r.as_number().unwrap_or_default() as i32).unwrap_or(-1)
166 | }
167 |
168 | #[no_mangle]
169 | pub extern "C" fn __invoke_i64(idx: i32) -> i64 {
170 | invoke(idx, |_ctx, r| {
171 | if let Some(number) = r.as_big_int() {
172 | return number.clone().to_i64().unwrap_or_default();
173 | } else if let Some(number) = r.as_number() {
174 | return number as i64;
175 | }
176 | 0
177 | })
178 | .unwrap_or(-1)
179 | }
180 |
181 | #[no_mangle]
182 | pub extern "C" fn __invoke_f64(idx: i32) -> f64 {
183 | invoke(idx, |_ctx, r| r.as_float().unwrap_or_default()).unwrap_or(-1.0)
184 | }
185 |
186 | #[no_mangle]
187 | pub extern "C" fn __invoke_f32(idx: i32) -> f32 {
188 | invoke(idx, |_ctx, r| r.as_number().unwrap_or_default() as f32).unwrap_or(-1.0)
189 | }
190 |
191 | #[no_mangle]
192 | pub extern "C" fn __invoke(idx: i32) {
193 | invoke(idx, |_ctx, _r| ()).unwrap()
194 | }
195 |
196 | fn export_names(exports: Object) -> anyhow::Result> {
197 | let mut keys_iter: ObjectKeysIter = exports.keys();
198 | let mut key = keys_iter.next();
199 | let mut keys: Vec = vec![];
200 | while key.is_some() {
201 | keys.push(key.unwrap()?.to_string());
202 | key = keys_iter.next();
203 | }
204 | keys.sort();
205 | Ok(keys)
206 | }
207 |
208 | enum ArgType {
209 | I32(i32),
210 | I64(i64),
211 | F32(f32),
212 | F64(f64),
213 | }
214 |
--------------------------------------------------------------------------------
/crates/core/src/prelude/types/polyfills.d.ts:
--------------------------------------------------------------------------------
1 | /*! *****************************************************************************
2 | Copyright (c) Microsoft Corporation. All rights reserved.
3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use
4 | this file except in compliance with the License. You may obtain a copy of the
5 | License at http://www.apache.org/licenses/LICENSE-2.0
6 |
7 | THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
8 | KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
9 | WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
10 | MERCHANTABLITY OR NON-INFRINGEMENT.
11 |
12 | See the Apache Version 2.0 License for specific language governing permissions
13 | and limitations under the License.
14 | ***************************************************************************** */
15 |
16 | ///
17 |
18 | /**
19 | * The URL interface represents an object providing static methods used for creating object URLs.
20 | *
21 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL)
22 | */
23 | interface URL {
24 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hash) */
25 | hash: string;
26 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/host) */
27 | host: string;
28 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hostname) */
29 | hostname: string;
30 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/href) */
31 | href: string;
32 | toString(): string;
33 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/origin) */
34 | readonly origin: string;
35 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/password) */
36 | password: string;
37 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/pathname) */
38 | pathname: string;
39 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/port) */
40 | port: string;
41 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/protocol) */
42 | protocol: string;
43 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/search) */
44 | search: string;
45 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/searchParams) */
46 | readonly searchParams: URLSearchParams;
47 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/username) */
48 | username: string;
49 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/toJSON) */
50 | toJSON(): string;
51 | }
52 |
53 | declare var URL: {
54 | prototype: URL;
55 | new (url: string | URL, base?: string | URL): URL;
56 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/canParse_static) */
57 | canParse(url: string | URL, base?: string): boolean;
58 | };
59 |
60 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams) */
61 | interface URLSearchParams {
62 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/size) */
63 | readonly size: number;
64 | /**
65 | * Appends a specified key/value pair as a new search parameter.
66 | *
67 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/append)
68 | */
69 | append(name: string, value: string): void;
70 | /**
71 | * Deletes the given search parameter, and its associated value, from the list of all search parameters.
72 | *
73 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/delete)
74 | */
75 | delete(name: string, value?: string): void;
76 | /**
77 | * Returns the first value associated to the given search parameter.
78 | *
79 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/get)
80 | */
81 | get(name: string): string | null;
82 | /**
83 | * Returns all the values association with a given search parameter.
84 | *
85 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/getAll)
86 | */
87 | getAll(name: string): string[];
88 | /**
89 | * Returns a Boolean indicating if such a search parameter exists.
90 | *
91 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/has)
92 | */
93 | has(name: string, value?: string): boolean;
94 | /**
95 | * Sets the value associated to a given search parameter to the given value. If there were several values, delete the others.
96 | *
97 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/set)
98 | */
99 | set(name: string, value: string): void;
100 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/sort) */
101 | sort(): void;
102 | /** Returns a string containing a query string suitable for use in a URL. Does not include the question mark. */
103 | toString(): string;
104 | forEach(
105 | callbackfn: (value: string, key: string, parent: URLSearchParams) => void,
106 | thisArg?: any
107 | ): void;
108 | }
109 |
110 | declare var URLSearchParams: {
111 | prototype: URLSearchParams;
112 | new (
113 | init?: string[][] | Record | string | URLSearchParams
114 | ): URLSearchParams;
115 | };
116 |
117 | interface TextDecodeOptions {
118 | stream?: boolean;
119 | }
120 |
121 | interface TextDecoderOptions {
122 | fatal?: boolean;
123 | ignoreBOM?: boolean;
124 | }
125 |
126 | interface TextEncoderEncodeIntoResult {
127 | read: number;
128 | written: number;
129 | }
130 |
131 | type AllowSharedBufferSource = ArrayBuffer | ArrayBufferView;
132 |
133 | /**
134 | * A decoder for a specific method, that is a specific character encoding, like utf-8, iso-8859-2, koi8, cp1261, gbk, etc. A decoder takes a stream of bytes as input and emits a stream of code points. For a more scalable, non-native library, see StringView – a C-like representation of strings based on typed arrays.
135 | *
136 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder)
137 | */
138 | interface TextDecoder extends TextDecoderCommon {
139 | /**
140 | * Returns the result of running encoding's decoder. The method can be invoked zero or more times with options's stream set to true, and then once without options's stream (or set to false), to process a fragmented input. If the invocation without options's stream (or set to false) has no input, it's clearest to omit both arguments.
141 | *
142 | * ```
143 | * var string = "", decoder = new TextDecoder(encoding), buffer;
144 | * while(buffer = next_chunk()) {
145 | * string += decoder.decode(buffer, {stream:true});
146 | * }
147 | * string += decoder.decode(); // end-of-queue
148 | * ```
149 | *
150 | * If the error mode is "fatal" and encoding's decoder returns error, throws a TypeError.
151 | *
152 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder/decode)
153 | */
154 | decode(input?: AllowSharedBufferSource, options?: TextDecodeOptions): string;
155 | }
156 |
157 | declare var TextDecoder: {
158 | prototype: TextDecoder;
159 | new (label?: string, options?: TextDecoderOptions): TextDecoder;
160 | };
161 |
162 | interface TextDecoderCommon {
163 | /**
164 | * Returns encoding's name, lowercased.
165 | *
166 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder/encoding)
167 | */
168 | readonly encoding: string;
169 | /**
170 | * Returns true if error mode is "fatal", otherwise false.
171 | *
172 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder/fatal)
173 | */
174 | readonly fatal: boolean;
175 | /**
176 | * Returns the value of ignore BOM.
177 | *
178 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder/ignoreBOM)
179 | */
180 | readonly ignoreBOM: boolean;
181 | }
182 |
183 | /**
184 | * TextEncoder takes a stream of code points as input and emits a stream of bytes. For a more scalable, non-native library, see StringView – a C-like representation of strings based on typed arrays.
185 | *
186 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder)
187 | */
188 | interface TextEncoder extends TextEncoderCommon {
189 | /**
190 | * Returns the result of running UTF-8's encoder.
191 | *
192 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder/encode)
193 | */
194 | encode(input?: string): Uint8Array;
195 | /**
196 | * Runs the UTF-8 encoder on source, stores the result of that operation into destination, and returns the progress made as an object wherein read is the number of converted code units of source and written is the number of bytes modified in destination.
197 | *
198 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder/encodeInto)
199 | */
200 | encodeInto(
201 | source: string,
202 | destination: Uint8Array
203 | ): TextEncoderEncodeIntoResult;
204 | }
205 |
206 | declare var TextEncoder: {
207 | prototype: TextEncoder;
208 | new (): TextEncoder;
209 | };
210 |
211 | interface TextEncoderCommon {
212 | /**
213 | * Returns "utf-8".
214 | *
215 | * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder/encoding)
216 | */
217 | readonly encoding: string;
218 | }
219 |
220 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/console) */
221 | interface Console {
222 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/debug_static) */
223 | debug(...data: any[]): void;
224 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/error_static) */
225 | error(...data: any[]): void;
226 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/info_static) */
227 | info(...data: any[]): void;
228 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static) */
229 | log(...data: any[]): void;
230 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/warn_static) */
231 | warn(...data: any[]): void;
232 | }
233 |
234 | declare var console: Console;
235 |
--------------------------------------------------------------------------------
/crates/cli/src/ts_parser.rs:
--------------------------------------------------------------------------------
1 | extern crate swc_common;
2 | extern crate swc_ecma_parser;
3 | use std::path::Path;
4 |
5 | use anyhow::{bail, Context, Result};
6 | use wagen::ValType;
7 |
8 | use swc_common::sync::Lrc;
9 | use swc_common::SourceMap;
10 | use swc_ecma_ast::{
11 | Decl, Module, ModuleDecl, Stmt, TsInterfaceDecl, TsKeywordTypeKind, TsModuleDecl, TsType,
12 | };
13 | use swc_ecma_ast::{ModuleItem, TsTypeElement};
14 | use swc_ecma_parser::{lexer::Lexer, Parser, StringInput, Syntax};
15 |
16 | #[derive(Debug, Clone)]
17 | pub struct Param {
18 | #[allow(unused)]
19 | pub name: String,
20 | pub ptype: ValType,
21 | }
22 |
23 | impl Param {
24 | pub fn new(name: &str, ptype: ValType) -> Param {
25 | Param {
26 | name: name.to_string(),
27 | ptype,
28 | }
29 | }
30 | }
31 |
32 | #[derive(Debug, Clone)]
33 | pub struct Signature {
34 | pub name: String,
35 | pub params: Vec ,
36 | pub results: Vec ,
37 | }
38 |
39 | #[derive(Debug, Clone)]
40 | pub struct Interface {
41 | pub name: String,
42 | pub functions: Vec,
43 | }
44 |
45 | #[derive(Debug, Clone)]
46 | pub struct PluginInterface {
47 | pub exports: Interface,
48 | pub imports: Vec,
49 | }
50 |
51 | pub fn val_type(s: &str) -> Result {
52 | match s.to_ascii_lowercase().as_str() {
53 | "i32" => Ok(ValType::I32),
54 | "i64" | "ptr" => Ok(ValType::I64),
55 | "f32" => Ok(ValType::F32),
56 | "f64" => Ok(ValType::F64),
57 | _ => anyhow::bail!("Unsupported type: {}", s), // Extism handle
58 | }
59 | }
60 |
61 | pub fn param_type(params: &mut Vec , vn: &str, t: &TsType) -> Result<()> {
62 | let typ = if let Some(t) = t.as_ts_type_ref() {
63 | t.type_name
64 | .as_ident()
65 | .context("Illegal param type")?
66 | .sym
67 | .as_str()
68 | } else {
69 | anyhow::bail!("Unsupported param type: {:?}", t);
70 | };
71 | params.push(Param::new(vn, val_type(typ)?));
72 | Ok(())
73 | }
74 |
75 | pub fn result_type(results: &mut Vec , return_type: &TsType) -> Result<()> {
76 | let return_type = if let Some(return_type) = return_type.as_ts_type_ref() {
77 | Some(
78 | return_type
79 | .type_name
80 | .as_ident()
81 | .context("Illegal return type")?
82 | .sym
83 | .as_str(),
84 | )
85 | } else if let Some(t) = return_type.as_ts_keyword_type() {
86 | match t.kind {
87 | TsKeywordTypeKind::TsVoidKeyword => None,
88 | _ => anyhow::bail!("Unsupported return type: {:?}", t.kind),
89 | }
90 | } else {
91 | anyhow::bail!("Unsupported return type: {:?}", return_type)
92 | };
93 | if let Some(r) = return_type {
94 | results.push(Param::new("result", val_type(r)?));
95 | }
96 | Ok(())
97 | }
98 |
99 | /// Parses the non-main parts of the module which maps to the wasm imports
100 | fn parse_user_interface(i: &TsInterfaceDecl) -> Result {
101 | let mut signatures = Vec::new();
102 | let name = i.id.sym.as_str();
103 | for sig in &i.body.body {
104 | match sig {
105 | TsTypeElement::TsMethodSignature(t) => {
106 | let name = t.key.as_ident().unwrap().sym.to_string();
107 | let mut params = vec![];
108 | let mut results = vec![];
109 |
110 | for p in t.params.iter() {
111 | let vn = p.as_ident().unwrap().id.sym.as_str();
112 | let typ = p.as_ident().unwrap().type_ann.clone();
113 | let t = typ.unwrap().type_ann;
114 | param_type(&mut params, vn, &t)?;
115 | }
116 | if params.len() > 5 {
117 | anyhow::bail!("Host functions only support up to 5 arguments");
118 | }
119 | if let Some(return_type) = &t.type_ann {
120 | result_type(&mut results, &return_type.type_ann)?;
121 | }
122 | let signature = Signature {
123 | name,
124 | params,
125 | results,
126 | };
127 | signatures.push(signature);
128 | }
129 | _ => {
130 | log::warn!("Warning: don't know what to do with sig {:#?}", sig);
131 | }
132 | }
133 | }
134 |
135 | Ok(Interface {
136 | name: name.into(),
137 | functions: signatures,
138 | })
139 | }
140 |
141 | /// Try to parse the imports
142 | fn parse_imports(tsmod: &TsModuleDecl) -> Result> {
143 | if let Some(block) = &tsmod.body {
144 | if let Some(block) = block.clone().ts_module_block() {
145 | for inter in block.body {
146 | if let ModuleItem::Stmt(Stmt::Decl(decl)) = inter {
147 | let i = decl.as_ts_interface().unwrap();
148 | let mut interface = parse_user_interface(i)?;
149 | if tsmod.id.clone().str().is_some() {
150 | interface.name = tsmod.id.clone().expect_str().value.as_str().to_string()
151 | + "/"
152 | + i.id.sym.as_str();
153 | }
154 | return Ok(Some(interface));
155 | } else {
156 | log::warn!("Not a module decl");
157 | }
158 | }
159 | } else {
160 | log::warn!("Not a Module Block");
161 | }
162 | }
163 | Ok(None)
164 | }
165 |
166 | /// Parses the main module declaration (the extism exports)
167 | fn parse_module_decl(tsmod: &TsModuleDecl) -> Result {
168 | let mut signatures = Vec::new();
169 |
170 | if let Some(block) = &tsmod.body {
171 | if let Some(block) = block.as_ts_module_block() {
172 | for decl in &block.body {
173 | if let ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(e)) = decl {
174 | if let Some(fndecl) = e.decl.as_fn_decl() {
175 | let name = fndecl.ident.sym.as_str().to_string();
176 | let mut params = vec![];
177 | let mut results = vec![];
178 | if let Some(return_type) = fndecl.function.clone().return_type.clone() {
179 | result_type(&mut results, &return_type.type_ann)?;
180 | }
181 |
182 | for param in fndecl.function.params.iter() {
183 | let name = param.pat.clone().expect_ident().id.sym.as_str().to_string();
184 | let p = param.pat.clone().expect_ident();
185 | match p.type_ann {
186 | None => params.push(Param::new(&name, val_type("i64")?)),
187 | Some(ann) => {
188 | param_type(&mut params, &name, &ann.type_ann)?;
189 | }
190 | }
191 | }
192 | let signature = Signature {
193 | name,
194 | params,
195 | results,
196 | };
197 |
198 | signatures.push(signature);
199 | }
200 | } else {
201 | bail!("Don't know what to do with non export on main module");
202 | }
203 | }
204 | }
205 | }
206 |
207 | Ok(Interface {
208 | name: "main".to_string(),
209 | functions: signatures,
210 | })
211 | }
212 |
213 | /// Parse the whole TS module type file
214 | fn parse_module(module: Module) -> Result> {
215 | let mut interfaces = Vec::new();
216 | for statement in &module.body {
217 | if let ModuleItem::Stmt(Stmt::Decl(Decl::TsModule(submod))) = statement {
218 | let name = submod.id.as_str().map(|name| name.value.as_str());
219 |
220 | match name {
221 | Some("main") | None => {
222 | interfaces.push(parse_module_decl(submod)?);
223 | }
224 | Some(_) => {
225 | if let Some(imports) = parse_imports(submod)? {
226 | interfaces.push(imports);
227 | }
228 | }
229 | };
230 | }
231 | }
232 |
233 | Ok(interfaces)
234 | }
235 |
236 | /// Parse the d.ts file representing the plugin interface
237 | pub fn parse_interface_file(interface_path: impl AsRef) -> Result {
238 | let cm: Lrc = Default::default();
239 | let fm = cm.load_file(interface_path.as_ref())?;
240 | let lexer = Lexer::new(
241 | Syntax::Typescript(Default::default()),
242 | Default::default(),
243 | StringInput::from(&*fm),
244 | None,
245 | );
246 |
247 | let mut parser = Parser::new_from(lexer);
248 | let parse_errs = parser.take_errors();
249 | if !parse_errs.is_empty() {
250 | for e in parse_errs {
251 | log::error!("{:#?}", e);
252 | }
253 | bail!("Failed to parse TypeScript interface file. It is not valid TypeScript.");
254 | }
255 |
256 | let module = parser.parse_module().expect("failed to parser module");
257 | let interfaces = parse_module(module)?;
258 | let mut exports = interfaces
259 | .iter()
260 | .find(|i| i.name == "main")
261 | .context("You need to declare a 'main' module")?
262 | .to_owned();
263 |
264 | exports.functions.sort_by_key(|x| x.name.to_string());
265 |
266 | let imports = interfaces
267 | .into_iter()
268 | .filter(|i| i.name != "main")
269 | .collect();
270 |
271 | Ok(PluginInterface { exports, imports })
272 | }
273 |
--------------------------------------------------------------------------------
/crates/cli/src/shims.rs:
--------------------------------------------------------------------------------
1 | use crate::ts_parser::Interface;
2 | use anyhow::Result;
3 | use std::path::Path;
4 | use wagen::{BlockType, Instr, ValType};
5 |
6 | #[derive(PartialEq)]
7 | enum TypeCode {
8 | Void = 0,
9 | I32 = 1,
10 | I64 = 2,
11 | F32 = 3,
12 | F64 = 4,
13 | }
14 |
15 | pub fn generate_wasm_shims(
16 | path: impl AsRef,
17 | exports: &Interface,
18 | imports: &[Interface],
19 | ) -> Result<()> {
20 | let mut module = wagen::Module::new();
21 |
22 | let __arg_start = module.import("core", "__arg_start", None, [], []);
23 | let __arg_i32 = module.import("core", "__arg_i32", None, [ValType::I32], []);
24 | let __arg_i64 = module.import("core", "__arg_i64", None, [ValType::I64], []);
25 | let __arg_f32 = module.import("core", "__arg_f32", None, [ValType::F32], []);
26 | let __arg_f64 = module.import("core", "__arg_f64", None, [ValType::F64], []);
27 | let __invoke_i32 = module.import("core", "__invoke_i32", None, [ValType::I32], [ValType::I32]);
28 | let __invoke_i64 = module.import("core", "__invoke_i64", None, [ValType::I32], [ValType::I64]);
29 | let __invoke_f32 = module.import("core", "__invoke_f32", None, [ValType::I32], [ValType::F32]);
30 | let __invoke_f64 = module.import("core", "__invoke_f64", None, [ValType::I32], [ValType::F64]);
31 | let __invoke = module.import("core", "__invoke", None, [ValType::I32], []);
32 |
33 | let mut import_elements = Vec::new();
34 | let mut import_items = vec![];
35 | for import in imports.iter() {
36 | for f in import.functions.iter() {
37 | let params: Vec<_> = f.params.iter().map(|x| x.ptype).collect();
38 | let results: Vec<_> = f.results.iter().map(|x| x.ptype).collect();
39 | let index = module.import(&import.name, &f.name, None, params.clone(), results.clone());
40 | import_items.push((f.name.clone(), index, params, results));
41 | }
42 | }
43 | import_items.sort_by_key(|x| x.0.to_string());
44 |
45 | for (_name, index, _params, _results) in &import_items {
46 | import_elements.push(index.index());
47 | }
48 |
49 | let table_min = import_elements.len() as u32;
50 |
51 | let import_table = module.tables().push(wagen::TableType {
52 | element_type: wagen::RefType::FUNCREF,
53 | minimum: table_min,
54 | maximum: None,
55 | });
56 |
57 | let mut get_function_return_type_builder = wagen::Builder::default();
58 |
59 | for (func_idx, (_name, _index, _params, results)) in import_items.iter().enumerate() {
60 | let type_code = results.first().map_or(TypeCode::Void, |val_type| match val_type {
61 | ValType::I32 => TypeCode::I32,
62 | ValType::I64 => TypeCode::I64,
63 | ValType::F32 => TypeCode::F32,
64 | ValType::F64 => TypeCode::F64,
65 | _ => TypeCode::Void,
66 | });
67 |
68 | if type_code == TypeCode::Void {
69 | continue;
70 | }
71 |
72 | // Compare the input function index with the current index.
73 | get_function_return_type_builder.push(Instr::LocalGet(0)); // load requested function index
74 | get_function_return_type_builder.push(Instr::I32Const(func_idx as i32)); // load func_idx
75 | get_function_return_type_builder.push(Instr::I32Eq); // compare
76 | get_function_return_type_builder.push(Instr::If(BlockType::Empty)); // if true
77 | get_function_return_type_builder.push(Instr::I32Const(type_code as i32)); // load type code
78 | get_function_return_type_builder.push(Instr::Return); // early return if match
79 | get_function_return_type_builder.push(Instr::End);
80 | }
81 |
82 | get_function_return_type_builder.push(Instr::I32Const(0)); // Default to 0
83 | get_function_return_type_builder.push(Instr::Return);
84 |
85 | let get_function_return_type_func = module.func(
86 | "__get_function_return_type",
87 | vec![ValType::I32], // takes function index
88 | vec![ValType::I32], // returns type code
89 | vec![],
90 | );
91 | get_function_return_type_func.export("__get_function_return_type");
92 | get_function_return_type_func.body = get_function_return_type_builder;
93 |
94 | let mut get_function_arg_type_builder = wagen::Builder::default();
95 |
96 | for (func_idx, (_name, _index, params, _results)) in import_items.iter().enumerate() {
97 | for arg_idx in 0..params.len() {
98 | let type_code = match params[arg_idx] {
99 | ValType::I32 => TypeCode::I32,
100 | ValType::I64 => TypeCode::I64,
101 | ValType::F32 => TypeCode::F32,
102 | ValType::F64 => TypeCode::F64,
103 | _ => panic!("Unsupported argument type for function {} at index {}", func_idx, arg_idx),
104 | };
105 |
106 | // Compare both function index and argument index
107 | get_function_arg_type_builder.push(Instr::LocalGet(0)); // function index
108 | get_function_arg_type_builder.push(Instr::I32Const(func_idx as i32));
109 | get_function_arg_type_builder.push(Instr::I32Eq);
110 |
111 | get_function_arg_type_builder.push(Instr::LocalGet(1)); // argument index
112 | get_function_arg_type_builder.push(Instr::I32Const(arg_idx as i32));
113 | get_function_arg_type_builder.push(Instr::I32Eq);
114 |
115 | get_function_arg_type_builder.push(Instr::I32And); // Both must match
116 |
117 | // If both match, return the type code
118 | get_function_arg_type_builder.push(Instr::If(BlockType::Empty));
119 | get_function_arg_type_builder.push(Instr::I32Const(type_code as i32));
120 | get_function_arg_type_builder.push(Instr::Return);
121 | get_function_arg_type_builder.push(Instr::End);
122 | }
123 | }
124 |
125 | // Default return if no match
126 | get_function_arg_type_builder.push(Instr::I32Const(0));
127 | get_function_arg_type_builder.push(Instr::Return);
128 |
129 | let get_function_arg_type_func = module.func(
130 | "__get_function_arg_type",
131 | vec![ValType::I32, ValType::I32], // takes (function_index, arg_index)
132 | vec![ValType::I32], // returns type code
133 | vec![],
134 | );
135 | get_function_arg_type_func.export("__get_function_arg_type");
136 | get_function_arg_type_func.body = get_function_arg_type_builder;
137 |
138 | // Create converters for each host function to reinterpret the I64 bit pattern as the expected type
139 | let mut converter_indices = Vec::new();
140 | for (_, (name, _index, params, results)) in import_items.iter().enumerate() {
141 | let import_type = module
142 | .types()
143 | .push(|t| t.function(params.clone(), results.clone()));
144 |
145 | let mut builder = wagen::Builder::default();
146 |
147 | // Convert input parameters
148 | for (i, param) in params.iter().enumerate() {
149 | builder.push(Instr::LocalGet((i + 1) as u32)); // skip function index param
150 |
151 | match param {
152 | ValType::I32 => {
153 | builder.push(Instr::I32WrapI64);
154 | }
155 | ValType::I64 => {
156 | // No conversion needed - already i64
157 | }
158 | ValType::F32 => {
159 | // Input is already the bit pattern from globals.rs convert_to_u64_bits
160 | // First truncate to i32 then reinterpret as f32
161 | builder.push(Instr::I32WrapI64);
162 | builder.push(Instr::F32ReinterpretI32);
163 | }
164 | ValType::F64 => {
165 | // Input is already the bit pattern from JS DataView
166 | // Just reinterpret the i64 as f64
167 | builder.push(Instr::F64ReinterpretI64);
168 | }
169 | r => {
170 | anyhow::bail!("Unsupported param type: {:?}", r);
171 | }
172 | }
173 | }
174 |
175 | // Call the imported function
176 | builder.push(Instr::LocalGet(0));
177 | builder.push(Instr::CallIndirect {
178 | ty: import_type,
179 | table: import_table,
180 | });
181 |
182 | // Convert result back to i64 bits for JS
183 | if let Some(result) = results.first() {
184 | match result {
185 | ValType::I32 => {
186 | builder.push(Instr::I64ExtendI32U);
187 | }
188 | ValType::I64 => {
189 | // Already i64, no conversion needed
190 | }
191 | ValType::F32 => {
192 | // Convert f32 to its bit pattern
193 | builder.push(Instr::I32ReinterpretF32);
194 | builder.push(Instr::I64ExtendI32U);
195 | }
196 | ValType::F64 => {
197 | // Convert f64 to its bit pattern
198 | builder.push(Instr::I64ReinterpretF64);
199 | }
200 | r => {
201 | anyhow::bail!("Unsupported result type: {:?}", r);
202 | }
203 | }
204 | } else {
205 | // No return value, push 0
206 | builder.push(Instr::I64Const(0));
207 | }
208 |
209 | // Create the converter function
210 | let mut shim_params = vec![ValType::I32]; // Function index
211 | shim_params.extend(std::iter::repeat(ValType::I64).take(params.len()));
212 |
213 | let conv_func = module.func(
214 | &format!("__conv_{}", name),
215 | shim_params,
216 | vec![ValType::I64],
217 | vec![],
218 | );
219 | conv_func.export(&format!("__conv_{}", name));
220 | conv_func.body = builder;
221 |
222 | converter_indices.push(conv_func.index);
223 | }
224 |
225 | let router = module.func(
226 | "__invokeHostFunc",
227 | vec![
228 | ValType::I32, // func_idx
229 | ValType::I64, // args[0]
230 | ValType::I64, // args[1]
231 | ValType::I64, // args[2]
232 | ValType::I64, // args[3]
233 | ValType::I64, // args[4]
234 | ],
235 | vec![ValType::I64],
236 | vec![],
237 | );
238 |
239 | // Similar builder logic as before but simplified to one function
240 | let mut router_builder = wagen::Builder::default();
241 |
242 | for (func_idx, (_name, _index, params, _results)) in import_items.iter().enumerate() {
243 | router_builder.push(Instr::LocalGet(0)); // func index
244 | router_builder.push(Instr::I32Const(func_idx as i32));
245 | router_builder.push(Instr::I32Eq);
246 | router_builder.push(Instr::If(BlockType::Empty));
247 |
248 | // First push func_idx for converter
249 | router_builder.push(Instr::LocalGet(0));
250 |
251 | // Then push remaining args from router's inputs
252 | for (i, _) in params.iter().enumerate() {
253 | router_builder.push(Instr::LocalGet((i + 1) as u32));
254 | }
255 |
256 | router_builder.push(Instr::Call(converter_indices[func_idx]));
257 | router_builder.push(Instr::Return);
258 | router_builder.push(Instr::End);
259 | }
260 |
261 | router_builder.push(Instr::I64Const(0));
262 | router_builder.push(Instr::Return);
263 |
264 | router.export("__invokeHostFunc");
265 | router.body = router_builder;
266 |
267 | // Set up the table
268 | module.active_element(
269 | Some(import_table),
270 | wagen::Elements::Functions(&import_elements),
271 | );
272 |
273 | // Generate exports
274 | for (idx, export) in exports.functions.iter().enumerate() {
275 | let params: Vec<_> = export.params.iter().map(|x| x.ptype).collect();
276 | let results: Vec<_> = export.results.iter().map(|x| x.ptype).collect();
277 | if results.len() > 1 {
278 | anyhow::bail!(
279 | "Multiple return arguments are not currently supported but used in exported function {}",
280 | export.name
281 | );
282 | }
283 |
284 | let mut builder = wagen::Builder::default();
285 | builder.push(Instr::Call(__arg_start.index()));
286 |
287 | for (parami, param) in params.iter().enumerate() {
288 | builder.push(Instr::LocalGet(parami as u32));
289 |
290 | match param {
291 | ValType::I32 => {
292 | builder.push(Instr::Call(__arg_i32.index()));
293 | }
294 | ValType::I64 => {
295 | builder.push(Instr::Call(__arg_i64.index()));
296 | }
297 | ValType::F32 => {
298 | builder.push(Instr::Call(__arg_f32.index()));
299 | }
300 | ValType::F64 => {
301 | builder.push(Instr::Call(__arg_f64.index()));
302 | }
303 | r => {
304 | anyhow::bail!("Unsupported param type: {:?}", r);
305 | }
306 | }
307 | }
308 |
309 | builder.push(Instr::I32Const(idx as i32));
310 | match results.first() {
311 | None => {
312 | builder.push(Instr::Call(__invoke.index()));
313 | }
314 | Some(ValType::I32) => {
315 | builder.push(Instr::Call(__invoke_i32.index()));
316 | }
317 | Some(ValType::I64) => {
318 | builder.push(Instr::Call(__invoke_i64.index()));
319 | }
320 | Some(ValType::F32) => {
321 | builder.push(Instr::Call(__invoke_f32.index()));
322 | }
323 | Some(ValType::F64) => {
324 | builder.push(Instr::Call(__invoke_f64.index()));
325 | }
326 | Some(r) => {
327 | anyhow::bail!("Unsupported result type: {:?}", r);
328 | }
329 | }
330 |
331 | let f = module.func(&export.name, params, results, vec![]);
332 | f.export(&export.name);
333 | f.body = builder;
334 | }
335 |
336 | // Validation with debug output
337 | if let Err(error) = module.clone().validate_save(path.as_ref()) {
338 | eprintln!("Validation failed: {:?}", error);
339 | module.save("/tmp/wizer/incomplete_shim.wasm")?;
340 | return Err(error);
341 | }
342 |
343 | Ok(())
344 | }
345 |
--------------------------------------------------------------------------------
/crates/core/src/prelude/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@extism/js-pdk",
3 | "version": "1.1.1",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "@extism/js-pdk",
9 | "version": "1.1.1",
10 | "license": "BSD-Clause-3",
11 | "dependencies": {
12 | "urlpattern-polyfill": "^8.0.2"
13 | },
14 | "devDependencies": {
15 | "core-js": "^3.30.2",
16 | "esbuild": "^0.17.19",
17 | "typescript": "^5.4.5"
18 | }
19 | },
20 | "node_modules/@esbuild/android-arm": {
21 | "version": "0.17.19",
22 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz",
23 | "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==",
24 | "cpu": [
25 | "arm"
26 | ],
27 | "dev": true,
28 | "optional": true,
29 | "os": [
30 | "android"
31 | ],
32 | "engines": {
33 | "node": ">=12"
34 | }
35 | },
36 | "node_modules/@esbuild/android-arm64": {
37 | "version": "0.17.19",
38 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz",
39 | "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==",
40 | "cpu": [
41 | "arm64"
42 | ],
43 | "dev": true,
44 | "optional": true,
45 | "os": [
46 | "android"
47 | ],
48 | "engines": {
49 | "node": ">=12"
50 | }
51 | },
52 | "node_modules/@esbuild/android-x64": {
53 | "version": "0.17.19",
54 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz",
55 | "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==",
56 | "cpu": [
57 | "x64"
58 | ],
59 | "dev": true,
60 | "optional": true,
61 | "os": [
62 | "android"
63 | ],
64 | "engines": {
65 | "node": ">=12"
66 | }
67 | },
68 | "node_modules/@esbuild/darwin-arm64": {
69 | "version": "0.17.19",
70 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz",
71 | "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==",
72 | "cpu": [
73 | "arm64"
74 | ],
75 | "dev": true,
76 | "optional": true,
77 | "os": [
78 | "darwin"
79 | ],
80 | "engines": {
81 | "node": ">=12"
82 | }
83 | },
84 | "node_modules/@esbuild/darwin-x64": {
85 | "version": "0.17.19",
86 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz",
87 | "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==",
88 | "cpu": [
89 | "x64"
90 | ],
91 | "dev": true,
92 | "optional": true,
93 | "os": [
94 | "darwin"
95 | ],
96 | "engines": {
97 | "node": ">=12"
98 | }
99 | },
100 | "node_modules/@esbuild/freebsd-arm64": {
101 | "version": "0.17.19",
102 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz",
103 | "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==",
104 | "cpu": [
105 | "arm64"
106 | ],
107 | "dev": true,
108 | "optional": true,
109 | "os": [
110 | "freebsd"
111 | ],
112 | "engines": {
113 | "node": ">=12"
114 | }
115 | },
116 | "node_modules/@esbuild/freebsd-x64": {
117 | "version": "0.17.19",
118 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz",
119 | "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==",
120 | "cpu": [
121 | "x64"
122 | ],
123 | "dev": true,
124 | "optional": true,
125 | "os": [
126 | "freebsd"
127 | ],
128 | "engines": {
129 | "node": ">=12"
130 | }
131 | },
132 | "node_modules/@esbuild/linux-arm": {
133 | "version": "0.17.19",
134 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz",
135 | "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==",
136 | "cpu": [
137 | "arm"
138 | ],
139 | "dev": true,
140 | "optional": true,
141 | "os": [
142 | "linux"
143 | ],
144 | "engines": {
145 | "node": ">=12"
146 | }
147 | },
148 | "node_modules/@esbuild/linux-arm64": {
149 | "version": "0.17.19",
150 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz",
151 | "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==",
152 | "cpu": [
153 | "arm64"
154 | ],
155 | "dev": true,
156 | "optional": true,
157 | "os": [
158 | "linux"
159 | ],
160 | "engines": {
161 | "node": ">=12"
162 | }
163 | },
164 | "node_modules/@esbuild/linux-ia32": {
165 | "version": "0.17.19",
166 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz",
167 | "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==",
168 | "cpu": [
169 | "ia32"
170 | ],
171 | "dev": true,
172 | "optional": true,
173 | "os": [
174 | "linux"
175 | ],
176 | "engines": {
177 | "node": ">=12"
178 | }
179 | },
180 | "node_modules/@esbuild/linux-loong64": {
181 | "version": "0.17.19",
182 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz",
183 | "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==",
184 | "cpu": [
185 | "loong64"
186 | ],
187 | "dev": true,
188 | "optional": true,
189 | "os": [
190 | "linux"
191 | ],
192 | "engines": {
193 | "node": ">=12"
194 | }
195 | },
196 | "node_modules/@esbuild/linux-mips64el": {
197 | "version": "0.17.19",
198 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz",
199 | "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==",
200 | "cpu": [
201 | "mips64el"
202 | ],
203 | "dev": true,
204 | "optional": true,
205 | "os": [
206 | "linux"
207 | ],
208 | "engines": {
209 | "node": ">=12"
210 | }
211 | },
212 | "node_modules/@esbuild/linux-ppc64": {
213 | "version": "0.17.19",
214 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz",
215 | "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==",
216 | "cpu": [
217 | "ppc64"
218 | ],
219 | "dev": true,
220 | "optional": true,
221 | "os": [
222 | "linux"
223 | ],
224 | "engines": {
225 | "node": ">=12"
226 | }
227 | },
228 | "node_modules/@esbuild/linux-riscv64": {
229 | "version": "0.17.19",
230 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz",
231 | "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==",
232 | "cpu": [
233 | "riscv64"
234 | ],
235 | "dev": true,
236 | "optional": true,
237 | "os": [
238 | "linux"
239 | ],
240 | "engines": {
241 | "node": ">=12"
242 | }
243 | },
244 | "node_modules/@esbuild/linux-s390x": {
245 | "version": "0.17.19",
246 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz",
247 | "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==",
248 | "cpu": [
249 | "s390x"
250 | ],
251 | "dev": true,
252 | "optional": true,
253 | "os": [
254 | "linux"
255 | ],
256 | "engines": {
257 | "node": ">=12"
258 | }
259 | },
260 | "node_modules/@esbuild/linux-x64": {
261 | "version": "0.17.19",
262 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz",
263 | "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==",
264 | "cpu": [
265 | "x64"
266 | ],
267 | "dev": true,
268 | "optional": true,
269 | "os": [
270 | "linux"
271 | ],
272 | "engines": {
273 | "node": ">=12"
274 | }
275 | },
276 | "node_modules/@esbuild/netbsd-x64": {
277 | "version": "0.17.19",
278 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz",
279 | "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==",
280 | "cpu": [
281 | "x64"
282 | ],
283 | "dev": true,
284 | "optional": true,
285 | "os": [
286 | "netbsd"
287 | ],
288 | "engines": {
289 | "node": ">=12"
290 | }
291 | },
292 | "node_modules/@esbuild/openbsd-x64": {
293 | "version": "0.17.19",
294 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz",
295 | "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==",
296 | "cpu": [
297 | "x64"
298 | ],
299 | "dev": true,
300 | "optional": true,
301 | "os": [
302 | "openbsd"
303 | ],
304 | "engines": {
305 | "node": ">=12"
306 | }
307 | },
308 | "node_modules/@esbuild/sunos-x64": {
309 | "version": "0.17.19",
310 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz",
311 | "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==",
312 | "cpu": [
313 | "x64"
314 | ],
315 | "dev": true,
316 | "optional": true,
317 | "os": [
318 | "sunos"
319 | ],
320 | "engines": {
321 | "node": ">=12"
322 | }
323 | },
324 | "node_modules/@esbuild/win32-arm64": {
325 | "version": "0.17.19",
326 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz",
327 | "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==",
328 | "cpu": [
329 | "arm64"
330 | ],
331 | "dev": true,
332 | "optional": true,
333 | "os": [
334 | "win32"
335 | ],
336 | "engines": {
337 | "node": ">=12"
338 | }
339 | },
340 | "node_modules/@esbuild/win32-ia32": {
341 | "version": "0.17.19",
342 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz",
343 | "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==",
344 | "cpu": [
345 | "ia32"
346 | ],
347 | "dev": true,
348 | "optional": true,
349 | "os": [
350 | "win32"
351 | ],
352 | "engines": {
353 | "node": ">=12"
354 | }
355 | },
356 | "node_modules/@esbuild/win32-x64": {
357 | "version": "0.17.19",
358 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz",
359 | "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==",
360 | "cpu": [
361 | "x64"
362 | ],
363 | "dev": true,
364 | "optional": true,
365 | "os": [
366 | "win32"
367 | ],
368 | "engines": {
369 | "node": ">=12"
370 | }
371 | },
372 | "node_modules/core-js": {
373 | "version": "3.30.2",
374 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.30.2.tgz",
375 | "integrity": "sha512-uBJiDmwqsbJCWHAwjrx3cvjbMXP7xD72Dmsn5LOJpiRmE3WbBbN5rCqQ2Qh6Ek6/eOrjlWngEynBWo4VxerQhg==",
376 | "dev": true,
377 | "hasInstallScript": true,
378 | "funding": {
379 | "type": "opencollective",
380 | "url": "https://opencollective.com/core-js"
381 | }
382 | },
383 | "node_modules/esbuild": {
384 | "version": "0.17.19",
385 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz",
386 | "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==",
387 | "dev": true,
388 | "hasInstallScript": true,
389 | "bin": {
390 | "esbuild": "bin/esbuild"
391 | },
392 | "engines": {
393 | "node": ">=12"
394 | },
395 | "optionalDependencies": {
396 | "@esbuild/android-arm": "0.17.19",
397 | "@esbuild/android-arm64": "0.17.19",
398 | "@esbuild/android-x64": "0.17.19",
399 | "@esbuild/darwin-arm64": "0.17.19",
400 | "@esbuild/darwin-x64": "0.17.19",
401 | "@esbuild/freebsd-arm64": "0.17.19",
402 | "@esbuild/freebsd-x64": "0.17.19",
403 | "@esbuild/linux-arm": "0.17.19",
404 | "@esbuild/linux-arm64": "0.17.19",
405 | "@esbuild/linux-ia32": "0.17.19",
406 | "@esbuild/linux-loong64": "0.17.19",
407 | "@esbuild/linux-mips64el": "0.17.19",
408 | "@esbuild/linux-ppc64": "0.17.19",
409 | "@esbuild/linux-riscv64": "0.17.19",
410 | "@esbuild/linux-s390x": "0.17.19",
411 | "@esbuild/linux-x64": "0.17.19",
412 | "@esbuild/netbsd-x64": "0.17.19",
413 | "@esbuild/openbsd-x64": "0.17.19",
414 | "@esbuild/sunos-x64": "0.17.19",
415 | "@esbuild/win32-arm64": "0.17.19",
416 | "@esbuild/win32-ia32": "0.17.19",
417 | "@esbuild/win32-x64": "0.17.19"
418 | }
419 | },
420 | "node_modules/typescript": {
421 | "version": "5.4.5",
422 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
423 | "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
424 | "dev": true,
425 | "bin": {
426 | "tsc": "bin/tsc",
427 | "tsserver": "bin/tsserver"
428 | },
429 | "engines": {
430 | "node": ">=14.17"
431 | }
432 | },
433 | "node_modules/urlpattern-polyfill": {
434 | "version": "8.0.2",
435 | "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-8.0.2.tgz",
436 | "integrity": "sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ=="
437 | }
438 | }
439 | }
440 |
--------------------------------------------------------------------------------
/examples/bundled/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bundled-plugin",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "bundled-plugin",
9 | "version": "1.0.0",
10 | "license": "BSD-3-Clause",
11 | "devDependencies": {
12 | "@extism/js-pdk": "../../crates/core/src/prelude",
13 | "cross-env": "^7.0.3",
14 | "esbuild": "^0.19.6",
15 | "typescript": "^5.3.2"
16 | }
17 | },
18 | "../../crates/core/src/prelude": {
19 | "name": "@extism/js-pdk",
20 | "version": "1.1.1",
21 | "dev": true,
22 | "license": "BSD-Clause-3",
23 | "dependencies": {
24 | "urlpattern-polyfill": "^8.0.2"
25 | },
26 | "devDependencies": {
27 | "core-js": "^3.30.2",
28 | "esbuild": "^0.17.19",
29 | "typescript": "^5.4.5"
30 | }
31 | },
32 | "node_modules/@esbuild/android-arm": {
33 | "version": "0.19.6",
34 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.6.tgz",
35 | "integrity": "sha512-muPzBqXJKCbMYoNbb1JpZh/ynl0xS6/+pLjrofcR3Nad82SbsCogYzUE6Aq9QT3cLP0jR/IVK/NHC9b90mSHtg==",
36 | "cpu": [
37 | "arm"
38 | ],
39 | "dev": true,
40 | "optional": true,
41 | "os": [
42 | "android"
43 | ],
44 | "engines": {
45 | "node": ">=12"
46 | }
47 | },
48 | "node_modules/@esbuild/android-arm64": {
49 | "version": "0.19.6",
50 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.6.tgz",
51 | "integrity": "sha512-KQ/hbe9SJvIJ4sR+2PcZ41IBV+LPJyYp6V1K1P1xcMRup9iYsBoQn4MzE3mhMLOld27Au2eDcLlIREeKGUXpHQ==",
52 | "cpu": [
53 | "arm64"
54 | ],
55 | "dev": true,
56 | "optional": true,
57 | "os": [
58 | "android"
59 | ],
60 | "engines": {
61 | "node": ">=12"
62 | }
63 | },
64 | "node_modules/@esbuild/android-x64": {
65 | "version": "0.19.6",
66 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.6.tgz",
67 | "integrity": "sha512-VVJVZQ7p5BBOKoNxd0Ly3xUM78Y4DyOoFKdkdAe2m11jbh0LEU4bPles4e/72EMl4tapko8o915UalN/5zhspg==",
68 | "cpu": [
69 | "x64"
70 | ],
71 | "dev": true,
72 | "optional": true,
73 | "os": [
74 | "android"
75 | ],
76 | "engines": {
77 | "node": ">=12"
78 | }
79 | },
80 | "node_modules/@esbuild/darwin-arm64": {
81 | "version": "0.19.6",
82 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.6.tgz",
83 | "integrity": "sha512-91LoRp/uZAKx6ESNspL3I46ypwzdqyDLXZH7x2QYCLgtnaU08+AXEbabY2yExIz03/am0DivsTtbdxzGejfXpA==",
84 | "cpu": [
85 | "arm64"
86 | ],
87 | "dev": true,
88 | "optional": true,
89 | "os": [
90 | "darwin"
91 | ],
92 | "engines": {
93 | "node": ">=12"
94 | }
95 | },
96 | "node_modules/@esbuild/darwin-x64": {
97 | "version": "0.19.6",
98 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.6.tgz",
99 | "integrity": "sha512-QCGHw770ubjBU1J3ZkFJh671MFajGTYMZumPs9E/rqU52md6lIil97BR0CbPq6U+vTh3xnTNDHKRdR8ggHnmxQ==",
100 | "cpu": [
101 | "x64"
102 | ],
103 | "dev": true,
104 | "optional": true,
105 | "os": [
106 | "darwin"
107 | ],
108 | "engines": {
109 | "node": ">=12"
110 | }
111 | },
112 | "node_modules/@esbuild/freebsd-arm64": {
113 | "version": "0.19.6",
114 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.6.tgz",
115 | "integrity": "sha512-J53d0jGsDcLzWk9d9SPmlyF+wzVxjXpOH7jVW5ae7PvrDst4kiAz6sX+E8btz0GB6oH12zC+aHRD945jdjF2Vg==",
116 | "cpu": [
117 | "arm64"
118 | ],
119 | "dev": true,
120 | "optional": true,
121 | "os": [
122 | "freebsd"
123 | ],
124 | "engines": {
125 | "node": ">=12"
126 | }
127 | },
128 | "node_modules/@esbuild/freebsd-x64": {
129 | "version": "0.19.6",
130 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.6.tgz",
131 | "integrity": "sha512-hn9qvkjHSIB5Z9JgCCjED6YYVGCNpqB7dEGavBdG6EjBD8S/UcNUIlGcB35NCkMETkdYwfZSvD9VoDJX6VeUVA==",
132 | "cpu": [
133 | "x64"
134 | ],
135 | "dev": true,
136 | "optional": true,
137 | "os": [
138 | "freebsd"
139 | ],
140 | "engines": {
141 | "node": ">=12"
142 | }
143 | },
144 | "node_modules/@esbuild/linux-arm": {
145 | "version": "0.19.6",
146 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.6.tgz",
147 | "integrity": "sha512-G8IR5zFgpXad/Zp7gr7ZyTKyqZuThU6z1JjmRyN1vSF8j0bOlGzUwFSMTbctLAdd7QHpeyu0cRiuKrqK1ZTwvQ==",
148 | "cpu": [
149 | "arm"
150 | ],
151 | "dev": true,
152 | "optional": true,
153 | "os": [
154 | "linux"
155 | ],
156 | "engines": {
157 | "node": ">=12"
158 | }
159 | },
160 | "node_modules/@esbuild/linux-arm64": {
161 | "version": "0.19.6",
162 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.6.tgz",
163 | "integrity": "sha512-HQCOrk9XlH3KngASLaBfHpcoYEGUt829A9MyxaI8RMkfRA8SakG6YQEITAuwmtzFdEu5GU4eyhKcpv27dFaOBg==",
164 | "cpu": [
165 | "arm64"
166 | ],
167 | "dev": true,
168 | "optional": true,
169 | "os": [
170 | "linux"
171 | ],
172 | "engines": {
173 | "node": ">=12"
174 | }
175 | },
176 | "node_modules/@esbuild/linux-ia32": {
177 | "version": "0.19.6",
178 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.6.tgz",
179 | "integrity": "sha512-22eOR08zL/OXkmEhxOfshfOGo8P69k8oKHkwkDrUlcB12S/sw/+COM4PhAPT0cAYW/gpqY2uXp3TpjQVJitz7w==",
180 | "cpu": [
181 | "ia32"
182 | ],
183 | "dev": true,
184 | "optional": true,
185 | "os": [
186 | "linux"
187 | ],
188 | "engines": {
189 | "node": ">=12"
190 | }
191 | },
192 | "node_modules/@esbuild/linux-loong64": {
193 | "version": "0.19.6",
194 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.6.tgz",
195 | "integrity": "sha512-82RvaYAh/SUJyjWA8jDpyZCHQjmEggL//sC7F3VKYcBMumQjUL3C5WDl/tJpEiKtt7XrWmgjaLkrk205zfvwTA==",
196 | "cpu": [
197 | "loong64"
198 | ],
199 | "dev": true,
200 | "optional": true,
201 | "os": [
202 | "linux"
203 | ],
204 | "engines": {
205 | "node": ">=12"
206 | }
207 | },
208 | "node_modules/@esbuild/linux-mips64el": {
209 | "version": "0.19.6",
210 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.6.tgz",
211 | "integrity": "sha512-8tvnwyYJpR618vboIv2l8tK2SuK/RqUIGMfMENkeDGo3hsEIrpGldMGYFcWxWeEILe5Fi72zoXLmhZ7PR23oQA==",
212 | "cpu": [
213 | "mips64el"
214 | ],
215 | "dev": true,
216 | "optional": true,
217 | "os": [
218 | "linux"
219 | ],
220 | "engines": {
221 | "node": ">=12"
222 | }
223 | },
224 | "node_modules/@esbuild/linux-ppc64": {
225 | "version": "0.19.6",
226 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.6.tgz",
227 | "integrity": "sha512-Qt+D7xiPajxVNk5tQiEJwhmarNnLPdjXAoA5uWMpbfStZB0+YU6a3CtbWYSy+sgAsnyx4IGZjWsTzBzrvg/fMA==",
228 | "cpu": [
229 | "ppc64"
230 | ],
231 | "dev": true,
232 | "optional": true,
233 | "os": [
234 | "linux"
235 | ],
236 | "engines": {
237 | "node": ">=12"
238 | }
239 | },
240 | "node_modules/@esbuild/linux-riscv64": {
241 | "version": "0.19.6",
242 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.6.tgz",
243 | "integrity": "sha512-lxRdk0iJ9CWYDH1Wpnnnc640ajF4RmQ+w6oHFZmAIYu577meE9Ka/DCtpOrwr9McMY11ocbp4jirgGgCi7Ls/g==",
244 | "cpu": [
245 | "riscv64"
246 | ],
247 | "dev": true,
248 | "optional": true,
249 | "os": [
250 | "linux"
251 | ],
252 | "engines": {
253 | "node": ">=12"
254 | }
255 | },
256 | "node_modules/@esbuild/linux-s390x": {
257 | "version": "0.19.6",
258 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.6.tgz",
259 | "integrity": "sha512-MopyYV39vnfuykHanRWHGRcRC3AwU7b0QY4TI8ISLfAGfK+tMkXyFuyT1epw/lM0pflQlS53JoD22yN83DHZgA==",
260 | "cpu": [
261 | "s390x"
262 | ],
263 | "dev": true,
264 | "optional": true,
265 | "os": [
266 | "linux"
267 | ],
268 | "engines": {
269 | "node": ">=12"
270 | }
271 | },
272 | "node_modules/@esbuild/linux-x64": {
273 | "version": "0.19.6",
274 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.6.tgz",
275 | "integrity": "sha512-UWcieaBzsN8WYbzFF5Jq7QULETPcQvlX7KL4xWGIB54OknXJjBO37sPqk7N82WU13JGWvmDzFBi1weVBajPovg==",
276 | "cpu": [
277 | "x64"
278 | ],
279 | "dev": true,
280 | "optional": true,
281 | "os": [
282 | "linux"
283 | ],
284 | "engines": {
285 | "node": ">=12"
286 | }
287 | },
288 | "node_modules/@esbuild/netbsd-x64": {
289 | "version": "0.19.6",
290 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.6.tgz",
291 | "integrity": "sha512-EpWiLX0fzvZn1wxtLxZrEW+oQED9Pwpnh+w4Ffv8ZLuMhUoqR9q9rL4+qHW8F4Mg5oQEKxAoT0G+8JYNqCiR6g==",
292 | "cpu": [
293 | "x64"
294 | ],
295 | "dev": true,
296 | "optional": true,
297 | "os": [
298 | "netbsd"
299 | ],
300 | "engines": {
301 | "node": ">=12"
302 | }
303 | },
304 | "node_modules/@esbuild/openbsd-x64": {
305 | "version": "0.19.6",
306 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.6.tgz",
307 | "integrity": "sha512-fFqTVEktM1PGs2sLKH4M5mhAVEzGpeZJuasAMRnvDZNCV0Cjvm1Hu35moL2vC0DOrAQjNTvj4zWrol/lwQ8Deg==",
308 | "cpu": [
309 | "x64"
310 | ],
311 | "dev": true,
312 | "optional": true,
313 | "os": [
314 | "openbsd"
315 | ],
316 | "engines": {
317 | "node": ">=12"
318 | }
319 | },
320 | "node_modules/@esbuild/sunos-x64": {
321 | "version": "0.19.6",
322 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.6.tgz",
323 | "integrity": "sha512-M+XIAnBpaNvaVAhbe3uBXtgWyWynSdlww/JNZws0FlMPSBy+EpatPXNIlKAdtbFVII9OpX91ZfMb17TU3JKTBA==",
324 | "cpu": [
325 | "x64"
326 | ],
327 | "dev": true,
328 | "optional": true,
329 | "os": [
330 | "sunos"
331 | ],
332 | "engines": {
333 | "node": ">=12"
334 | }
335 | },
336 | "node_modules/@esbuild/win32-arm64": {
337 | "version": "0.19.6",
338 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.6.tgz",
339 | "integrity": "sha512-2DchFXn7vp/B6Tc2eKdTsLzE0ygqKkNUhUBCNtMx2Llk4POIVMUq5rUYjdcedFlGLeRe1uLCpVvCmE+G8XYybA==",
340 | "cpu": [
341 | "arm64"
342 | ],
343 | "dev": true,
344 | "optional": true,
345 | "os": [
346 | "win32"
347 | ],
348 | "engines": {
349 | "node": ">=12"
350 | }
351 | },
352 | "node_modules/@esbuild/win32-ia32": {
353 | "version": "0.19.6",
354 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.6.tgz",
355 | "integrity": "sha512-PBo/HPDQllyWdjwAVX+Gl2hH0dfBydL97BAH/grHKC8fubqp02aL4S63otZ25q3sBdINtOBbz1qTZQfXbP4VBg==",
356 | "cpu": [
357 | "ia32"
358 | ],
359 | "dev": true,
360 | "optional": true,
361 | "os": [
362 | "win32"
363 | ],
364 | "engines": {
365 | "node": ">=12"
366 | }
367 | },
368 | "node_modules/@esbuild/win32-x64": {
369 | "version": "0.19.6",
370 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.6.tgz",
371 | "integrity": "sha512-OE7yIdbDif2kKfrGa+V0vx/B3FJv2L4KnIiLlvtibPyO9UkgO3rzYE0HhpREo2vmJ1Ixq1zwm9/0er+3VOSZJA==",
372 | "cpu": [
373 | "x64"
374 | ],
375 | "dev": true,
376 | "optional": true,
377 | "os": [
378 | "win32"
379 | ],
380 | "engines": {
381 | "node": ">=12"
382 | }
383 | },
384 | "node_modules/@extism/js-pdk": {
385 | "resolved": "../../crates/core/src/prelude",
386 | "link": true
387 | },
388 | "node_modules/cross-env": {
389 | "version": "7.0.3",
390 | "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
391 | "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
392 | "dev": true,
393 | "dependencies": {
394 | "cross-spawn": "^7.0.1"
395 | },
396 | "bin": {
397 | "cross-env": "src/bin/cross-env.js",
398 | "cross-env-shell": "src/bin/cross-env-shell.js"
399 | },
400 | "engines": {
401 | "node": ">=10.14",
402 | "npm": ">=6",
403 | "yarn": ">=1"
404 | }
405 | },
406 | "node_modules/cross-spawn": {
407 | "version": "7.0.3",
408 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
409 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
410 | "dev": true,
411 | "dependencies": {
412 | "path-key": "^3.1.0",
413 | "shebang-command": "^2.0.0",
414 | "which": "^2.0.1"
415 | },
416 | "engines": {
417 | "node": ">= 8"
418 | }
419 | },
420 | "node_modules/esbuild": {
421 | "version": "0.19.6",
422 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.6.tgz",
423 | "integrity": "sha512-Xl7dntjA2OEIvpr9j0DVxxnog2fyTGnyVoQXAMQI6eR3mf9zCQds7VIKUDCotDgE/p4ncTgeRqgX8t5d6oP4Gw==",
424 | "dev": true,
425 | "hasInstallScript": true,
426 | "bin": {
427 | "esbuild": "bin/esbuild"
428 | },
429 | "engines": {
430 | "node": ">=12"
431 | },
432 | "optionalDependencies": {
433 | "@esbuild/android-arm": "0.19.6",
434 | "@esbuild/android-arm64": "0.19.6",
435 | "@esbuild/android-x64": "0.19.6",
436 | "@esbuild/darwin-arm64": "0.19.6",
437 | "@esbuild/darwin-x64": "0.19.6",
438 | "@esbuild/freebsd-arm64": "0.19.6",
439 | "@esbuild/freebsd-x64": "0.19.6",
440 | "@esbuild/linux-arm": "0.19.6",
441 | "@esbuild/linux-arm64": "0.19.6",
442 | "@esbuild/linux-ia32": "0.19.6",
443 | "@esbuild/linux-loong64": "0.19.6",
444 | "@esbuild/linux-mips64el": "0.19.6",
445 | "@esbuild/linux-ppc64": "0.19.6",
446 | "@esbuild/linux-riscv64": "0.19.6",
447 | "@esbuild/linux-s390x": "0.19.6",
448 | "@esbuild/linux-x64": "0.19.6",
449 | "@esbuild/netbsd-x64": "0.19.6",
450 | "@esbuild/openbsd-x64": "0.19.6",
451 | "@esbuild/sunos-x64": "0.19.6",
452 | "@esbuild/win32-arm64": "0.19.6",
453 | "@esbuild/win32-ia32": "0.19.6",
454 | "@esbuild/win32-x64": "0.19.6"
455 | }
456 | },
457 | "node_modules/isexe": {
458 | "version": "2.0.0",
459 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
460 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
461 | "dev": true
462 | },
463 | "node_modules/path-key": {
464 | "version": "3.1.1",
465 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
466 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
467 | "dev": true,
468 | "engines": {
469 | "node": ">=8"
470 | }
471 | },
472 | "node_modules/shebang-command": {
473 | "version": "2.0.0",
474 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
475 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
476 | "dev": true,
477 | "dependencies": {
478 | "shebang-regex": "^3.0.0"
479 | },
480 | "engines": {
481 | "node": ">=8"
482 | }
483 | },
484 | "node_modules/shebang-regex": {
485 | "version": "3.0.0",
486 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
487 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
488 | "dev": true,
489 | "engines": {
490 | "node": ">=8"
491 | }
492 | },
493 | "node_modules/typescript": {
494 | "version": "5.3.2",
495 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz",
496 | "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==",
497 | "dev": true,
498 | "bin": {
499 | "tsc": "bin/tsc",
500 | "tsserver": "bin/tsserver"
501 | },
502 | "engines": {
503 | "node": ">=14.17"
504 | }
505 | },
506 | "node_modules/which": {
507 | "version": "2.0.2",
508 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
509 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
510 | "dev": true,
511 | "dependencies": {
512 | "isexe": "^2.0.0"
513 | },
514 | "bin": {
515 | "node-which": "bin/node-which"
516 | },
517 | "engines": {
518 | "node": ">= 8"
519 | }
520 | }
521 | }
522 | }
523 |
--------------------------------------------------------------------------------
/examples/kitchen-sink/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "kitchen-sink",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "kitchen-sink",
9 | "version": "1.0.0",
10 | "license": "BSD-3-Clause",
11 | "devDependencies": {
12 | "@extism/js-pdk": "../../crates/core/src/prelude",
13 | "cross-env": "^7.0.3",
14 | "esbuild": "^0.19.6",
15 | "typescript": "^5.3.2"
16 | }
17 | },
18 | "../../crates/core/src/prelude": {
19 | "name": "@extism/js-pdk",
20 | "version": "1.1.1",
21 | "dev": true,
22 | "license": "BSD-Clause-3",
23 | "dependencies": {
24 | "urlpattern-polyfill": "^8.0.2"
25 | },
26 | "devDependencies": {
27 | "core-js": "^3.30.2",
28 | "esbuild": "^0.17.19",
29 | "typescript": "^5.4.5"
30 | }
31 | },
32 | "node_modules/@esbuild/aix-ppc64": {
33 | "version": "0.19.12",
34 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz",
35 | "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==",
36 | "cpu": [
37 | "ppc64"
38 | ],
39 | "dev": true,
40 | "optional": true,
41 | "os": [
42 | "aix"
43 | ],
44 | "engines": {
45 | "node": ">=12"
46 | }
47 | },
48 | "node_modules/@esbuild/android-arm": {
49 | "version": "0.19.12",
50 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz",
51 | "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==",
52 | "cpu": [
53 | "arm"
54 | ],
55 | "dev": true,
56 | "optional": true,
57 | "os": [
58 | "android"
59 | ],
60 | "engines": {
61 | "node": ">=12"
62 | }
63 | },
64 | "node_modules/@esbuild/android-arm64": {
65 | "version": "0.19.12",
66 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz",
67 | "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==",
68 | "cpu": [
69 | "arm64"
70 | ],
71 | "dev": true,
72 | "optional": true,
73 | "os": [
74 | "android"
75 | ],
76 | "engines": {
77 | "node": ">=12"
78 | }
79 | },
80 | "node_modules/@esbuild/android-x64": {
81 | "version": "0.19.12",
82 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz",
83 | "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==",
84 | "cpu": [
85 | "x64"
86 | ],
87 | "dev": true,
88 | "optional": true,
89 | "os": [
90 | "android"
91 | ],
92 | "engines": {
93 | "node": ">=12"
94 | }
95 | },
96 | "node_modules/@esbuild/darwin-arm64": {
97 | "version": "0.19.12",
98 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz",
99 | "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==",
100 | "cpu": [
101 | "arm64"
102 | ],
103 | "dev": true,
104 | "optional": true,
105 | "os": [
106 | "darwin"
107 | ],
108 | "engines": {
109 | "node": ">=12"
110 | }
111 | },
112 | "node_modules/@esbuild/darwin-x64": {
113 | "version": "0.19.12",
114 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz",
115 | "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==",
116 | "cpu": [
117 | "x64"
118 | ],
119 | "dev": true,
120 | "optional": true,
121 | "os": [
122 | "darwin"
123 | ],
124 | "engines": {
125 | "node": ">=12"
126 | }
127 | },
128 | "node_modules/@esbuild/freebsd-arm64": {
129 | "version": "0.19.12",
130 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz",
131 | "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==",
132 | "cpu": [
133 | "arm64"
134 | ],
135 | "dev": true,
136 | "optional": true,
137 | "os": [
138 | "freebsd"
139 | ],
140 | "engines": {
141 | "node": ">=12"
142 | }
143 | },
144 | "node_modules/@esbuild/freebsd-x64": {
145 | "version": "0.19.12",
146 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz",
147 | "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==",
148 | "cpu": [
149 | "x64"
150 | ],
151 | "dev": true,
152 | "optional": true,
153 | "os": [
154 | "freebsd"
155 | ],
156 | "engines": {
157 | "node": ">=12"
158 | }
159 | },
160 | "node_modules/@esbuild/linux-arm": {
161 | "version": "0.19.12",
162 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz",
163 | "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==",
164 | "cpu": [
165 | "arm"
166 | ],
167 | "dev": true,
168 | "optional": true,
169 | "os": [
170 | "linux"
171 | ],
172 | "engines": {
173 | "node": ">=12"
174 | }
175 | },
176 | "node_modules/@esbuild/linux-arm64": {
177 | "version": "0.19.12",
178 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz",
179 | "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==",
180 | "cpu": [
181 | "arm64"
182 | ],
183 | "dev": true,
184 | "optional": true,
185 | "os": [
186 | "linux"
187 | ],
188 | "engines": {
189 | "node": ">=12"
190 | }
191 | },
192 | "node_modules/@esbuild/linux-ia32": {
193 | "version": "0.19.12",
194 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz",
195 | "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==",
196 | "cpu": [
197 | "ia32"
198 | ],
199 | "dev": true,
200 | "optional": true,
201 | "os": [
202 | "linux"
203 | ],
204 | "engines": {
205 | "node": ">=12"
206 | }
207 | },
208 | "node_modules/@esbuild/linux-loong64": {
209 | "version": "0.19.12",
210 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz",
211 | "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==",
212 | "cpu": [
213 | "loong64"
214 | ],
215 | "dev": true,
216 | "optional": true,
217 | "os": [
218 | "linux"
219 | ],
220 | "engines": {
221 | "node": ">=12"
222 | }
223 | },
224 | "node_modules/@esbuild/linux-mips64el": {
225 | "version": "0.19.12",
226 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz",
227 | "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==",
228 | "cpu": [
229 | "mips64el"
230 | ],
231 | "dev": true,
232 | "optional": true,
233 | "os": [
234 | "linux"
235 | ],
236 | "engines": {
237 | "node": ">=12"
238 | }
239 | },
240 | "node_modules/@esbuild/linux-ppc64": {
241 | "version": "0.19.12",
242 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz",
243 | "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==",
244 | "cpu": [
245 | "ppc64"
246 | ],
247 | "dev": true,
248 | "optional": true,
249 | "os": [
250 | "linux"
251 | ],
252 | "engines": {
253 | "node": ">=12"
254 | }
255 | },
256 | "node_modules/@esbuild/linux-riscv64": {
257 | "version": "0.19.12",
258 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz",
259 | "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==",
260 | "cpu": [
261 | "riscv64"
262 | ],
263 | "dev": true,
264 | "optional": true,
265 | "os": [
266 | "linux"
267 | ],
268 | "engines": {
269 | "node": ">=12"
270 | }
271 | },
272 | "node_modules/@esbuild/linux-s390x": {
273 | "version": "0.19.12",
274 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz",
275 | "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==",
276 | "cpu": [
277 | "s390x"
278 | ],
279 | "dev": true,
280 | "optional": true,
281 | "os": [
282 | "linux"
283 | ],
284 | "engines": {
285 | "node": ">=12"
286 | }
287 | },
288 | "node_modules/@esbuild/linux-x64": {
289 | "version": "0.19.12",
290 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz",
291 | "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==",
292 | "cpu": [
293 | "x64"
294 | ],
295 | "dev": true,
296 | "optional": true,
297 | "os": [
298 | "linux"
299 | ],
300 | "engines": {
301 | "node": ">=12"
302 | }
303 | },
304 | "node_modules/@esbuild/netbsd-x64": {
305 | "version": "0.19.12",
306 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz",
307 | "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==",
308 | "cpu": [
309 | "x64"
310 | ],
311 | "dev": true,
312 | "optional": true,
313 | "os": [
314 | "netbsd"
315 | ],
316 | "engines": {
317 | "node": ">=12"
318 | }
319 | },
320 | "node_modules/@esbuild/openbsd-x64": {
321 | "version": "0.19.12",
322 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz",
323 | "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==",
324 | "cpu": [
325 | "x64"
326 | ],
327 | "dev": true,
328 | "optional": true,
329 | "os": [
330 | "openbsd"
331 | ],
332 | "engines": {
333 | "node": ">=12"
334 | }
335 | },
336 | "node_modules/@esbuild/sunos-x64": {
337 | "version": "0.19.12",
338 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz",
339 | "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==",
340 | "cpu": [
341 | "x64"
342 | ],
343 | "dev": true,
344 | "optional": true,
345 | "os": [
346 | "sunos"
347 | ],
348 | "engines": {
349 | "node": ">=12"
350 | }
351 | },
352 | "node_modules/@esbuild/win32-arm64": {
353 | "version": "0.19.12",
354 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz",
355 | "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==",
356 | "cpu": [
357 | "arm64"
358 | ],
359 | "dev": true,
360 | "optional": true,
361 | "os": [
362 | "win32"
363 | ],
364 | "engines": {
365 | "node": ">=12"
366 | }
367 | },
368 | "node_modules/@esbuild/win32-ia32": {
369 | "version": "0.19.12",
370 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz",
371 | "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==",
372 | "cpu": [
373 | "ia32"
374 | ],
375 | "dev": true,
376 | "optional": true,
377 | "os": [
378 | "win32"
379 | ],
380 | "engines": {
381 | "node": ">=12"
382 | }
383 | },
384 | "node_modules/@esbuild/win32-x64": {
385 | "version": "0.19.12",
386 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz",
387 | "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==",
388 | "cpu": [
389 | "x64"
390 | ],
391 | "dev": true,
392 | "optional": true,
393 | "os": [
394 | "win32"
395 | ],
396 | "engines": {
397 | "node": ">=12"
398 | }
399 | },
400 | "node_modules/@extism/js-pdk": {
401 | "resolved": "../../crates/core/src/prelude",
402 | "link": true
403 | },
404 | "node_modules/cross-env": {
405 | "version": "7.0.3",
406 | "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
407 | "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
408 | "dev": true,
409 | "dependencies": {
410 | "cross-spawn": "^7.0.1"
411 | },
412 | "bin": {
413 | "cross-env": "src/bin/cross-env.js",
414 | "cross-env-shell": "src/bin/cross-env-shell.js"
415 | },
416 | "engines": {
417 | "node": ">=10.14",
418 | "npm": ">=6",
419 | "yarn": ">=1"
420 | }
421 | },
422 | "node_modules/cross-spawn": {
423 | "version": "7.0.3",
424 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
425 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
426 | "dev": true,
427 | "dependencies": {
428 | "path-key": "^3.1.0",
429 | "shebang-command": "^2.0.0",
430 | "which": "^2.0.1"
431 | },
432 | "engines": {
433 | "node": ">= 8"
434 | }
435 | },
436 | "node_modules/esbuild": {
437 | "version": "0.19.12",
438 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz",
439 | "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==",
440 | "dev": true,
441 | "hasInstallScript": true,
442 | "bin": {
443 | "esbuild": "bin/esbuild"
444 | },
445 | "engines": {
446 | "node": ">=12"
447 | },
448 | "optionalDependencies": {
449 | "@esbuild/aix-ppc64": "0.19.12",
450 | "@esbuild/android-arm": "0.19.12",
451 | "@esbuild/android-arm64": "0.19.12",
452 | "@esbuild/android-x64": "0.19.12",
453 | "@esbuild/darwin-arm64": "0.19.12",
454 | "@esbuild/darwin-x64": "0.19.12",
455 | "@esbuild/freebsd-arm64": "0.19.12",
456 | "@esbuild/freebsd-x64": "0.19.12",
457 | "@esbuild/linux-arm": "0.19.12",
458 | "@esbuild/linux-arm64": "0.19.12",
459 | "@esbuild/linux-ia32": "0.19.12",
460 | "@esbuild/linux-loong64": "0.19.12",
461 | "@esbuild/linux-mips64el": "0.19.12",
462 | "@esbuild/linux-ppc64": "0.19.12",
463 | "@esbuild/linux-riscv64": "0.19.12",
464 | "@esbuild/linux-s390x": "0.19.12",
465 | "@esbuild/linux-x64": "0.19.12",
466 | "@esbuild/netbsd-x64": "0.19.12",
467 | "@esbuild/openbsd-x64": "0.19.12",
468 | "@esbuild/sunos-x64": "0.19.12",
469 | "@esbuild/win32-arm64": "0.19.12",
470 | "@esbuild/win32-ia32": "0.19.12",
471 | "@esbuild/win32-x64": "0.19.12"
472 | }
473 | },
474 | "node_modules/isexe": {
475 | "version": "2.0.0",
476 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
477 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
478 | "dev": true
479 | },
480 | "node_modules/path-key": {
481 | "version": "3.1.1",
482 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
483 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
484 | "dev": true,
485 | "engines": {
486 | "node": ">=8"
487 | }
488 | },
489 | "node_modules/shebang-command": {
490 | "version": "2.0.0",
491 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
492 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
493 | "dev": true,
494 | "dependencies": {
495 | "shebang-regex": "^3.0.0"
496 | },
497 | "engines": {
498 | "node": ">=8"
499 | }
500 | },
501 | "node_modules/shebang-regex": {
502 | "version": "3.0.0",
503 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
504 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
505 | "dev": true,
506 | "engines": {
507 | "node": ">=8"
508 | }
509 | },
510 | "node_modules/typescript": {
511 | "version": "5.4.3",
512 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz",
513 | "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==",
514 | "dev": true,
515 | "bin": {
516 | "tsc": "bin/tsc",
517 | "tsserver": "bin/tsserver"
518 | },
519 | "engines": {
520 | "node": ">=14.17"
521 | }
522 | },
523 | "node_modules/which": {
524 | "version": "2.0.2",
525 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
526 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
527 | "dev": true,
528 | "dependencies": {
529 | "isexe": "^2.0.0"
530 | },
531 | "bin": {
532 | "node-which": "bin/node-which"
533 | },
534 | "engines": {
535 | "node": ">= 8"
536 | }
537 | }
538 | }
539 | }
540 |
--------------------------------------------------------------------------------
/examples/react/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-plugin",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "react-plugin",
9 | "version": "1.0.0",
10 | "license": "BSD-3-Clause",
11 | "dependencies": {
12 | "react-dom": "^18.3.1"
13 | },
14 | "devDependencies": {
15 | "@extism/js-pdk": "../../crates/core/src/prelude",
16 | "@types/react": "^18.3.1",
17 | "cross-env": "^7.0.3",
18 | "esbuild": "^0.19.6",
19 | "typescript": "^5.3.2"
20 | }
21 | },
22 | "../../crates/core/src/prelude": {
23 | "name": "@extism/js-pdk",
24 | "version": "1.1.1",
25 | "dev": true,
26 | "license": "BSD-Clause-3",
27 | "dependencies": {
28 | "urlpattern-polyfill": "^8.0.2"
29 | },
30 | "devDependencies": {
31 | "core-js": "^3.30.2",
32 | "esbuild": "^0.17.19",
33 | "typescript": "^5.4.5"
34 | }
35 | },
36 | "node_modules/@esbuild/android-arm": {
37 | "version": "0.19.6",
38 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.6.tgz",
39 | "integrity": "sha512-muPzBqXJKCbMYoNbb1JpZh/ynl0xS6/+pLjrofcR3Nad82SbsCogYzUE6Aq9QT3cLP0jR/IVK/NHC9b90mSHtg==",
40 | "cpu": [
41 | "arm"
42 | ],
43 | "dev": true,
44 | "optional": true,
45 | "os": [
46 | "android"
47 | ],
48 | "engines": {
49 | "node": ">=12"
50 | }
51 | },
52 | "node_modules/@esbuild/android-arm64": {
53 | "version": "0.19.6",
54 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.6.tgz",
55 | "integrity": "sha512-KQ/hbe9SJvIJ4sR+2PcZ41IBV+LPJyYp6V1K1P1xcMRup9iYsBoQn4MzE3mhMLOld27Au2eDcLlIREeKGUXpHQ==",
56 | "cpu": [
57 | "arm64"
58 | ],
59 | "dev": true,
60 | "optional": true,
61 | "os": [
62 | "android"
63 | ],
64 | "engines": {
65 | "node": ">=12"
66 | }
67 | },
68 | "node_modules/@esbuild/android-x64": {
69 | "version": "0.19.6",
70 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.6.tgz",
71 | "integrity": "sha512-VVJVZQ7p5BBOKoNxd0Ly3xUM78Y4DyOoFKdkdAe2m11jbh0LEU4bPles4e/72EMl4tapko8o915UalN/5zhspg==",
72 | "cpu": [
73 | "x64"
74 | ],
75 | "dev": true,
76 | "optional": true,
77 | "os": [
78 | "android"
79 | ],
80 | "engines": {
81 | "node": ">=12"
82 | }
83 | },
84 | "node_modules/@esbuild/darwin-arm64": {
85 | "version": "0.19.6",
86 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.6.tgz",
87 | "integrity": "sha512-91LoRp/uZAKx6ESNspL3I46ypwzdqyDLXZH7x2QYCLgtnaU08+AXEbabY2yExIz03/am0DivsTtbdxzGejfXpA==",
88 | "cpu": [
89 | "arm64"
90 | ],
91 | "dev": true,
92 | "optional": true,
93 | "os": [
94 | "darwin"
95 | ],
96 | "engines": {
97 | "node": ">=12"
98 | }
99 | },
100 | "node_modules/@esbuild/darwin-x64": {
101 | "version": "0.19.6",
102 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.6.tgz",
103 | "integrity": "sha512-QCGHw770ubjBU1J3ZkFJh671MFajGTYMZumPs9E/rqU52md6lIil97BR0CbPq6U+vTh3xnTNDHKRdR8ggHnmxQ==",
104 | "cpu": [
105 | "x64"
106 | ],
107 | "dev": true,
108 | "optional": true,
109 | "os": [
110 | "darwin"
111 | ],
112 | "engines": {
113 | "node": ">=12"
114 | }
115 | },
116 | "node_modules/@esbuild/freebsd-arm64": {
117 | "version": "0.19.6",
118 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.6.tgz",
119 | "integrity": "sha512-J53d0jGsDcLzWk9d9SPmlyF+wzVxjXpOH7jVW5ae7PvrDst4kiAz6sX+E8btz0GB6oH12zC+aHRD945jdjF2Vg==",
120 | "cpu": [
121 | "arm64"
122 | ],
123 | "dev": true,
124 | "optional": true,
125 | "os": [
126 | "freebsd"
127 | ],
128 | "engines": {
129 | "node": ">=12"
130 | }
131 | },
132 | "node_modules/@esbuild/freebsd-x64": {
133 | "version": "0.19.6",
134 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.6.tgz",
135 | "integrity": "sha512-hn9qvkjHSIB5Z9JgCCjED6YYVGCNpqB7dEGavBdG6EjBD8S/UcNUIlGcB35NCkMETkdYwfZSvD9VoDJX6VeUVA==",
136 | "cpu": [
137 | "x64"
138 | ],
139 | "dev": true,
140 | "optional": true,
141 | "os": [
142 | "freebsd"
143 | ],
144 | "engines": {
145 | "node": ">=12"
146 | }
147 | },
148 | "node_modules/@esbuild/linux-arm": {
149 | "version": "0.19.6",
150 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.6.tgz",
151 | "integrity": "sha512-G8IR5zFgpXad/Zp7gr7ZyTKyqZuThU6z1JjmRyN1vSF8j0bOlGzUwFSMTbctLAdd7QHpeyu0cRiuKrqK1ZTwvQ==",
152 | "cpu": [
153 | "arm"
154 | ],
155 | "dev": true,
156 | "optional": true,
157 | "os": [
158 | "linux"
159 | ],
160 | "engines": {
161 | "node": ">=12"
162 | }
163 | },
164 | "node_modules/@esbuild/linux-arm64": {
165 | "version": "0.19.6",
166 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.6.tgz",
167 | "integrity": "sha512-HQCOrk9XlH3KngASLaBfHpcoYEGUt829A9MyxaI8RMkfRA8SakG6YQEITAuwmtzFdEu5GU4eyhKcpv27dFaOBg==",
168 | "cpu": [
169 | "arm64"
170 | ],
171 | "dev": true,
172 | "optional": true,
173 | "os": [
174 | "linux"
175 | ],
176 | "engines": {
177 | "node": ">=12"
178 | }
179 | },
180 | "node_modules/@esbuild/linux-ia32": {
181 | "version": "0.19.6",
182 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.6.tgz",
183 | "integrity": "sha512-22eOR08zL/OXkmEhxOfshfOGo8P69k8oKHkwkDrUlcB12S/sw/+COM4PhAPT0cAYW/gpqY2uXp3TpjQVJitz7w==",
184 | "cpu": [
185 | "ia32"
186 | ],
187 | "dev": true,
188 | "optional": true,
189 | "os": [
190 | "linux"
191 | ],
192 | "engines": {
193 | "node": ">=12"
194 | }
195 | },
196 | "node_modules/@esbuild/linux-loong64": {
197 | "version": "0.19.6",
198 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.6.tgz",
199 | "integrity": "sha512-82RvaYAh/SUJyjWA8jDpyZCHQjmEggL//sC7F3VKYcBMumQjUL3C5WDl/tJpEiKtt7XrWmgjaLkrk205zfvwTA==",
200 | "cpu": [
201 | "loong64"
202 | ],
203 | "dev": true,
204 | "optional": true,
205 | "os": [
206 | "linux"
207 | ],
208 | "engines": {
209 | "node": ">=12"
210 | }
211 | },
212 | "node_modules/@esbuild/linux-mips64el": {
213 | "version": "0.19.6",
214 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.6.tgz",
215 | "integrity": "sha512-8tvnwyYJpR618vboIv2l8tK2SuK/RqUIGMfMENkeDGo3hsEIrpGldMGYFcWxWeEILe5Fi72zoXLmhZ7PR23oQA==",
216 | "cpu": [
217 | "mips64el"
218 | ],
219 | "dev": true,
220 | "optional": true,
221 | "os": [
222 | "linux"
223 | ],
224 | "engines": {
225 | "node": ">=12"
226 | }
227 | },
228 | "node_modules/@esbuild/linux-ppc64": {
229 | "version": "0.19.6",
230 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.6.tgz",
231 | "integrity": "sha512-Qt+D7xiPajxVNk5tQiEJwhmarNnLPdjXAoA5uWMpbfStZB0+YU6a3CtbWYSy+sgAsnyx4IGZjWsTzBzrvg/fMA==",
232 | "cpu": [
233 | "ppc64"
234 | ],
235 | "dev": true,
236 | "optional": true,
237 | "os": [
238 | "linux"
239 | ],
240 | "engines": {
241 | "node": ">=12"
242 | }
243 | },
244 | "node_modules/@esbuild/linux-riscv64": {
245 | "version": "0.19.6",
246 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.6.tgz",
247 | "integrity": "sha512-lxRdk0iJ9CWYDH1Wpnnnc640ajF4RmQ+w6oHFZmAIYu577meE9Ka/DCtpOrwr9McMY11ocbp4jirgGgCi7Ls/g==",
248 | "cpu": [
249 | "riscv64"
250 | ],
251 | "dev": true,
252 | "optional": true,
253 | "os": [
254 | "linux"
255 | ],
256 | "engines": {
257 | "node": ">=12"
258 | }
259 | },
260 | "node_modules/@esbuild/linux-s390x": {
261 | "version": "0.19.6",
262 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.6.tgz",
263 | "integrity": "sha512-MopyYV39vnfuykHanRWHGRcRC3AwU7b0QY4TI8ISLfAGfK+tMkXyFuyT1epw/lM0pflQlS53JoD22yN83DHZgA==",
264 | "cpu": [
265 | "s390x"
266 | ],
267 | "dev": true,
268 | "optional": true,
269 | "os": [
270 | "linux"
271 | ],
272 | "engines": {
273 | "node": ">=12"
274 | }
275 | },
276 | "node_modules/@esbuild/linux-x64": {
277 | "version": "0.19.6",
278 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.6.tgz",
279 | "integrity": "sha512-UWcieaBzsN8WYbzFF5Jq7QULETPcQvlX7KL4xWGIB54OknXJjBO37sPqk7N82WU13JGWvmDzFBi1weVBajPovg==",
280 | "cpu": [
281 | "x64"
282 | ],
283 | "dev": true,
284 | "optional": true,
285 | "os": [
286 | "linux"
287 | ],
288 | "engines": {
289 | "node": ">=12"
290 | }
291 | },
292 | "node_modules/@esbuild/netbsd-x64": {
293 | "version": "0.19.6",
294 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.6.tgz",
295 | "integrity": "sha512-EpWiLX0fzvZn1wxtLxZrEW+oQED9Pwpnh+w4Ffv8ZLuMhUoqR9q9rL4+qHW8F4Mg5oQEKxAoT0G+8JYNqCiR6g==",
296 | "cpu": [
297 | "x64"
298 | ],
299 | "dev": true,
300 | "optional": true,
301 | "os": [
302 | "netbsd"
303 | ],
304 | "engines": {
305 | "node": ">=12"
306 | }
307 | },
308 | "node_modules/@esbuild/openbsd-x64": {
309 | "version": "0.19.6",
310 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.6.tgz",
311 | "integrity": "sha512-fFqTVEktM1PGs2sLKH4M5mhAVEzGpeZJuasAMRnvDZNCV0Cjvm1Hu35moL2vC0DOrAQjNTvj4zWrol/lwQ8Deg==",
312 | "cpu": [
313 | "x64"
314 | ],
315 | "dev": true,
316 | "optional": true,
317 | "os": [
318 | "openbsd"
319 | ],
320 | "engines": {
321 | "node": ">=12"
322 | }
323 | },
324 | "node_modules/@esbuild/sunos-x64": {
325 | "version": "0.19.6",
326 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.6.tgz",
327 | "integrity": "sha512-M+XIAnBpaNvaVAhbe3uBXtgWyWynSdlww/JNZws0FlMPSBy+EpatPXNIlKAdtbFVII9OpX91ZfMb17TU3JKTBA==",
328 | "cpu": [
329 | "x64"
330 | ],
331 | "dev": true,
332 | "optional": true,
333 | "os": [
334 | "sunos"
335 | ],
336 | "engines": {
337 | "node": ">=12"
338 | }
339 | },
340 | "node_modules/@esbuild/win32-arm64": {
341 | "version": "0.19.6",
342 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.6.tgz",
343 | "integrity": "sha512-2DchFXn7vp/B6Tc2eKdTsLzE0ygqKkNUhUBCNtMx2Llk4POIVMUq5rUYjdcedFlGLeRe1uLCpVvCmE+G8XYybA==",
344 | "cpu": [
345 | "arm64"
346 | ],
347 | "dev": true,
348 | "optional": true,
349 | "os": [
350 | "win32"
351 | ],
352 | "engines": {
353 | "node": ">=12"
354 | }
355 | },
356 | "node_modules/@esbuild/win32-ia32": {
357 | "version": "0.19.6",
358 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.6.tgz",
359 | "integrity": "sha512-PBo/HPDQllyWdjwAVX+Gl2hH0dfBydL97BAH/grHKC8fubqp02aL4S63otZ25q3sBdINtOBbz1qTZQfXbP4VBg==",
360 | "cpu": [
361 | "ia32"
362 | ],
363 | "dev": true,
364 | "optional": true,
365 | "os": [
366 | "win32"
367 | ],
368 | "engines": {
369 | "node": ">=12"
370 | }
371 | },
372 | "node_modules/@esbuild/win32-x64": {
373 | "version": "0.19.6",
374 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.6.tgz",
375 | "integrity": "sha512-OE7yIdbDif2kKfrGa+V0vx/B3FJv2L4KnIiLlvtibPyO9UkgO3rzYE0HhpREo2vmJ1Ixq1zwm9/0er+3VOSZJA==",
376 | "cpu": [
377 | "x64"
378 | ],
379 | "dev": true,
380 | "optional": true,
381 | "os": [
382 | "win32"
383 | ],
384 | "engines": {
385 | "node": ">=12"
386 | }
387 | },
388 | "node_modules/@extism/js-pdk": {
389 | "resolved": "../../crates/core/src/prelude",
390 | "link": true
391 | },
392 | "node_modules/@types/prop-types": {
393 | "version": "15.7.12",
394 | "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
395 | "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==",
396 | "dev": true
397 | },
398 | "node_modules/@types/react": {
399 | "version": "18.3.1",
400 | "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.1.tgz",
401 | "integrity": "sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==",
402 | "dev": true,
403 | "dependencies": {
404 | "@types/prop-types": "*",
405 | "csstype": "^3.0.2"
406 | }
407 | },
408 | "node_modules/cross-env": {
409 | "version": "7.0.3",
410 | "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
411 | "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
412 | "dev": true,
413 | "dependencies": {
414 | "cross-spawn": "^7.0.1"
415 | },
416 | "bin": {
417 | "cross-env": "src/bin/cross-env.js",
418 | "cross-env-shell": "src/bin/cross-env-shell.js"
419 | },
420 | "engines": {
421 | "node": ">=10.14",
422 | "npm": ">=6",
423 | "yarn": ">=1"
424 | }
425 | },
426 | "node_modules/cross-spawn": {
427 | "version": "7.0.3",
428 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
429 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
430 | "dev": true,
431 | "dependencies": {
432 | "path-key": "^3.1.0",
433 | "shebang-command": "^2.0.0",
434 | "which": "^2.0.1"
435 | },
436 | "engines": {
437 | "node": ">= 8"
438 | }
439 | },
440 | "node_modules/csstype": {
441 | "version": "3.1.3",
442 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
443 | "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
444 | "dev": true
445 | },
446 | "node_modules/esbuild": {
447 | "version": "0.19.6",
448 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.6.tgz",
449 | "integrity": "sha512-Xl7dntjA2OEIvpr9j0DVxxnog2fyTGnyVoQXAMQI6eR3mf9zCQds7VIKUDCotDgE/p4ncTgeRqgX8t5d6oP4Gw==",
450 | "dev": true,
451 | "hasInstallScript": true,
452 | "bin": {
453 | "esbuild": "bin/esbuild"
454 | },
455 | "engines": {
456 | "node": ">=12"
457 | },
458 | "optionalDependencies": {
459 | "@esbuild/android-arm": "0.19.6",
460 | "@esbuild/android-arm64": "0.19.6",
461 | "@esbuild/android-x64": "0.19.6",
462 | "@esbuild/darwin-arm64": "0.19.6",
463 | "@esbuild/darwin-x64": "0.19.6",
464 | "@esbuild/freebsd-arm64": "0.19.6",
465 | "@esbuild/freebsd-x64": "0.19.6",
466 | "@esbuild/linux-arm": "0.19.6",
467 | "@esbuild/linux-arm64": "0.19.6",
468 | "@esbuild/linux-ia32": "0.19.6",
469 | "@esbuild/linux-loong64": "0.19.6",
470 | "@esbuild/linux-mips64el": "0.19.6",
471 | "@esbuild/linux-ppc64": "0.19.6",
472 | "@esbuild/linux-riscv64": "0.19.6",
473 | "@esbuild/linux-s390x": "0.19.6",
474 | "@esbuild/linux-x64": "0.19.6",
475 | "@esbuild/netbsd-x64": "0.19.6",
476 | "@esbuild/openbsd-x64": "0.19.6",
477 | "@esbuild/sunos-x64": "0.19.6",
478 | "@esbuild/win32-arm64": "0.19.6",
479 | "@esbuild/win32-ia32": "0.19.6",
480 | "@esbuild/win32-x64": "0.19.6"
481 | }
482 | },
483 | "node_modules/isexe": {
484 | "version": "2.0.0",
485 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
486 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
487 | "dev": true
488 | },
489 | "node_modules/js-tokens": {
490 | "version": "4.0.0",
491 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
492 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
493 | },
494 | "node_modules/loose-envify": {
495 | "version": "1.4.0",
496 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
497 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
498 | "dependencies": {
499 | "js-tokens": "^3.0.0 || ^4.0.0"
500 | },
501 | "bin": {
502 | "loose-envify": "cli.js"
503 | }
504 | },
505 | "node_modules/path-key": {
506 | "version": "3.1.1",
507 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
508 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
509 | "dev": true,
510 | "engines": {
511 | "node": ">=8"
512 | }
513 | },
514 | "node_modules/react": {
515 | "version": "18.3.1",
516 | "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
517 | "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
518 | "peer": true,
519 | "dependencies": {
520 | "loose-envify": "^1.1.0"
521 | },
522 | "engines": {
523 | "node": ">=0.10.0"
524 | }
525 | },
526 | "node_modules/react-dom": {
527 | "version": "18.3.1",
528 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
529 | "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
530 | "dependencies": {
531 | "loose-envify": "^1.1.0",
532 | "scheduler": "^0.23.2"
533 | },
534 | "peerDependencies": {
535 | "react": "^18.3.1"
536 | }
537 | },
538 | "node_modules/scheduler": {
539 | "version": "0.23.2",
540 | "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
541 | "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
542 | "dependencies": {
543 | "loose-envify": "^1.1.0"
544 | }
545 | },
546 | "node_modules/shebang-command": {
547 | "version": "2.0.0",
548 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
549 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
550 | "dev": true,
551 | "dependencies": {
552 | "shebang-regex": "^3.0.0"
553 | },
554 | "engines": {
555 | "node": ">=8"
556 | }
557 | },
558 | "node_modules/shebang-regex": {
559 | "version": "3.0.0",
560 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
561 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
562 | "dev": true,
563 | "engines": {
564 | "node": ">=8"
565 | }
566 | },
567 | "node_modules/typescript": {
568 | "version": "5.3.2",
569 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz",
570 | "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==",
571 | "dev": true,
572 | "bin": {
573 | "tsc": "bin/tsc",
574 | "tsserver": "bin/tsserver"
575 | },
576 | "engines": {
577 | "node": ">=14.17"
578 | }
579 | },
580 | "node_modules/which": {
581 | "version": "2.0.2",
582 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
583 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
584 | "dev": true,
585 | "dependencies": {
586 | "isexe": "^2.0.0"
587 | },
588 | "bin": {
589 | "node-which": "bin/node-which"
590 | },
591 | "engines": {
592 | "node": ">= 8"
593 | }
594 | }
595 | }
596 | }
597 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Extism JavaScript PDK
2 |
3 | 
4 | 
5 |
6 | This project contains a tool that can be used to create
7 | [Extism Plug-ins](https://extism.org/docs/concepts/plug-in) in JavaScript.
8 |
9 | ## Overview
10 |
11 | This PDK uses [QuickJS](https://bellard.org/quickjs/) and
12 | [wizer](https://github.com/bytecodealliance/wizer) to run javascript as an
13 | Extism Plug-in.
14 |
15 | This is essentially a fork of [Javy](https://github.com/bytecodealliance/javy)
16 | by Shopify. We may wish to collaborate and upstream some things to them. For the
17 | time being I built this up from scratch using some of their crates, namely
18 | quickjs-wasm-rs.
19 |
20 | > Warning: This is a very bare-bones runtime. It's only for running pure JS code
21 | > and it does not expose node APIs or the browser APIs. We have limited support
22 | > for some W3C APIs (e.g. we support `Text{Encoder,Decoder}` but not `fetch`),
23 | > but many modules you take from npm will not work out of the box. There is no
24 | > support for node APIs or anything that makes syscalls typically. You'll need
25 | > to polyfill any APIs with a pure JS implementation, which is often possible,
26 | > but some things, such as controlling sockets, are not possible. Feel free to
27 | > [file an issue](https://github.com/extism/js-pdk/issues/new) if you think an
28 | > API should be supported though.
29 |
30 | ## Install the compiler
31 |
32 | We release the compiler as native binaries you can download and run. Check the
33 | [releases](https://github.com/extism/js-pdk/releases) page for the latest.
34 |
35 | ## Install Script
36 |
37 | ### Linux, macOS
38 |
39 | ```bash
40 | curl -O https://raw.githubusercontent.com/extism/js-pdk/main/install.sh
41 | bash install.sh
42 | ```
43 |
44 | ### Windows
45 |
46 | > 7zip is required, you can find it [here](https://www.7-zip.org/).
47 |
48 | Open the Command Prompt as Administrator, then run :
49 |
50 | ```bash
51 | powershell Invoke-WebRequest -Uri https://raw.githubusercontent.com/extism/js-pdk/main/install-windows.ps1 -OutFile install-windows.ps1
52 | powershell -executionpolicy bypass -File .\install-windows.ps1
53 | ```
54 |
55 | This will install extism-js and binaryen dependency under `Program File` folder
56 | (i.e. C:\Program Files\Binaryen and C:\Program Files\Extism). You must add these
57 | paths to your PATH environment variable.
58 |
59 | ### Testing the Install
60 |
61 | > _Note_: [Binaryen](https://github.com/WebAssembly/binaryen), specifically the
62 | > `wasm-merge` and `wasm-opt` tools are required as a dependency. We will try to
63 | > package this up eventually but for now it must be reachable on your machine.
64 | > You can install on mac with `brew install binaryen` or see their
65 | > [releases page](https://github.com/WebAssembly/binaryen/releases).
66 |
67 | Then run command with `-h` to see the help:
68 |
69 | ```
70 | extism-js 1.1.1
71 | Extism JavaScript PDK Plugin Compiler
72 |
73 | USAGE:
74 | extism-js [FLAGS] [OPTIONS]
75 |
76 | FLAGS:
77 | -h, --help Prints help information
78 | --skip-opt Skip final optimization pass
79 | -V, --version Prints version information
80 |
81 | OPTIONS:
82 | -i [default: index.d.ts]
83 | -o [default: index.wasm]
84 |
85 | ARGS:
86 |
87 | ```
88 |
89 | > **Note**: If you are using mac, you may need to tell your security system this
90 | > unsigned binary is fine. If you think this is dangerous, or can't get it to
91 | > work, see the "compile from source" section below.
92 |
93 | ## Getting Started
94 |
95 | The goal of writing an
96 | [Extism plug-in](https://extism.org/docs/concepts/plug-in) is to compile your
97 | JavaScript code to a Wasm module with exported functions that the host
98 | application can invoke. The first thing you should understand is creating an
99 | export.
100 |
101 | ### Exports
102 |
103 | Let's write a simple program that exports a `greet` function which will take a
104 | name as a string and return a greeting string. Paste this into a file
105 | `plugin.js`:
106 |
107 | ```javascript
108 | function greet() {
109 | const name = Host.inputString();
110 | Host.outputString(`Hello, ${name}`);
111 | }
112 |
113 | module.exports = { greet };
114 | ```
115 |
116 | Some things to note about this code:
117 |
118 | 1. We can export functions by name using the normal `module.exports` object.
119 | This allows the host to invoke this function. Like a normal js module,
120 | functions cannot be seen from the outside without exporting them.
121 | 2. Currently, you must use
122 | [CJS Module syntax](https://nodejs.org/api/modules.html#modules-commonjs-modules)
123 | when not using a bundler. So the `export` keyword is not directly supported.
124 | See the [Using with a Bundler](#using-with-a-bundler) section for more.
125 | 3. In this PDK we code directly to the ABI. We get input from the Host using
126 | `Host.input*` functions and we return data back with the `Host.output*`
127 | functions.
128 |
129 | We must also describe the Wasm interface for our plug-in. We do this with a
130 | typescript module DTS file. Here is our `plugin.d.ts` file:
131 |
132 | ```typescript
133 | declare module "main" {
134 | // Extism exports take no params and return an I32
135 | export function greet(): I32;
136 | }
137 | ```
138 |
139 | Let's compile this to Wasm now using the `extism-js` tool:
140 |
141 | ```bash
142 | extism-js plugin.js -i plugin.d.ts -o plugin.wasm
143 | ```
144 |
145 | We can now test `plugin.wasm` using the
146 | [Extism CLI](https://github.com/extism/cli)'s `run` command:
147 |
148 | ```bash
149 | extism call plugin.wasm greet --input="Benjamin" --wasi
150 | # => Hello, Benjamin!
151 | ```
152 |
153 | > **Note**: Currently `wasi` must be provided for all JavaScript plug-ins even
154 | > if they don't need system access, however we're looking at how to make this
155 | > optional.
156 |
157 | > **Note**: We also have a web-based, plug-in tester called the
158 | > [Extism Playground](https://playground.extism.org/)
159 |
160 | ### More Exports: Error Handling
161 |
162 | We catch any exceptions thrown and return them as errors to the host. Suppose we
163 | want to re-write our greeting module to never greet Benjamins:
164 |
165 | ```javascript
166 | function greet() {
167 | const name = Host.inputString();
168 | if (name === "Benjamin") {
169 | throw new Error("Sorry, we don't greet Benjamins!");
170 | }
171 | Host.outputString(`Hello, ${name}!`);
172 | }
173 |
174 | module.exports = { greet };
175 | ```
176 |
177 | Now compile and run:
178 |
179 | ```bash
180 | extism-js plugin.js -i plugin.d.ts -o plugin.wasm
181 | extism call plugin.wasm greet --input="Benjamin" --wasi
182 | # => Error: Uncaught Error: Sorry, we don't greet Benjamins!
183 | # => at greet (script.js:4)
184 | # => at (script.js)
185 | echo $? # print last status code
186 | # => 1
187 | extism call plugin.wasm greet --input="Zach" --wasi
188 | # => Hello, Zach!
189 | echo $?
190 | # => 0
191 | ```
192 |
193 | ### JSON
194 |
195 | If you want to handle more complex types, the plug-in can input and output bytes
196 | with `Host.inputBytes` and `Host.outputBytes` respectively. Those bytes can
197 | represent any complex type. A common format to use is JSON:
198 |
199 | ```javascript
200 | function sum() {
201 | const params = JSON.parse(Host.inputString());
202 | Host.outputString(JSON.stringify({ sum: params.a + params.b }));
203 | }
204 | ```
205 |
206 | ```bash
207 | extism call plugin.wasm sum --input='{"a": 20, "b": 21}' --wasi
208 | # => {"sum":41}
209 | ```
210 |
211 | ### Configs
212 |
213 | Configs are key-value pairs that can be passed in by the host when creating a
214 | plug-in. These can be useful to statically configure the plug-in with some data
215 | that exists across every function call. Here is a trivial example using
216 | `Config.get`:
217 |
218 | ```javascript
219 | function greet() {
220 | const user = Config.get("user");
221 | Host.outputString(`Hello, ${user}!`);
222 | }
223 |
224 | module.exports = { greet };
225 | ```
226 |
227 | To test it, the [Extism CLI](https://github.com/extism/cli) has a `--config`
228 | option that lets you pass in `key=value` pairs:
229 |
230 | ```bash
231 | extism call plugin.wasm greet --config user=Benjamin --wasi
232 | # => Hello, Benjamin!
233 | ```
234 |
235 | ### Variables
236 |
237 | Variables are another key-value mechanism but it's a mutable data store that
238 | will persist across function calls. These variables will persist as long as the
239 | host has loaded and not freed the plug-in. You can use `Var.getBytes`,
240 | `Var.getString`, and `Var.set` to manipulate vars:
241 |
242 | ```javascript
243 | function count() {
244 | let count = Var.getString("count") || "0";
245 | count = parseInt(count, 10);
246 | count += 1;
247 | Var.set("count", count.toString());
248 | Host.outputString(count.toString());
249 | }
250 |
251 | module.exports = { count };
252 | ```
253 |
254 | ### Logging
255 |
256 | There are several functions that can be used for logging:
257 |
258 | ```javascript
259 | function logStuff() {
260 | console.info("Info");
261 | console.debug("Debug");
262 | console.error("Error");
263 | console.warn("Warning");
264 | console.log("Log"); // Alias for console.info
265 | }
266 |
267 | module.exports = { logStuff };
268 | ```
269 |
270 | Running it, you need to pass a log-level flag:
271 |
272 | ```
273 | extism call plugin.wasm logStuff --wasi --log-level=info
274 | # => 2023/10/17 14:25:00 Hello, World!
275 | ```
276 |
277 | ### HTTP
278 |
279 | HTTP calls can be made using the synchronous API `Http.request`:
280 |
281 | ```javascript
282 | function callHttp() {
283 | const request = {
284 | method: "GET",
285 | url: "https://jsonplaceholder.typicode.com/todos/1",
286 | };
287 | const response = Http.request(request);
288 | if (response.status != 200) {
289 | throw new Error(`Got non 200 response ${response.status}`);
290 | }
291 | Host.outputString(response.body);
292 | }
293 |
294 | module.exports = { callHttp };
295 | ```
296 |
297 | ### Host Functions
298 |
299 | Until the js-pdk hits 1.0, we may make changes to this API. To use host
300 | functions you need to declare a TypeScript interface `extism:host/user`:
301 |
302 | ```typescript
303 | declare module "main" {
304 | export function greet(): I32;
305 | }
306 |
307 | declare module "extism:host" {
308 | interface user {
309 | myHostFunction1(ptr: I64): I64;
310 | myHostFunction2(ptr: I64): I64;
311 | }
312 | }
313 | ```
314 |
315 | **Note:** These functions may only use `I64` arguments, up to 5 arguments.
316 |
317 | To use these you need to use `Host.getFunctions()`:
318 |
319 | ```typescript
320 | const { myHostFunction1, myHostFunction2 } = Host.getFunctions();
321 | ```
322 |
323 | Calling them is a similar process to other PDKs. You need to manage the memory
324 | with the Memory object and pass across an offset as the `I64` ptr. Using the
325 | return value means dereferencing the returned `I64` ptr from Memory.
326 |
327 | ```typescript
328 | function greet() {
329 | let msg = "Hello from js 1";
330 | let mem = Memory.fromString(msg);
331 | let offset = myHostFunction1(mem.offset);
332 | let response = Memory.find(offset).readString();
333 | if (response != "myHostFunction1: " + msg) {
334 | throw Error(`wrong message came back from myHostFunction1: ${response}`);
335 | }
336 |
337 | msg = { hello: "world!" };
338 | mem = Memory.fromJsonObject(msg);
339 | offset = myHostFunction2(mem.offset);
340 | response = Memory.find(offset).readJsonObject();
341 | if (response.hello != "myHostFunction2") {
342 | throw Error(`wrong message came back from myHostFunction2: ${response}`);
343 | }
344 |
345 | Host.outputString(`Hello, World!`);
346 | }
347 |
348 | module.exports = { greet };
349 | ```
350 |
351 | **IMPORTANT:** Currently, a limitation in the js-pdk is that host functions may
352 | only have up to 5 arguments.
353 |
354 | ## Using with a bundler
355 |
356 | The compiler cli and core engine can now run bundled code. You will want to use
357 | a bundler if you want to want to or include modules from NPM, or write the
358 | plugin in Typescript, for example.
359 |
360 | There are 2 primary constraints to using a bundler:
361 |
362 | 1. Your compiled output must be CJS format, not ESM
363 | 2. You must target es2020 or lower
364 |
365 | ### Using with esbuild
366 |
367 | The easiest way to set this up would be to use esbuild. The following is a
368 | quickstart guide to setting up a project:
369 |
370 | ```bash
371 | # Make a new JS project
372 | mkdir extism-plugin
373 | cd extism-plugin
374 | npm init -y
375 | npm install esbuild @extism/js-pdk --save-dev
376 | mkdir src
377 | mkdir dist
378 | ```
379 |
380 | Optionally add a `jsconfig.json` or `tsconfig.json` to improve intellisense:
381 |
382 | ```jsonc
383 | {
384 | "compilerOptions": {
385 | "lib": [], // this ensures unsupported globals aren't suggested
386 | "types": ["@extism/js-pdk"], // while this makes the IDE aware of the ones that are
387 | "noEmit": true // this is only relevant for tsconfig.json
388 | },
389 | "include": ["src/**/*"]
390 | }
391 | ```
392 |
393 | Add `esbuild.js`:
394 |
395 | ```js
396 | const esbuild = require("esbuild");
397 | // include this if you need some node support:
398 | // npm i @esbuild-plugins/node-modules-polyfill --save-dev
399 | // const { NodeModulesPolyfillPlugin } = require('@esbuild-plugins/node-modules-polyfill')
400 |
401 | esbuild
402 | .build({
403 | // supports other types like js or ts
404 | entryPoints: ["src/index.js"],
405 | outdir: "dist",
406 | bundle: true,
407 | sourcemap: true,
408 | //plugins: [NodeModulesPolyfillPlugin()], // include this if you need some node support
409 | minify: false, // might want to use true for production build
410 | format: "cjs", // needs to be CJS for now
411 | target: ["es2020"], // don't go over es2020 because quickjs doesn't support it
412 | });
413 | ```
414 |
415 | Add a `build` script to your `package.json`:
416 |
417 | ```json
418 | {
419 | "name": "extism-plugin",
420 | // ...
421 | "scripts": {
422 | // ...
423 | "build": "node esbuild.js && extism-js dist/index.js -i src/index.d.ts -o dist/plugin.wasm"
424 | }
425 | // ...
426 | }
427 | ```
428 |
429 | Let's import a module from NPM:
430 |
431 | ```bash
432 | npm install --save fastest-levenshtein
433 | ```
434 |
435 | Now make some code in `src/index.js`. You can use `import` to load node_modules:
436 |
437 | > **Note**: This module uses the ESM Module syntax. The bundler will transform
438 | > all the code to CJS for us
439 |
440 | ```js
441 | import { closest, distance } from "fastest-levenshtein";
442 |
443 | // this function is private to the module
444 | function privateFunc() {
445 | return "world";
446 | }
447 |
448 | // use any export syntax to export a function be callable by the extism host
449 | export function get_closest() {
450 | let input = Host.inputString();
451 | let result = closest(input, ["slow", "faster", "fastest"]);
452 | Host.outputString(result + " " + privateFunc());
453 | }
454 | ```
455 |
456 | And a d.ts file for it at `src/index.d.ts`:
457 |
458 | ```typescript
459 | declare module "main" {
460 | // Extism exports take no params and return an I32
461 | export function get_closest(): I32;
462 | }
463 | ```
464 |
465 | ```bash
466 | # Run the build script and the plugin will be compiled to dist/plugin.wasm
467 | npm run build
468 | # You can now call from the extism cli or a host SDK
469 | extism call dist/plugin.wasm get_closest --input="fest" --wasi
470 | # => faster World
471 | ```
472 |
473 | ## Using with React and JSX / TSX
474 |
475 | Oftentimes people want their JS plug-ins to control or create views. React and
476 | JSX are a great way to do this. Here is the simplest example. Let's just render
477 | a simple view in a typescript plugin.
478 |
479 | First declare a `render` export:
480 |
481 | ```typescript
482 | declare module "main" {
483 | export function render(): I32;
484 | }
485 | ```
486 |
487 | Now install the deps:
488 |
489 | ```bash
490 | npm install react-dom --save
491 | npm install @types/react --save-dev
492 | ```
493 |
494 | Now we can make an index.tsx:
495 |
496 | ```typescript
497 | import { renderToString } from "react-dom/server";
498 | import React from "react";
499 |
500 | interface AppProps {
501 | name: string;
502 | }
503 |
504 | function App(props: AppProps) {
505 | return (
506 | <>
507 | Hello ${props.name}!
508 | >
509 | );
510 | }
511 |
512 | export function render() {
513 | const props = JSON.parse(Host.inputString()) as AppProps;
514 | const app = ;
515 | Host.outputString(renderToString(app));
516 | }
517 | ```
518 |
519 | To see a more complex example of how you might build something real, see
520 | [examples/react](./examples/react/)
521 |
522 | ## Generating Bindings
523 |
524 | It's often very useful to define a schema to describe the function signatures
525 | and types you want to use between Extism SDK and PDK languages.
526 |
527 | [XTP Bindgen](https://github.com/dylibso/xtp-bindgen) is an open source
528 | framework to generate PDK bindings for Extism plug-ins. It's used by the
529 | [XTP Platform](https://www.getxtp.com/), but can be used outside of the platform
530 | to define any Extism compatible plug-in system.
531 |
532 | ### 1. Install the `xtp` CLI.
533 |
534 | See installation instructions
535 | [here](https://docs.xtp.dylibso.com/docs/cli#installation).
536 |
537 | ### 2. Create a schema using our OpenAPI-inspired IDL:
538 |
539 | ```yaml
540 | version: v1-draft
541 | exports:
542 | CountVowels:
543 | input:
544 | type: string
545 | contentType: text/plain; charset=utf-8
546 | output:
547 | $ref: "#/components/schemas/VowelReport"
548 | contentType: application/json
549 | # components.schemas defined in example-schema.yaml...
550 | ```
551 |
552 | > See an example in [example-schema.yaml](./example-schema.yaml), or a full
553 | > "kitchen sink" example on
554 | > [the docs page](https://docs.xtp.dylibso.com/docs/concepts/xtp-schema/).
555 |
556 | ### 3. Generate bindings to use from your plugins:
557 |
558 | ```
559 | xtp plugin init --schema-file ./example-schema.yaml
560 | > 1. TypeScript
561 | 2. Go
562 | 3. Rust
563 | 4. Python
564 | 5. C#
565 | 6. Zig
566 | 7. C++
567 | 8. GitHub Template
568 | 9. Local Template
569 | ```
570 |
571 | This will create an entire boilerplate plugin project for you to get started
572 | with:
573 |
574 | ```typescript
575 | /**
576 | * @returns {VowelReport} The result of counting vowels on the Vowels input.
577 | */
578 | export function CountVowelsImpl(input: string): VowelReport {
579 | // TODO: fill out your implementation here
580 | throw new Error("Function not implemented.");
581 | }
582 | ```
583 |
584 | Implement the empty function(s), and run `xtp plugin build` to compile your
585 | plugin.
586 |
587 | > For more information about XTP Bindgen, see the
588 | > [dylibso/xtp-bindgen](https://github.com/dylibso/xtp-bindgen) repository and
589 | > the official
590 | > [XTP Schema documentation](https://docs.xtp.dylibso.com/docs/concepts/xtp-schema).
591 |
592 | ## Compiling the compiler from source
593 |
594 | ### Prerequisites
595 |
596 | Before compiling the compiler, you need to install prerequisites.
597 |
598 | 1. Install Rust using [rustup](https://rustup.rs)
599 | 2. Install the WASI target platform via
600 | `rustup target add --toolchain stable wasm32-wasip1`
601 | 3. Install the wasi sdk using the makefile command: `make download-wasi-sdk`
602 | 4. Install [CMake](https://cmake.org/install/) (on macOS with homebrew,
603 | `brew install cmake`)
604 | 5. Install [Binaryen](https://github.com/WebAssembly/binaryen/) and add it's
605 | install location to your PATH (only wasm-opt is required for build process)
606 | 6. Install [7zip](https://www.7-zip.org/)(only for Windows)
607 |
608 | ### Compiling from source
609 |
610 | Run make to compile the core crate (the engine) and the cli:
611 |
612 | ```
613 | make
614 | ```
615 |
616 | To test the built compiler (ensure you have Extism installed):
617 |
618 | ```bash
619 | ./target/release/extism-js bundle.js -i bundle.d.ts -o out.wasm
620 | extism call out.wasm count_vowels --wasi --input='Hello World Test!'
621 | # => "{\"count\":4}"
622 | ```
623 |
624 | ## How it works
625 |
626 | This works a little differently than other PDKs. You cannot compile JS to Wasm
627 | because it doesn't have an appropriate type system to do this. Something like
628 | [Assemblyscript](https://github.com/extism/assemblyscript-pdk) is better suited
629 | for this. Instead, we have compiled QuickJS to Wasm. The `extism-js` command we
630 | have provided here is a little compiler / wrapper that does a series of things
631 | for you:
632 |
633 | 1. It loads an "engine" Wasm program containing the QuickJS runtime
634 | 2. It initializes a QuickJS context
635 | 3. It loads your js source code into memory
636 | 4. It parses the js source code for exports and generates 1-to-1 proxy export
637 | functions in Wasm
638 | 5. It freezes and emits the machine state as a new Wasm file at this
639 | post-initialized point in time
640 |
641 | This new Wasm file can be used just like any other Extism plugin.
642 |
643 | ## Why not use Javy?
644 |
645 | Javy, and many other high level language Wasm tools, assume use of the _command
646 | pattern_. This is when the Wasm module only exports a main function and
647 | communicates with the host through stdin and stdout. With Extism, we have more
648 | of a shared library interface. The module exposes multiple entry points through
649 | exported functions. Furthermore, Javy has many Javy and Shopify specific things
650 | it's doing that we will not need. However, the core idea is the same, and we can
651 | possibly contribute by adding support to Javy for non-command-pattern modules.
652 | Then separating the Extism PDK specific stuff into another repo.
653 |
--------------------------------------------------------------------------------