{"id":16487038,"url":"https://github.com/gmlewis/moonbit-pdk","last_synced_at":"2025-07-14T23:04:46.499Z","repository":{"id":242163522,"uuid":"808855725","full_name":"gmlewis/moonbit-pdk","owner":"gmlewis","description":"Extism PDK for the MoonBit programming language.","archived":false,"fork":false,"pushed_at":"2025-06-18T23:40:28.000Z","size":1418,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-07-04T08:47:18.243Z","etag":null,"topics":["extism","moonbit","pdk"],"latest_commit_sha":null,"homepage":"https://mooncakes.io/docs/#/gmlewis/moonbit-pdk/","language":"MoonBit","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gmlewis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-06-01T01:44:27.000Z","updated_at":"2025-06-18T23:39:51.000Z","dependencies_parsed_at":"2024-06-01T02:51:28.521Z","dependency_job_id":"83b0990d-5c7d-43e5-9d02-7b31e68c7a68","html_url":"https://github.com/gmlewis/moonbit-pdk","commit_stats":{"total_commits":85,"total_committers":1,"mean_commits":85.0,"dds":0.0,"last_synced_commit":"f2ca98711cbe57a7e9d3bbdb41273147eeeecd08"},"previous_names":["gmlewis/moonbit-pdk"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/gmlewis/moonbit-pdk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gmlewis%2Fmoonbit-pdk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gmlewis%2Fmoonbit-pdk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gmlewis%2Fmoonbit-pdk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gmlewis%2Fmoonbit-pdk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gmlewis","download_url":"https://codeload.github.com/gmlewis/moonbit-pdk/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gmlewis%2Fmoonbit-pdk/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265365543,"owners_count":23753349,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["extism","moonbit","pdk"],"created_at":"2024-10-11T13:32:04.695Z","updated_at":"2025-07-14T23:04:46.482Z","avatar_url":"https://github.com/gmlewis.png","language":"MoonBit","funding_links":[],"categories":["Examples and Projects"],"sub_categories":[],"readme":"# gmlewis/moonbit-pdk\n[![check](https://github.com/gmlewis/moonbit-pdk/actions/workflows/check.yml/badge.svg)](https://github.com/gmlewis/moonbit-pdk/actions/workflows/check.yml)\n\nThis is an [Extism PDK] that can be used to write [Extism Plug-ins] using the [MoonBit] programming language.\n\n[Extism PDK]: https://extism.org/docs/concepts/pdk\n[Extism Plug-ins]: https://extism.org/docs/concepts/plug-in\n[MoonBit]: https://www.moonbitlang.com/\n\n## Install\n\nAdd the library to your project as a dependency with the `moon` tool:\n\n```bash\nmoon add gmlewis/moonbit-pdk\n```\n\n## Reference Documentation\n\nYou can find the reference documentation for this library on [mooncakes.io]:\n\n* [gmlewis/moonbit-pdk overview and status]\n* [gmlewis/moonbit-pdk/pdk/config]\n* [gmlewis/moonbit-pdk/pdk/host]\n* [gmlewis/moonbit-pdk/pdk/http]\n* [gmlewis/moonbit-pdk/pdk/var]\n\n[mooncakes.io]: https://mooncakes.io\n[gmlewis/moonbit-pdk overview and status]: https://mooncakes.io/docs/#/gmlewis/moonbit-pdk/\n[gmlewis/moonbit-pdk/pdk/config]: https://mooncakes.io/docs/#/gmlewis/moonbit-pdk/pdk/config/members\n[gmlewis/moonbit-pdk/pdk/host]: https://mooncakes.io/docs/#/gmlewis/moonbit-pdk/pdk/host/members\n[gmlewis/moonbit-pdk/pdk/http]: https://mooncakes.io/docs/#/gmlewis/moonbit-pdk/pdk/http/members\n[gmlewis/moonbit-pdk/pdk/var]: https://mooncakes.io/docs/#/gmlewis/moonbit-pdk/pdk/var/members\n\nExamples can also be found there:\n\n* [gmlewis/moonbit-pdk/examples/add]\n* [gmlewis/moonbit-pdk/examples/arrays]\n* [gmlewis/moonbit-pdk/examples/count-vowels]\n* [gmlewis/moonbit-pdk/examples/greet]\n* [gmlewis/moonbit-pdk/examples/http-get]\n* [gmlewis/moonbit-pdk/examples/kitchen-sink]\n\n[gmlewis/moonbit-pdk/examples/add]: https://mooncakes.io/docs/#/gmlewis/moonbit-pdk/examples/add/members\n[gmlewis/moonbit-pdk/examples/arrays]: https://mooncakes.io/docs/#/gmlewis/moonbit-pdk/examples/arrays/members\n[gmlewis/moonbit-pdk/examples/count-vowels]: https://mooncakes.io/docs/#/gmlewis/moonbit-pdk/examples/count-vowels/members\n[gmlewis/moonbit-pdk/examples/greet]: https://mooncakes.io/docs/#/gmlewis/moonbit-pdk/examples/greet/members\n[gmlewis/moonbit-pdk/examples/http-get]: https://mooncakes.io/docs/#/gmlewis/moonbit-pdk/examples/http-get/members\n[gmlewis/moonbit-pdk/examples/kitchen-sink]: https://mooncakes.io/docs/#/gmlewis/moonbit-pdk/examples/kitchen-sink/members\n\n## Getting Started\n\nThe goal of writing an [Extism plug-in](https://extism.org/docs/concepts/plug-in)\nis to compile your MoonBit code to a Wasm module with exported functions that the\nhost application can invoke. The first thing you should understand is creating an export.\nLet's write a simple program that exports a `greet` function which will take\na name as a string and return a greeting string.\n\nFirst, install the `moon` CLI tool:\n\nSee https://www.moonbitlang.com/download/ for instructions for your platform.\n\nCreate a new MoonBit project directory using the `moon` tool and initialize\nthe project:\n\n```bash\nmoon new greet\ncd greet\n```\n\nNext, add this Extism PDK to the project and remove the default \"lib\" example:\n\n```bash\nmoon add gmlewis/moonbit-pdk\nrm -rf lib\n```\n\nNow paste this into your `main/main.mbt` file:\n\n```rust\npub fn greet() -\u003e Int {\n  let name = @host.input_string()\n  let greeting = \"Hello, \\{name}!\"\n  @host.output_string(greeting)\n  0 // success\n}\n\nfn main {\n\n}\n```\n\nThen paste this into your `main/moon.pkg.json` file to export the `greet` function\nand include the `@host` import into your plugin:\n\n```json\n{\n  \"import\": [\n    \"gmlewis/moonbit-pdk/pdk/host\"\n  ],\n  \"link\": {\n    \"wasm\": {\n      \"exports\": [\n        \"greet\"\n      ],\n      \"export-memory-name\": \"memory\"\n    }\n  }\n}\n```\n\nSome things to note about this code:\n\n1. The `moon.pkg.json` file is required. This marks the greet function as an export with the name `greet` that can be called by the host.\n2. We need a `main` but it is unused.\n3. Exports in the MoonBit PDK are coded to the raw ABI. You get parameters from the host by calling [`@host.input*` functions](https://mooncakes.io/docs/#/gmlewis/moonbit-pdk/pdk/host/members?id=input) and you send return values back with the [`@host.output*` functions](https://mooncakes.io/docs/#/gmlewis/moonbit-pdk/pdk/host/members?id=output).\n4. An Extism export expects an i32 (a MoonBit `Int`) return code. `0` is success and `1` (or any other value) is a failure.\n\nFinally, compile this with the command:\n\n```bash\nmoon build --target wasm\n```\n\nWe can now test `plugin.wasm` using the [Extism CLI](https://github.com/extism/cli)'s `run`\ncommand:\n\n```bash\nextism call target/wasm/release/build/main/main.wasm greet --input \"Benjamin\" --wasi\n# =\u003e Hello, Benjamin!\n```\n\n\u003e **Note**: We also have a web-based, plug-in tester called the [Extism Playground](https://playground.extism.org/)\n\n### More Exports: Error Handling\n\nSuppose we want to re-write our greeting module to never greet Benjamins.\nWe can use [`@host.set_error`](https://mooncakes.io/docs/#/gmlewis/moonbit-pdk/pdk/host/members?id=set_error):\n\n```rust\npub fn greet() -\u003e Int {\n  let name = @host.input_string()\n  if name == \"Benjamin\" {\n    @host.set_error(\"Sorry, we don't greet Benjamins!\")\n    return 1 // failure\n  }\n  let greeting = \"Hello, \\{name}!\"\n  @host.output_string(greeting)\n  0 // success\n}\n```\n\nNow when we try again:\n\n```bash\nmoon build --target wasm\nextism call target/wasm/release/build/main/main.wasm greet --input \"Benjamin\" --wasi\n# =\u003e Error: Sorry, we don't greet Benjamins!\necho $? # print last status code\n# =\u003e 1\nextism call target/wasm/release/build/main/main.wasm greet --input \"Zach\" --wasi\n# =\u003e Hello, Zach!\necho $?\n# =\u003e 0\n```\n\n### JSON\n\nExtism export functions simply take bytes in and bytes out. Those can be whatever you want them to be.\nA common way to get more complex types to and from the host is with JSON:\n(MoonBit currently requires a bit of boilerplate to handle JSON I/O but\nhopefully this situation will improve as the standard library is fleshed out.)\n\n```rust\nstruct Add {\n  a : Int\n  b : Int\n}\n\npub fn Add::from_json(value : Json) -\u003e Add? {\n  // From: https://github.com/moonbitlang/core/issues/892#issuecomment-2306068783\n  match value {\n    { \"a\": Number(a), \"b\": Number(b) } =\u003e Some({ a: a.to_int(), b: b.to_int() })\n    _ =\u003e None\n  }\n}\n\ntype! ParseError String derive(Show)\n\npub fn Add::parse(s : String) -\u003e Add!ParseError {\n  match @json.parse?(s) {\n    Ok(jv) =\u003e\n      match Add::from_json(jv) {\n        Some(value) =\u003e value\n        None =\u003e raise ParseError(\"unable to parse Add \\{s}\")\n      }\n    Err(e) =\u003e raise ParseError(\"unable to parse Add \\{s}: \\{e}\")\n  }\n}\n\nstruct Sum {\n  sum : Int\n} derive(ToJson)\n\npub fn add() -\u003e Int {\n  let input = @host.input_string()\n  let params = try {\n    Add::parse!(input)\n  } catch {\n    ParseError(e) =\u003e {\n      @host.set_error(e)\n      return 1\n    }\n  }\n  //\n  let sum = { sum: params.a + params.b }\n  let json_value = sum.to_json()\n  @host.output_json_value(json_value)\n  0 // success\n}\n```\n\n\nExport your `add` function in `main/moon.pkg.json`:\n\n```json\n{\n  \"import\": [\n    \"gmlewis/moonbit-pdk/pdk/host\"\n  ],\n  \"link\": {\n    \"wasm\": {\n      \"exports\": [\n        \"add\"\n      ],\n      \"export-memory-name\": \"memory\"\n    }\n  }\n}\n```\n\nThen compile and run:\n\n```bash\nmoon build --target wasm\nextism call plugin.wasm add --input='{\"a\": 20, \"b\": 21}' --wasi\n# =\u003e {\"sum\":41}\n```\n\n## Configs\n\nConfigs are key-value pairs that can be passed in by the host when creating a plug-in.\nThese can be useful to statically configure the plug-in with some data that exists\nacross every function call.\n\nHere is a trivial example using [`config.get`](https://mooncakes.io/docs/#/gmlewis/moonbit-pdk/pdk/config/members?id=get):\n\n```rust\npub fn greet() -\u003e Int {\n  let user = match @config.get(\"user\") {\n    Some(user) =\u003e user\n    None =\u003e {\n      @host.set_error(\"This plug-in requires a 'user' key in the config\")\n      return 1 // failure\n    }\n  }\n  let greeting = \"Hello, \\{user}!\"\n  @host.output_string(greeting)\n  0 // success\n}\n```\n\nRemember to import the `config` and `host` packages in `main/moon.pkg.json` and\nexport your function:\n\n```json\n{\n  \"import\": [\n    \"gmlewis/moonbit-pdk/pdk/config\",\n    \"gmlewis/moonbit-pdk/pdk/host\"\n  ],\n  \"link\": {\n    \"wasm\": {\n      \"exports\": [\n        \"greet\"\n      ],\n      \"export-memory-name\": \"memory\"\n    }\n  }\n}\n```\n\nTo test it, the [Extism CLI](https://github.com/extism/cli) has a `--config` option that lets you pass in `key=value` pairs:\n\n```bash\nmoon build --target wasm\nextism call target/wasm/release/build/main/main.wasm greet --config user=Benjamin\n# =\u003e Hello, Benjamin!\nextism call target/wasm/release/build/main/main.wasm greet\n# =\u003e Error: This plug-in requires a 'user' key in the config\n```\n\n## Variables\n\nVariables are another key-value mechanism but are a mutable data store that\nwill persist across function calls. These variables will persist as long as the\nhost has loaded and not freed the plug-in.\n\n```rust\npub fn count() -\u003e Int {\n  let mut count = match @var.get_int(\"count\") {\n    Some(v) =\u003e v\n    None =\u003e 0\n  }\n  count = count + 1\n  @var.set_int(\"count\", count)\n  let s = count.to_string()\n  @host.output_string(s)\n  0 // success\n}\n```\n\n\u003e **Note**: Use the untyped variant [`@var.set_bytes`](https://mooncakes.io/docs/#/gmlewis/moonbit-pdk/pdk/var/members?id=set_bytes)\n\u003e to handle your own types.\n\nRemember to import the `host` and `var` packages in `main/moon.pkg.json` and\nexport your function:\n\n```json\n{\n  \"import\": [\n    \"gmlewis/moonbit-pdk/pdk/host\",\n    \"gmlewis/moonbit-pdk/pdk/var\"\n  ],\n  \"link\": {\n    \"wasm\": {\n      \"exports\": [\n        \"count\"\n      ],\n      \"export-memory-name\": \"memory\"\n    }\n  }\n}\n```\n\n## Logging\n\nBecause Wasm modules by default do not have access to the system, printing to\nstdout won't work (unless you use WASI). Extism provides simple\n[logging functions](https://mooncakes.io/docs/#/gmlewis/moonbit-pdk/pdk/host/members?id=log_debug_str)\nthat allow you to use the host application to log without having to give the\nplug-in permission to make syscalls.\n\n```rust\npub fn log_stuff() -\u003e Int {\n  @host.log_info_str(\"An info log!\")\n  @host.log_debug_str(\"A debug log!\")\n  @host.log_warn_str(\"A warn log!\")\n  @host.log_error_str(\"An error log!\")\n  0 // success\n}\n```\n\nFrom [Extism CLI](https://github.com/extism/cli):\n\n```bash\nmoon build --target wasm\nextism call target/wasm/release/build/main/main.wasm log_stuff --wasi --log-level=trace\n# =\u003e 2024/07/09 11:37:30 No runtime detected\n# =\u003e 2024/07/09 11:37:30 Calling function : log_stuff\n# =\u003e 2024/07/09 11:37:30 An info log!\n# =\u003e 2024/07/09 11:37:30 A debug log!\n# =\u003e 2024/07/09 11:37:30 A warn log!\n# =\u003e 2024/07/09 11:37:30 An error log!\n```\n\n\u003e *Note*: From the CLI you need to pass a level with `--log-level`.\n\u003e If you are running the plug-in in your own host using one of our SDKs, you need\n\u003e to make sure that you call `set_log_file` to `\"stdout\"` or some file location.\n\n## HTTP\n\nSometimes it is useful to let a plug-in [make HTTP calls](https://mooncakes.io/docs/#/gmlewis/moonbit-pdk/pdk/http/members?id=send).\n[See this example](examples/http-get/http-get.mbt).\n\n```rust\npub fn http_get() -\u003e Int {\n  // create an HTTP Request (without relying on WASI), set headers as needed\n  let req = @http.new_request(\n    @http.Method::GET,\n    \"https://jsonplaceholder.typicode.com/todos/1\",\n  )\n  req.header.set(\"some-name\", \"some-value\")\n  req.header.set(\"another\", \"again\")\n  // send the request, get response back\n  let res = req.send()\n\n  // zero-copy send output to host\n  res.output()\n  0 // success\n}\n```\n\nBy default, Extism modules cannot make HTTP requests unless you specify which\nhosts it can connect to. You can use `--alow-host` in the Extism CLI to set this:\n\n```bash\nextism call \\\n    target/wasm/release/build/examples/http-get/http-get.wasm \\\n    http_get \\\n    --wasi \\\n    --allow-host='*.typicode.com'\n# =\u003e {\n# =\u003e   \"userId\": 1,\n# =\u003e   \"id\": 1,\n# =\u003e   \"title\": \"delectus aut autem\",\n# =\u003e   \"completed\": false\n# =\u003e }\n```\n\n## Imports (Host Functions)\n\nLike any other code module, Wasm not only lets you export functions to the outside world, you can\nimport them too. Host Functions allow a plug-in to import functions defined in the host. For example,\nif your host application is written in Python, it can pass a Python function down to your MoonBit plug-in\nwhere you can invoke it.\n\nThis topic can get fairly complicated and we have not yet fully abstracted the Wasm knowledge you need\nto do this correctly. So we recommend reading our [concept doc on Host Functions](https://extism.org/docs/concepts/host-functions)\nbefore you get started.\n\n### A Simple Example\n\nHost functions have a similar interface as exports. You just need to declare them\nas external in your `main.mbt`. You only declare the interface as it is the host's\nresponsibility to provide the implementation:\n\n```rust\npub fn a_python_func(offset : Int64) -\u003e Int64 = \"extism:host/user\" \"a_python_func\"\n```\n\nWe should be able to call this function as a normal Go function. Note that we need to manually handle the pointer casting:\n\n```rust\npub fn hello_from_python() -\u003e Int {\n  let msg = \"An argument to send to Python\"\n  let mem = @host.allocate_string(msg)\n  let ptr = a_python_func(mem.offset)\n  mem.free()\n  let rmem = @host.find_memory(ptr)\n  let response = rmem.to_string()\n  @host.output_string(response)\n  return 0\n}\n```\n\n### Testing it out\n\nWe can't really test this from the Extism CLI as something must provide the implementation. So let's\nwrite out the Python side here. Check out the [docs for Host SDKs](https://extism.org/docs/concepts/host-sdk)\nto implement a host function in a language of your choice.\n\n```python\nfrom extism import host_fn, Plugin\n\n@host_fn()\ndef a_python_func(input: str) -\u003e str:\n    # just printing this out to prove we're in Python land\n    print(\"Hello from Python!\")\n\n    # let's just add \"!\" to the input string\n    # but you could imagine here we could add some\n    # applicaiton code like query or manipulate the database\n    # or our application APIs\n    return input + \"!\"\n```\n\nNow when we load the plug-in we pass the host function:\n\n```python\nmanifest = {\"wasm\": [{\"path\": \"target/wasm/release/build/main/main.wasm\"}]}\nplugin = Plugin(manifest, functions=[a_python_func], wasi=True)\nresult = plugin.call('hello_from_python', b'').decode('utf-8')\nprint(result)\n```\n\n```bash\nmoon build --target wasm\npython3 -m pip install extism\npython3 app.py\n# =\u003e Hello from Python!\n# =\u003e An argument to send to Python!\n```\n\n\u003e **Note**: This fails on my Mac M2 Max with some weird system error\n\u003e but works great on my Linux Mint Cinnamon box.\n\n## For PDK Devs: Building the PDK locally\n\nBefore building, you must have already installed the MoonBit programming language,\nthe [Go] programming language, and the [Extism CLI tool].\n\nTo install MoonBit, follow the instructions here (it is super-easy with VSCode):\nhttps://www.moonbitlang.com/download/\n\nThen, to build this PDK, clone the repo, and type:\n\n```bash\nmoon update \u0026\u0026 moon install\n./build.sh\n```\n\n[Extism CLI tool]: https://extism.org/docs/install/\n[Go]: https://go.dev/\n\n### Run\n\nTo run the examples, type:\n\n```bash\n./run.sh\n```\n\n## Status\n\nThe code has been updated to support compiler:\n\n```bash\n$ moon version --all\nmoon 0.1.20250704 (608fa55 2025-07-04) ~/.moon/bin/moon\nmoonc v0.6.20 ~/.moon/bin/moonc\nmoonrun 0.1.20250704 (608fa55 2025-07-04) ~/.moon/bin/moonrun\n```\n\n## Reach Out!\n\nHave a question or just want to drop in and say hi? [Hop on the Discord](https://extism.org/discord)!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgmlewis%2Fmoonbit-pdk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgmlewis%2Fmoonbit-pdk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgmlewis%2Fmoonbit-pdk/lists"}