A Ruby gem that converts Dry::Struct definitions to TypeScript types.
Add to your Gemfile:
gem 'dry-typescript'Configure in an initializer (config/initializers/dry_typescript.rb):
Dry::TypeScript.configure do |config|
config.output_dir = Rails.root.join("app/javascript/types")
config.dirs = [
Rails.root.join("app/resources"),
Rails.root.join("app/structs")
]
endThese rake tasks are automatically available:
rails dry_typescript:generate- Generate TypeScript files from all Dry::Struct classesrails dry_typescript:refresh- Clean and regeneraterails dry_typescript:clean- Remove generated filesrails dry_typescript:check- Check if types are up to date (exits 1 if stale, for CI)
Add the listen gem to automatically regenerate TypeScript files when struct files change:
gem 'listen', group: :developmentThe watcher starts automatically in development when listen is available.
# Force enable/disable listening
Dry::TypeScript.listen = true # Force enable
Dry::TypeScript.listen = false # Force disable
Dry::TypeScript.listen = nil # Auto-detect (default)
# Disable via environment variable
ENV["DISABLE_DRY_TYPESCRIPT"] = "true"Enable debug output:
DRY_TYPESCRIPT_DEBUG=1 rails serverrequire 'dry-typescript'
Dry::TypeScript.configure do |config|
config.output_dir = "lib/types"
endAdd to your Rakefile:
require 'dry/typescript/rake_task'
Dry::TypeScript::RakeTask.new(:typescript) do |t|
t.output_dir = "lib/types"
t.structs = [User, Address, Order]
endThis provides:
rake typescript:generate- Generate TypeScript filesrake typescript:clean- Remove generated files
generator = Dry::TypeScript::Generator.new(structs: [User, Address, Order])
sorted = generator.sorted_structs
writer = Dry::TypeScript::Writer.new
writer.write_all(sorted)
writer.cleanup(current_structs: sorted)Dry::TypeScript.configure do |config|
config.output_dir = "app/javascript/types"
config.export_style = :named # :named (default) or :default
config.null_strategy = :optional # :nullable, :optional, or :nullable_and_optional
config.barrel_file = true # false (default) - generate index.ts barrel file
config.type_name_transformers = [Dry::TypeScript::Transformers.strip_struct_suffix]
config.property_name_transformer = ->(name) { name.to_s.camelize(:lower) }
endExport styles:
:named(default) -export type User = {...}with barrelexport type { User } from './User':default-type User = {...}+export default Userwith barrelexport { default as User } from './User'
Barrel file:
false(default) - No index.ts generated. Direct imports recommended to avoid bundle bloat.true- Generates index.ts with re-exports of all types.
Override global settings for individual structs:
class User < Dry::Struct
extend Dry::TypeScript::StructMethods
extend Dry::TypeScript::PerStructConfig
typescript_config do |config|
config.type_name = "UserResponse" # Custom TypeScript type name
config.export_style = :default # :named or :default
config.null_strategy = :optional # :nullable, :optional, or :nullable_and_optional
config.type_name_transformers = [Dry::TypeScript::Transformers.strip_struct_suffix]
config.property_name_transformer = ->(name) { name.to_s.camelize(:lower) }
end
attribute :name, Types::String
endclass User < Dry::Struct
extend Dry::TypeScript::StructMethods
attribute :name, Types::String
attribute :age, Types::Integer
attribute :email, Types::String.optional
end
User.to_typescript
# => { typescript: "export type User = {\n name: string;\n age: number;\n email: string | null;\n}", dependencies: [] }- Fingerprint-based change detection: Only rewrites files when content changes
- Import generation: Automatically generates imports for struct dependencies
- Barrel exports: Optionally creates index.ts with all type exports (disabled by default)
- Safe cleanup: Only removes files generated by dry-typescript
- Collision detection: Raises error if multiple structs resolve to same filename
- Atomic writes: Uses temp files to prevent partial writes
- Deterministic output: Imports and exports are sorted alphabetically
MIT