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

@juspay/rescript-bindgen

v1.0.0

Published

Deterministic TypeScript -> ReScript binding generator. Reads .d.ts via the TypeScript compiler API and emits type-safe @react.component bindings — no AI, no %identity.

Downloads

0

Readme

@juspay/rescript-bindgen

Deterministic TypeScript → ReScript binding generator. Reads .d.ts files through the TypeScript compiler API and emits type-safe @react.component bindings — no AI, no %identity, no unsafe casts.

Built to keep @juspay/rescript-blend in sync with @juspay/blend-design-system without hand-maintaining bindings — but it works on any typed React component package.


Why

There is one existing tool in this space, ts2ocaml, but it cannot generate React component bindings (it emits external x: any with a FIXME for ForwardRefExoticComponent) and doesn't match a project's house style.

rescript-bindgen is purpose-built for React component packages: it resolves Omit<…>, intersections, imported enums, RefAttributes, and indexed-access types via the TypeScript type-checker, then emits idiomatic ReScript 12 bindings.

The generator is deterministic: identical input always produces identical output. Anything it cannot bind in a fully type-safe way is flagged for human review, never silently hacked.


What it produces

Given this .d.ts:

declare const Button: import('react').ForwardRefExoticComponent<{
  buttonType?: ButtonType;            // enum
  text?: string;
  width?: string | number;            // multi-type
  onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
} & Omit<ButtonHTMLAttributes<HTMLButtonElement>, "style" | "className">>;
export default Button;

it emits:

type buttonType =
  | @as("primary") Primary
  | @as("secondary") Secondary

@unboxed type widthValue = Str(string) | Num(float)

@module("@juspay/blend-design-system") @react.component
external make: (
  ~buttonType: buttonType=?,
  ~text: string=?,
  ~width: widthValue=?,
  ~onClick: ReactEvent.Mouse.t => unit=?,
  ~id: string=?,
  @as("aria-label") ~ariaLabel: string=?,
) => React.element = "Button"

<Button width=Num(5.0) /> sends width: 5 to JS; <Button width=Str("100%") /> sends width: "100%". Type-safe and zero-cost — the @unboxed variant is erased at runtime.


Install

npm install -D @juspay/rescript-bindgen
# or run ad-hoc:
npx @juspay/rescript-bindgen --help

Requires Node ≥ 20. ReScript 12 is recommended for the generated output.


Usage

Generate bindings for any typed React package

The package spec is the exact one you'd give to npm install (name, [email protected], a beta, or a pkg.pr.new URL). It's installed into a scratch cache and read from there, so output is reproducible and version-pinned.

# a published package (any version)
npx rescript-bindgen --pkg react-day-picker --out generated
npx rescript-bindgen --pkg @mui/[email protected] --only Button --out generated

# a single .d.ts file, printed to stdout
npx rescript-bindgen --file ./types/Foo.d.ts --stdout

# a local folder containing an index.d.ts
npx rescript-bindgen --dir ./node_modules/some-lib --out generated

| Flag | Meaning | |------|---------| | --pkg <name[@ver]> | npm package (auto-installed to a scratch cache if absent) | | --file <path.d.ts> | a single declaration file (one component) | | --dir <folder> | a folder containing index.d.ts | | --out <dir> | output directory (default generated) | | --only <Comp> | generate just one component | | --report | also write _REPORT.md — the ready / loose / review / defect summary | | --from <name> | override the @module(...) import name | | --stdout | print to stdout instead of writing files (single component) | | --no-install | don't auto-install a missing --pkg |

Untyped JS packages produce only loose skeleton bindings — the tool is type-driven.

Get the report

Add --report to also emit _REPORT.md next to the bindings — a checklist of which components are ready, which props were widened to string (loose), which need human review, and which are broken (unknown/any):

npx rescript-bindgen --pkg @mui/material --out generated --report

How it works

