Skip to main content

Three real examples

Each example covers a different linkage-mode choice.

_duckdb_capability — compose-time static, big vendor library

DuckDB is ~40 MB compiled to wasm and holds prepared-statement / connection handles across many Python calls. Handles cross the WIT boundary as resource types (conn, prepared-stmt, appender), which require the compose-time path.

cpython-ext/_duckdb_capability/
├── _duckdb_capability_module.c # DB-API-shaped C ext, ~700 lines
├── duckdb.py # Facade shim → Lib/duckdb.py
├── wit/
│ └── duckdb-import.wit # world duckdb-import { import duckdb:component/database; }
├── gen/ # wit-bindgen-c output (checked in)
│ ├── duckdb_import.c
│ ├── duckdb_import.h
│ └── duckdb_import_component_type.o
└── pyforge-pkg.toml

Sibling: ~/git/duckdb-wasm/ — the Rust cargo-component crate that builds ducklink_core.wasm and exports duckdb:component/database.

Composition: plugged in by scripts/compose-python-component.sh at the DUCKDB_COMPONENT slot.

Same C-ext shape as any other cap, but the C ext imports compose:dynlink/linker instead of crc32c:.... At PyInit, it calls linker.resolve_by_id("crc32c") once and caches the handle. Every crc32c() call becomes instance.invoke("compute", data).

cpython-ext/_crc32c_dynlink_cap/
├── _crc32c_dynlink_capmodule.c
├── wit/ # world { import compose:dynlink/linker; }
├── gen/
└── pyforge-pkg.toml

Provider: packages-wasm/crc32c-dynlink-provider/ (Rust component exporting compose:dynlink/endpoint). Registered in build-pylon-dynlink-plan.py's DYNLINK_PROVIDERS list.

Adding an alternate CRC32C provider (say, a faster SIMD one) requires building a new provider component, adding it to DYNLINK_PROVIDERS, regenerating the plan. pylon.wasm does not change.

_id_mux_cap — shared-interface multiplexer

Some caps share a single WIT interface with a scheme-parameter (ULID, NanoID, KSUID all route through tegmentum:id-multiplexer/id-dispatcher). The multiplexer component (~/git/id-multiplexer/) is one wasm blob; the cap C ext is one module; the Python-side facade dispatches to per-scheme sub-modules.

cpython-ext/_id_mux_cap/
├── _id_mux_capmodule.c # thin C ext exposing dispatch()
├── id_mux.py # Facade → Lib/id_mux.py, offers
│ # generate("ulid"), generate("ksuid"), ...
├── wit/
├── gen/
└── pyforge-pkg.toml

The multiplexer pattern is worth reaching for whenever three-plus sibling caps would otherwise be three-plus components importing three-near-identical interfaces. It keeps pylon.wasm's import graph flat.


For the hands-on companion — "how do I add one of these?" — see Add a cap.