npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@amritk/mini-lynx-native

v0.2.2

Published

The thread hop between a main-thread mini-lynx tree and Lynx's background-thread NativeModules, as a typed request/reply channel.

Readme

@amritk/mini-lynx-native

The thread hop between a main-thread @amritk/mini-lynx tree and Lynx's background-thread NativeModules, as a typed request/reply channel.

The problem

Lynx is explicit about it: "native modules can only be used in Background Thread Scripting." NativeModules and GlobalEventEmitter are background globals.

@amritk/mini-lynx renders on the main thread, because the Element PAPI it drives is a main-thread API.

Those two facts do not overlap. A component reaching for NativeModules finds undefined — no error, no warning, just a property that is not there.

This package is the wire between them.

Install

bun add @amritk/mini-lynx-native

Setup

Two lines, in two chunks.

// background chunk — once, at startup
import { installNativeBridge } from '@amritk/mini-lynx-native/background'

installNativeBridge()
// main-thread chunk — anywhere in a component
import { callNative } from '@amritk/mini-lynx-native'

const level = await callNative<number>('BatteryModule', 'level')

Calls made before the background half is installed are queued, not lost — the main-thread chunk usually runs first, because the engine calls renderPage to get a first screen up.

API

| Function | What it does | | --- | --- | | callNative(module, method, ...args) | Invokes a native method that returns its result. | | callNativeAsync(module, method, ...args) | Invokes a native method that takes a trailing callback. | | isNativeModuleAvailable(module, method?) | Feature-detects without invoking anything. | | onNativeEvent(name, listener) | Subscribes to sendGlobalEvent. Returns the unsubscribe. | | resetNativeChannel() | Detaches and rejects everything in flight. For teardown and tests. | | setPeerContext(proxy) | Substitutes the context. For tests. |

callNative and callNativeAsync differ in the shape of the native method, not the JavaScript one — both return promises, because both cross a thread.

Everything is async, including the synchronous things

A native method that returns immediately still reaches you a thread later. The practical consequence: a native value cannot be read during a component's build. Bind a signal, start the call, write the signal when it settles.

const level = signal(0)
callNative<number>('BatteryModule', 'level').then(level)

const View = () => <text>{() => `${level()}%`}</text>

What crosses

Arguments and results are serialised by the engine, so they must be plain JSON. No functions, no class instances, no Date. A failure crosses as a message and is rebuilt into an Error on the main thread — an Error itself would arrive as {}.

Testing

@amritk/mini-lynx-native/testing ships the two fakes both halves run against, so a bridge-backed module can be tested with no device:

import { resetNativeChannel, setPeerContext } from '@amritk/mini-lynx-native'
import { installNativeBridge } from '@amritk/mini-lynx-native/background'
import { createFakeContexts, createFakeEmitter } from '@amritk/mini-lynx-native/testing'

const contexts = createFakeContexts()
const emitter = createFakeEmitter()
setPeerContext(contexts.mainThread)
const uninstall = installNativeBridge({
  peer: contexts.background,
  emitter,
  modules: { BatteryModule: { level: () => 87 } },
})

Caveats worth knowing before you build on it

  • The app owns the background half. This package cannot inject itself into a chunk it cannot see; which module runs in the background context is a bundler question. One line, once.
  • A native method that never calls back leaves a promise pending forever. The wire cannot detect it. resetNativeChannel() settles the stragglers.
  • Calls are not retried across a background reload. Subscriptions are replayed because they are idempotent; a native method that charged a card is not something the channel should retry on its own initiative.

Licence

MIT