From rescript-lsp
Creates ReScript bindings for JavaScript/TypeScript libraries. Use when converting TypeScript definitions to ReScript, writing external function bindings, or binding to npm packages. Handles module imports, class bindings, type mappings (variants, records, generics), callbacks, and function overloads.
How this skill is triggered — by the user, by Claude, or both
Slash command
/rescript-lsp:rescript-bindingsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Create type-safe ReScript bindings for JavaScript/TypeScript libraries.
Create type-safe ReScript bindings for JavaScript/TypeScript libraries.
Ask these questions first:
What's the task?
ReScript version? (v11+, v10.x, or older)
@unboxed variants, @tag discriminators, optional record fields{name?: string})@obj for optional fieldsZero-cost or ergonomic?
@unboxedTypeScript version? When looking up type definitions, ensure you use the exact version the user expects. If unclear, ask.
CRITICAL: Always use the actual TypeScript .d.ts files as the canonical reference for what bindings should look like.
DO use:
node_modules/{library}/*.d.ts filesnode_modules/@types/{library}/*.d.ts filesDO NOT use as primary reference:
When verifying or updating bindings, read the TypeScript interfaces prop by prop and compare systematically against the ReScript bindings.
Before creating bindings for a library or its dependencies:
@module("{library}") patterns)@rescript/react, rescript-webapi)Avoid duplicating or conflicting with existing bindings.
Compiling ReScript does NOT ensure bindings are correct! You must:
Choose the pattern that matches your use case:
| Pattern | When to Use | Reference |
|---|---|---|
| Module imports | import {x} from "pkg" | functions.md |
| Classes | JS classes with methods | classes.md |
| Globals | window.foo, Math.random | globals.md |
| Types | Records, variants, unions | types.md |
| Callbacks | Function parameters | callbacks.md |
| Overloads | Multiple signatures | overloads.md |
| Attribute | Purpose |
|---|---|
@module("pkg") | Import from npm package |
@val | Bind global value |
@send | Instance method (instance is first arg) |
@get / @set | Property access |
@new | Constructor |
@as("jsName") | Rename for JS |
@variadic | Rest/spread args |
@return(nullable) | null or undefined → option |
@unboxed | Zero-cost variant wrapper |
Functions that may throw: Suffix with OrThrow (e.g., parseOrThrow, getExn)
Overloaded functions: Use descriptive suffixes (fetchWithOptions, not fetch2)
Unsafe bindings: Suffix with Unsafe when:
useQueryStatesUnsafe)Array.getUnsafe returns 'a instead of option<'a>)Follows ReScript convention: Option.getUnsafe, Array.getUnsafe, etc.
If a TypeScript type cannot be fully represented in ReScript (mapped types, conditional types, complex generics):
Unsafe when the return type is a type variable (and especially when inputs are too)// Library provides generic/unsafe version:
@module("nuqs")
external useQueryStatesUnsafe: 'parsers => ('values, setQueryStates<'values>) = "useQueryStates"
// User creates type-safe version with concrete types in their application:
type myParsers = {name: singleParserBuilderWithDefault<string>, count: singleParserBuilder<int>}
type myValues = {name: string, count: Nullable.t<int>} // name has default, count doesn't
@module("nuqs")
external useQueryStates: myParsers => (myValues, setQueryStates<myValues>) = "useQueryStates"
This pattern gives library authors safe defaults while letting application developers opt into precise types when they control both sides.
TypeScript types tell you what a value is, but the right ReScript type depends on how it will be used. When a TypeScript type is overly dynamic (mapped types, Record<string, T>, index signatures), ask: what does the consumer actually do with this value?
For example, if a library returns Record<string, (event: Event) => void> but those handlers will be spread onto a React element, Dict.t<JsxEvent.Synthetic.t => unit> is the faithful translation — but a record with optional fields for the known event handlers is more ergonomic and avoids needing %identity casts at the call site.
Guideline: Prefer ergonomic types that work well at the call site over 1:1 TypeScript mappings, especially when the TypeScript type is lazily typed. A Record<string, T> in TypeScript often means "we didn't enumerate the keys" rather than "the keys are truly arbitrary."
Use /** */ syntax (NOT JSDoc):
/** Fetches data from the API. */
@module("api") external fetch: string => promise<response> = "fetch"
npx claudepluginhub illusionalsagacity/claude-plugins --plugin rescript-lspCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.