I’ve been working on a new library that automatically generates fluent builder patterns for ReScript types, and I’d love to get some feedback from the community before continuing development.
What it does
rescript-derive-builder scans your ReScript codebase and generates type-safe builder patterns for types annotated with @@deriving(builder). Here’s a quick example:
/**
* @@deriving(builder)
* User profile with optional fields
*/
type t = {
name: string,
age: int,
email?: string, // Optional field support!
}
Generates:
let user = UserBuilder.empty()
->UserBuilder.name("Alice")
->UserBuilder.age(25)
->UserBuilder.email(Some("alice@example.com"))
->UserBuilder.build()
// Result: result<User.t, string> with specific error messages
GitHub: GitHub - nathan-tranquilla/rescript-derive-builder
NPM: https://www.npmjs.com/package/rescript-derive-builder
Design Decisions I’d Love Feedback On
1. Using rescript-tools doc for Type Extraction
Instead of using PPX, I chose to use rescript-tools doc to extract type information:
Pros:
-
Leverages the official ReScript toolchain -
Gets accurate type information (handles all ReScript syntax) -
JSON output is stable and well-structured -
Future-proof against ReScript syntax changes -
Allows me to write codegen in ReScript!
Cons:
-
Requires an extra compilation step
Question: Does this approach feel right to the community? Are there concerns about the approach?
2. Docstring Annotations + JSON Config
I went with a hybrid approach:
-
Docstring annotations (
@@deriving(builder)) mark which types to process - JSON configuration within the rescript.json file specify file patterns and output directories
{
"derive-builder": {
"include": ["src/**/*.res"],
"output": "src/generated"
}
}
Rationale:
- Docstring annotations keep the intent close to the type definition
- JSON config handles project-level concerns (file patterns, output paths)
Question: How does this feel compared to any alternative approaches like a dedicated config file for the builder?
3. Strategy Pattern Architecture
The library uses a chain-of-responsibility pattern for code generation:
module type HandlerInterface = {
let canHandle: JSON.t => bool
let handle: JSON.t => string
}
This makes it easy to add support for different type patterns by implementing new handlers.
Current Limitations (Intentional)
Right now, the library only supports the main type pattern (single type t within a file):
// ✅ Supported
type t = { name: string, age: int }
// ❌ Multiple non-t types not yet supported
type user = { name: string, age: int }
...
Why start here?
- Most common ReScript pattern (module-scoped
.ttypes) - Validates the architecture before expanding
- Keeps initial scope manageable
Key Features
-
Optional field support with ReScript’s
field?: typesyntax - Comprehensive error messages for missing required fields
- Type-safe builders with proper ReScript typing
- Automatic discovery of configuration files (looks for the first rescript.json file in parent having the “derive-builder” configuration key)
- Extensible architecture for future type patterns
Questions for the Community
-
Architecture: Does the
rescript-tools doc+ docstring approach feel idiomatic? - API Design: How does the generated builder API feel? Any ergonomic issues?
- Limitations: What type patterns should I prioritize next?
- Performance: Any concerns about the build-time code generation step?
- Integration: How would this fit into your existing ReScript workflows?
Next Steps
Next steps would involve expanding type generation for other type patterns
Try it out:
npm install rescript-derive-builder
npx rescript-derive-builder
I’d really appreciate any thoughts, concerns, or suggestions! This is my first major ReScript library, so community input is invaluable for making sure I’m heading in the right direction.
What it does
Design Decisions I’d Love Feedback On
Leverages the official ReScript toolchain
Requires an extra compilation step
Current Limitations (Intentional)
Key Features
Questions for the Community
Next Steps