Swift WASM Nodes
Swift’s WASM support lets you bring existing Swift code into Flow-Like nodes. The template uses wit-bindgen C bindings consumed through a Swift C-interop module, compiled with the swift.org toolchain’s WASM backend.
Prerequisites
Section titled “Prerequisites”- Swift 6.2+ — the swift.org toolchain
- Swift WASM SDK:
swift sdk install swift-6.2.3-RELEASE_wasm(or matching version) - wasm-tools — component model tooling
- wit-bindgen — WIT binding generator
- Cargo — needed to install wasm-tools and wit-bindgen
# Install tooling (or use `mise run setup` from the template)cargo install wasm-toolscargo install wit-bindgen-cli@0.53.1Set TOOLCHAINS to your swift.org toolchain identifier so swift build uses the right compiler:
export TOOLCHAINS=org.swift.623202512101a # adjust to your installed versionProject Structure
Section titled “Project Structure”wasm-node-swift/├── Package.swift # SPM package definition├── flow-like.toml # Flow-Like package manifest├── mise.toml # Build tasks├── Sources/│ ├── Node/│ │ └── main.swift # Node implementation│ └── WitBindings/ # C-interop module for WIT bindings│ ├── include/│ │ ├── flow_like_node.h│ │ ├── WitBindings.h│ │ └── module.modulemap│ ├── flow_like_node.c│ ├── flow_like_node_component_type.o│ ├── reactor_init.c│ └── stubs.c└── wit/ └── flow-like-node.wit # WIT interface definitionThe WitBindings target is a C library that Swift imports as a module. The Node target depends on it.
Package.swift
Section titled “Package.swift”// swift-tools-version: 6.0import PackageDescription
let package = Package( name: "FlowLikeWasmNode", targets: [ .target( name: "WitBindings", path: "Sources/WitBindings", publicHeadersPath: "include" ), .executableTarget( name: "Node", dependencies: ["WitBindings"], path: "Sources/Node", linkerSettings: [ .unsafeFlags([ "-Xlinker", "--no-entry", "-Xlinker", "--export=_initialize", "-Xlinker", "--export=exports_flow_like_node_get_node", "-Xlinker", "--export=exports_flow_like_node_get_nodes", "-Xlinker", "--export=exports_flow_like_node_run", "-Xlinker", "--export=exports_flow_like_node_get_abi_version", "-Xlinker", "--export=cabi_realloc", "-Xlinker", "Sources/WitBindings/flow_like_node_component_type.o", ]), ] ), ])The linker settings export the required WIT entry points and embed the component type metadata.
Quick Start
Section titled “Quick Start”Node Definition
Section titled “Node Definition”Define your node’s metadata, pins, and permissions:
import WitBindings
func buildDefinition() -> NodeDefinition { var def = NodeDefinition() def.name = "my_custom_node_swift" def.friendlyName = "My Custom Node (Swift)" def.description = "A template WASM node built with Swift (Component Model)" def.category = "Custom/WASM" def.abiVersion = 1 def.addPermission("streaming")
// Input pins def.addPin(.input("exec", "Execute", "Trigger execution", "Exec")) def.addPin(.input("input_text", "Input Text", "Text to process", "String").withDefault("\"\"")) def.addPin(.input("multiplier", "Multiplier", "Times to repeat", "I64").withDefault("1"))
// Output pins def.addPin(.output("exec_out", "Done", "Execution complete", "Exec")) def.addPin(.output("output_text", "Output Text", "Processed text", "String")) def.addPin(.output("char_count", "Character Count", "Characters in output", "I64"))
return def}Run Handler
Section titled “Run Handler”The Context struct wraps WIT import functions (flow_like_node_pins_get_input, flow_like_node_pins_set_output, etc.):
func handleRun(_ ctx: inout Context) -> ExecutionResult { let inputText = ctx.getString("input_text") let multiplier = ctx.getI64("multiplier", 1)
ctx.debug("Processing: '\(inputText)' x \(multiplier)")
var output = "" for _ in 0..<multiplier { output += inputText }
ctx.streamText("Generated \(output.count) characters")
ctx.setOutput("output_text", jsonQuote(output)) ctx.setOutput("char_count", "\(output.count)")
return ctx.success()}Context API
Section titled “Context API”// Read inputsctx.getString("pin_name") // -> Stringctx.getI64("pin_name", defaultValue) // -> Int64ctx.getF64("pin_name", defaultValue) // -> Doublectx.getBool("pin_name", defaultValue) // -> Bool
// Write outputsctx.setOutput("pin_name", jsonValue)
// Loggingctx.debug("message")ctx.info("message")ctx.warn("message")ctx.logError("message")
// Streamingctx.streamText("partial output")ctx.streamEvent("event_type", "data")
// Execution controlctx.success() // -> ExecutionResult (activates "exec_out")ctx.fail("reason") // -> ExecutionResult with errorWIT Exports
Section titled “WIT Exports”Every node must export these four @_cdecl functions:
@_cdecl("exports_flow_like_node_get_node")func _exports_get_node(_ ret: UnsafeMutablePointer<flow_like_node_string_t>) { setWitResult(buildDefinition().toJSON(), ret)}
@_cdecl("exports_flow_like_node_get_nodes")func _exports_get_nodes(_ ret: UnsafeMutablePointer<flow_like_node_string_t>) { setWitResult("[" + buildDefinition().toJSON() + "]", ret)}
@_cdecl("exports_flow_like_node_run")func _exports_run(_ input: UnsafeMutablePointer<flow_like_node_string_t>, _ ret: UnsafeMutablePointer<flow_like_node_string_t>) { var ctx = Context() let result = handleRun(&ctx) setWitResult(result.toJSON(), ret)}
@_cdecl("exports_flow_like_node_get_abi_version")func _exports_get_abi_version() -> UInt32 { 1 }The template uses mise for task orchestration:
# One-time setup: install wasm-tools, wit-bindgen, WASI adapter, resolve packagesmise run setup
# Generate C bindings and copy into Sources/WitBindings/mise run generate
# Build the WASM component (generates node.wasm)mise run buildThe build pipeline:
wit-bindgen cgenerates C bindings from the WIT file- Generated files are copied into
Sources/WitBindings/(header + C source + component type object) swift build --swift-sdk swift-6.2.3-RELEASE_wasm -c releasecompiles to a core WASM modulewasm-tools component embedadds the WIT world to the modulewasm-tools component newwraps it into a WASM Component with the WASI adapter
Output: node.wasm
Manual Build (without mise)
Section titled “Manual Build (without mise)”# Step 1: Generate bindingswit-bindgen c --world flow-like-node --out-dir gen wit/flow-like-node.witcp gen/flow_like_node.h Sources/WitBindings/include/cp gen/flow_like_node.c Sources/WitBindings/cp gen/flow_like_node_component_type.o Sources/WitBindings/
# Step 2: Compileswift build --swift-sdk swift-6.2.3-RELEASE_wasm -c release
# Step 3: Embed WIT + create componentwasm-tools component embed wit/ .build/wasm32-unknown-wasip1/release/Node.wasm \ --world flow-like-node -o build/node.embed.wasm
wasm-tools component new build/node.embed.wasm \ --adapt wasi_snapshot_preview1=wasi_snapshot_preview1.reactor.wasm \ -o node.wasmTesting
Section titled “Testing”mise run test # runs swift test (native, not WASM)mise run clean # removes .build/, build/, gen/, node.wasmRelated
Section titled “Related”- Overview — How WASM nodes work
- Package Manifest — Full manifest reference
- Rust WASM Nodes — Recommended language with SDK macros