9.7 KiB
Natives Binding Contract (JavaScript/TypeScript Side)
This document defines the JS/TS contract between @oh-my-pi/pi-natives callers and the loaded N-API addon.
Current package shape is direct-to-native: there is no packages/natives/src/<module> TypeScript wrapper layer. The public API is the generated packages/natives/native/index.d.ts declaration file, the CommonJS loader in packages/natives/native/index.js, and the Rust #[napi] exports in crates/pi-natives/src.
Implementation files
packages/natives/native/index.jspackages/natives/native/index.d.tspackages/natives/native/loader-state.jspackages/natives/scripts/build-native.tspackages/natives/scripts/gen-enums.tspackages/natives/package.jsoncrates/pi-natives/src/lib.rs- Rust modules under
crates/pi-natives/src/*.rs
Contract model
The contract has three parts:
- Generated runtime loader (
native/index.js)- computes candidates and
require(...)s the.nodeaddon; - exports the loaded addon object directly;
- appends enum objects generated by
scripts/gen-enums.ts.
- computes candidates and
- Generated TypeScript declarations (
native/index.d.ts)- generated by napi-rs during
scripts/build-native.ts; - declares exported functions, classes, object interfaces, and native enums;
- is the package
typesentry.
- generated by napi-rs during
- Rust N-API exports (
crates/pi-natives/src)#[napi]functions/classes/objects/enums are the source of generated declarations and runtime symbols;- snake_case Rust names become camelCase JavaScript names by napi-rs convention.
There is no current NativeBindings declaration-merging lifecycle and no validateNative(...) required-export list in the loader.
Public export surface organization
packages/natives/package.json exposes the package root only:
{
"main": "./native/index.js",
"types": "./native/index.d.ts",
"exports": {
".": {
"types": "./native/index.d.ts",
"import": "./native/index.js"
}
}
}
Consumers in packages/coding-agent and packages/tui import directly from @oh-my-pi/pi-natives.
JS API ↔ native export mapping (representative)
| Category | Public JS API | Rust source | Return style |
|---|---|---|---|
| Grep | grep(options, onMatch?) |
grep.rs |
Promise<GrepResult> |
| Grep | search(content, options) |
grep.rs |
SearchResult |
| Grep | hasMatch(content, pattern, ignoreCase?, multiline?) |
grep.rs |
boolean |
| Fuzzy path search | fuzzyFind(options) |
fd.rs |
Promise<FuzzyFindResult> |
| Glob | glob(options, onMatch?) |
glob.rs |
Promise<GlobResult> |
| Glob cache | invalidateFsScanCache(path?) |
fs_cache.rs |
void |
| AST search/edit | astGrep(options), astEdit(options) |
ast.rs |
Promise<...> |
| Shell | executeShell(options, onChunk?) |
shell.rs |
Promise<ShellExecuteResult> |
| Shell | new Shell(options?), shell.run(...), shell.abort() |
shell.rs |
class / promises |
| PTY | new PtySession(), start/write/resize/kill |
pty.rs |
class / promises |
| Process | killTree(pid, signal), listDescendants(pid) |
ps.rs |
sync |
| Keys | parseKey, matchesKey, Kitty/legacy helpers |
keys.rs |
sync |
| Text | wrapTextWithAnsi, truncateToWidth, sliceWithWidth, extractSegments, sanitizeText, visibleWidth |
text.rs |
sync |
| Highlight | highlightCode, supportsLanguage, getSupportedLanguages |
highlight.rs |
sync |
| HTML | htmlToMarkdown(html, options?) |
html.rs |
Promise<string> |
| Image | PhotonImage, encodeSixel |
image.rs |
class / sync / promises |
| Clipboard | copyToClipboard, readImageFromClipboard |
clipboard.rs |
sync / promise |
| Tokens | countTokens(input, encoding?) |
tokens.rs |
sync |
| System | detectMacOSAppearance, MacAppearanceObserver, MacOSPowerAssertion, getWorkProfile, ProjFS helpers |
appearance.rs, power.rs, prof.rs, projfs_overlay.rs |
mixed |
Sync vs async contract differences
The contract preserves Rust/N-API call style:
- Promise-returning exports for worker-thread or async runtime work (
grep,glob,fuzzyFind,astGrep,astEdit,htmlToMarkdown, shell/PTY runs, image parse/resize/encode, clipboard image read). - Synchronous exports for deterministic in-memory transforms/parsers or direct system calls (
search,hasMatch, highlighting, text utilities, token counting, process queries,copyToClipboard,encodeSixel). - Constructor exports for stateful runtime objects (
Shell,PtySession,PhotonImage, macOS observer/power handles).
Changing sync ↔ async for an existing export is a breaking public API change because consumers call these exports directly.
Object and enum typing patterns
Object patterns
#[napi(object)] Rust structs become TS interfaces, for example:
GrepResult,SearchResult,GlobResult,FuzzyFindResultShellRunResult,ShellExecuteResult,PtyRunResult,MinimizerResultAstFindResult,AstReplaceResultSystem/media payloads such asClipboardImage,WorkProfile,ParsedKittyResult
Runtime shape correctness is owned by napi-rs and the Rust implementation.
Enum patterns
Native enums are represented in generated declarations and also appended to module.exports by scripts/gen-enums.ts, because the loader is hand-maintained CommonJS around the generated addon. Current enum objects include:
AstMatchStrictnessEllipsisEncodingFileTypeGrepOutputModeImageFormatKeyEventTypeMacOSAppearanceSamplingFilter
Error behavior and caveats
- Addon load failure or unsupported platform throws during package import from
native/index.js. - The loader does not verify the full export set after
require(...); stale or mismatched binaries surface as native load errors or missing members at use sites. - N-API conversion validates basic argument conversion, but TS optional fields do not guarantee semantic validity for untyped callers.
- Numeric enum declarations do not prevent out-of-range numeric values from untyped callers unless the Rust function rejects them during conversion.
- Callback exports use napi-rs
ThreadsafeFunctionshape:(error: Error | null, value) => void. Native code generally emits successful values; hard failures reject/throw through the owning call.
Maintainer checklist for binding changes
When adding/changing an export, update all of:
- Rust
#[napi]implementation in the owningcrates/pi-natives/src/<module>.rs. crates/pi-natives/src/lib.rsif a new module is added.- Any consumer imports/callsites in
packages/coding-agentorpackages/tui. - Build output by running the natives build so
native/index.d.tsandnative/index.jsstay in sync. scripts/gen-enums.tsif enum runtime export patching needs to change.
Do not add a parallel TS wrapper convention unless the package design intentionally moves back to wrappers; current consumers depend on the direct generated API.