INPUT          RESOLVE         EXTRACT          MAP             EMIT            REPORT
.d.ts / pkg → locate types → TS type-checker → mapping table → ReScript 12 → _REPORT.md
                              → IR             (fixed table)   emitter        (--report)
  1. Resolve (resolve.mjs) — find the declaration entry for a file / dir / npm package.
  2. Extract (extract.mjs) — the TypeScript type-checker resolves Omit, intersections, RefAttributes, generics and indexed-access into a flat prop list (the IR).
  3. Map (extract.mjs + emit.mjs) — each TS type maps to ReScript via a fixed table (below).
  4. Emit (emit.mjs) — render the IR to ReScript 12: @as variants, @unboxed variants, records, and the @module @react.component external make binding.
  5. Report (report.mjs) — with --report, write a per-component _REPORT.md bucketing props into ready / loose / review / defect.

Type mapping

| TypeScript | ReScript | |---|---| | string / boolean | string / bool | | number | int (count/size/index names) or float | | string-literal union / enum | @as variant | | string \| number, string \| string[] | @unboxed untagged variant (Str \| Num \| StrArr) | | ReactNode / ReactElement | React.element | | React.CSSProperties | JsxDOM.style | | MouseEvent / FocusEvent / ChangeEvent / KeyboardEvent | ReactEvent.Mouse.t / .Focus.t / .Form.t / .Keyboard.t | | Ref<HTMLX> | React.ref<Nullable.t<Dom.element>> | | X[] / Record<K,V> | array<X> / Dict.t<V> | | Date / CSSObject['x'] | Date.t / string | | Omit / Pick / Partial / intersection | resolved & flattened by the checker | | unknown / any | flagged as defect — never typed | | undiscriminable union (object shapes) | flagged for human review |

Multi-type props use untagged variants

For string | number style props the tool emits a ReScript 11+ untagged variant — the officially recommended, zero-cost way to bind a JS value that can be several types. The raw value reaches JS, with no %identity, @unwrap, or Obj.magic.

When a union's members can't be told apart at runtime (e.g. two object shapes), the tool refuses to guess: it emits a string placeholder with an inline // ⚠️ REVIEW comment and lists it in the report.


The report

Add --report to write _REPORT.md next to the bindings — a checklist of components:

  • [x] ready to use — every prop bound type-safely
  • [~] needs human review — a multi-type prop couldn't be auto-discriminated
  • [ ] broken — has unknown/any props that won't work as typed (fix upstream)
  • (n loose) — props widened to string (compile and work, just loosely typed)

This separates what won't work (defects) from what needs a decision (review) from what's done (ready). Each flagged prop is listed with its original TypeScript.

npx rescript-bindgen --pkg @mui/material --out generated --report

Library API

import { extractComponent, extractModule, emit, report } from '@juspay/rescript-bindgen'

const ir = extractComponent('node_modules/pkg/dist/Button.d.ts', { from: 'pkg' })
const code = emit(ir)              // ReScript source string
const { defects, review, loose } = report(ir)

Exports: extractComponent, extractModule, emit, report, resolveInput, writeReport. Full type definitions ship in types.d.ts.


Limitations

  • Generic components (<T extends …>) — generic type parameters resolve to unknown and are flagged as defects (e.g. blend's DataTable). Needs concrete types upstream or generic-binding support (planned).
  • Sub-components (Drawer.Title) — detected in the IR; nested-module emission is planned.
  • Untyped JS — produces loose skeleton bindings only (no types to read).
  • int vs float for number is a name heuristic — verify numeric props if exact.

Development

npm test                # self-contained smoke test
npm run gen -- --pkg <some-package> --out generated --report
node test/ts-demo.mjs   # live TypeScript compiler-API walkthrough (see test/DEMOS.md)

The ReScript compile sandbox lives in test/sandbox/ (used to compile-check generated output during development).


License

MIT © Juspay Technologies