convex-rescript-codegen
v0.1.0
Published
Generate type-safe ReScript bindings from Convex backend functions
Maintainers
Readme
convex-rescript-codegen
Generate type-safe ReScript bindings from your Convex backend functions. This tool parses your Convex TypeScript code using the TypeScript AST and generates idiomatic ReScript modules with proper types and React hooks.
✨ Features
- 🎯 Type-safe bindings - Generates fully typed ReScript modules from Convex functions
- 🔄 React hooks - Ready-to-use
useQueryanduseMutationhooks - 📦 Schema support - Parses Convex schema to generate document types
- 🔍 AST-based parsing - Uses TypeScript compiler API for accurate parsing
- ⚡ Blazing fast - Powered by Bun runtime (10x faster than Node.js)
- 👁️ Watch mode - Auto-regenerate on file changes with @parcel/watcher
- 🎨 Idiomatic ReScript - Generates clean, idiomatic ReScript code
- 🛡️ Edge case handling - Handles reserved keywords, polymorphic variants
📦 Installation
This package is designed to run with Bun for best performance:
# Recommended: Install with Bun
bun add -D convex-rescript-codegen
# Also works with npm/yarn/pnpm (requires Bun runtime)
npm install -D convex-rescript-codegenNote: This package requires Bun runtime to be installed on your system.
🚀 Quick Start
- Install Bun if you haven't already:
curl -fsSL https://bun.sh/install | bash- Add to your package.json scripts:
{
"scripts": {
"codegen": "bunx convex-rescript",
"codegen:watch": "bunx convex-rescript --watch"
}
}- Run the generator:
bun run codegen
# or directly with
bunx convex-rescript- Use the generated bindings:
// Your generated bindings are in src/bindings/generated/
open ConvexTypes
@react.component
let make = () => {
// Use generated query hook
let users = Convex_users.Query_list.use(~limit=Some(JSON.Encode.int(10)))
// Use generated mutation hook
let createUser = Convex_users.Mutation_create.use()
// Type-safe function calls
let handleCreate = async () => {
let userId = await createUser({
name: "Alice",
email: "[email protected]"
})
Console.log2("Created:", userId)
}
// Render your UI...
}📁 Generated File Structure
src/bindings/generated/
├── ConvexTypes.res # Document types from schema
├── ConvexBindings.res # Core Convex React bindings
├── ConvexGenerated.res # Module exports
├── Convex_users.res # Bindings for users.ts
├── Convex_rooms.res # Bindings for rooms.ts
└── Convex_messages.res # Bindings for messages.ts🔧 Configuration
Command Line Options
convex-rescript [options]
Options:
-i, --input <path> Input directory (default: ./convex)
-o, --output <path> Output directory (default: ./src/bindings/generated)
-w, --watch Watch for changes
-v, --verbose Verbose output
-h, --help Show helpExamples
# Basic usage (uses defaults)
convex-rescript
# Watch mode
convex-rescript --watch
# Custom paths
convex-rescript -i ./backend/convex -o ./frontend/src/bindings
# Verbose output for debugging
convex-rescript --verbose📝 Type Mappings
| Convex Type | ReScript Type |
|------------|--------------|
| v.string() | string |
| v.number() | float |
| v.boolean() | bool |
| v.null() | unit |
| v.id("table") | tableId (e.g., usersId) |
| v.optional(...) | option<...> |
| v.array(...) | array<...> |
| v.object(...) | JSON.t |
| v.any() | JSON.t |
| v.union(...) | Polymorphic variants |
| v.literal(...) | Polymorphic variants |
🎯 Generated Code Examples
Query with no arguments
module Query_list = {
type output = array<usersDoc>
@module("convex/react")
external useQuery: ('api, unit) => option<output> = "useQuery"
let use = () => {
useQuery(api["users"]["list"], ())
}
}Query with arguments
module Query_getById = {
type input = {
userId: usersId,
}
type output = option<usersDoc>
@module("convex/react")
external useQuery: ('api, input) => option<output> = "useQuery"
let use = (~userId) => {
useQuery(api["users"]["getById"], {
userId: userId
})
}
}Mutation
module Mutation_create = {
type input = {
name: string,
email: string,
avatar: option<string>,
}
type t = input => promise<usersId>
@module("convex/react")
external useMutation: 'api => t = "useMutation"
let use = () => {
useMutation(api["users"]["create"])
}
}Document Types from Schema
type usersDoc = {
@as("_id") id: usersId,
@as("_creationTime") creationTime: float,
name: string,
email: string,
avatar: option<string>,
status: [| #online | #offline | #away],
lastSeen: float,
createdAt: float,
}🛡️ Edge Case Handling
Reserved Keywords
The generator automatically escapes ReScript reserved keywords:
type→type_with@as("type")private→#"private"in polymorphic variants
Polymorphic Variants
Convex string unions are converted to ReScript polymorphic variants:
// Convex
status: v.union(
v.literal("online"),
v.literal("offline"),
v.literal("away")
)// ReScript
status: [| #online | #offline | #away]🤝 Requirements
- Bun runtime 1.0+ (required) - Install Bun
- ReScript v11+ (tested with v12 RC)
- Convex backend with TypeScript functions
- React project (for the generated hooks)
🐛 Known Limitations
- Complex nested types - Very complex nested object types default to
JSON.t - Function return types - Currently infers common patterns, not all return types
- Convex validators - Complex validator compositions may not be fully supported
- Actions - Limited support for Convex actions (treated similar to mutations)
🚧 Roadmap
- [ ] Full return type inference from function implementations
- [ ] Support for Convex HTTP endpoints
- [ ] Custom type mapping configuration
- [ ] Generate mock data for testing
- [ ] VS Code extension for instant generation
- [ ] Support for convex-helpers library patterns
📄 License
MIT
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
🙏 Acknowledgments
- Built for the ReScript and Convex communities
- Inspired by GraphQL Code Generator and similar tools
- Uses ts-morph for robust TypeScript AST parsing
📚 Resources
Made with ❤️ for type-safe full-stack development
