Skip to content

Commit 5cf1cdd

Browse files
committed
Initial implementation
0 parents  commit 5cf1cdd

File tree

109 files changed

+2952
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

109 files changed

+2952
-0
lines changed

.github/workflows/main.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: Ruby
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
pull_request:
9+
10+
jobs:
11+
lint:
12+
runs-on: ubuntu-latest
13+
name: Linter
14+
env:
15+
BUNDLE_JOBS: 4
16+
BUNDLE_RETRY: 3
17+
steps:
18+
- uses: actions/checkout@v3
19+
- name: Set up Ruby
20+
uses: ruby/setup-ruby@v1
21+
with:
22+
ruby-version: "3.3"
23+
bundler-cache: true
24+
- name: Run StandardRB
25+
run: bundle exec standardrb
26+
27+
build:
28+
runs-on: ubuntu-latest
29+
name: Ruby ${{ matrix.ruby }}
30+
env:
31+
BUNDLE_JOBS: 4
32+
BUNDLE_RETRY: 3
33+
strategy:
34+
matrix:
35+
ruby:
36+
- "3.3"
37+
- "3.2"
38+
- "3.1"
39+
- "3.0"
40+
- "2.7"
41+
- "jruby"
42+
- "truffleruby"
43+
44+
steps:
45+
- uses: actions/checkout@v4
46+
- name: Set up Ruby
47+
uses: ruby/setup-ruby@v1
48+
with:
49+
ruby-version: ${{ matrix.ruby }}
50+
bundler-cache: true
51+
- name: Run tests
52+
run: bundle exec rspec

.gitignore

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/.bundle/
2+
/.yardoc
3+
/_yardoc/
4+
/coverage/
5+
/doc/
6+
/pkg/
7+
/spec/reports/
8+
/tmp/
9+
10+
# rspec failure tracking
11+
.rspec_status
12+
13+
Gemfile.lock

.rspec

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
--format documentation
2+
--color
3+
--require spec_helper

.standard.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# For available configuration options, see:
2+
# https://github.com/standardrb/standard
3+
ruby_version: 2.7

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog],
6+
and this project adheres to [Semantic Versioning].
7+
8+
## [Unreleased]
9+
10+
## [0.1.0] - 2024-08-02
11+
12+
- Initial release ([@skryukov])
13+
14+
[@skryukov]: https://github.com/skryukov
15+
16+
[Unreleased]: https://github.com/skryukov/typelizer/compare/v0.1.0...HEAD
17+
[0.1.0]: https://github.com/skryukov/typelizer/commits/v0.1.0
18+
19+
[Keep a Changelog]: https://keepachangelog.com/en/1.0.0/
20+
[Semantic Versioning]: https://semver.org/spec/v2.0.0.html

Gemfile

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# frozen_string_literal: true
2+
3+
source "https://rubygems.org"
4+
5+
gemspec
6+
7+
gem "rake", "~> 13.0"
8+
9+
gem "rspec", "~> 3.0"
10+
11+
gem "rspec-snapshot", "~> 2.0"
12+
13+
gem "standard", "~> 1.3"
14+
15+
gem "oj_serializers"
16+
17+
gem "active_model_serializers"
18+
19+
gem "alba"
20+
21+
# Rails app
22+
gem "rails", "~> 7.1.3"
23+
gem "sqlite3", "~> 1.4"
24+
gem "puma", ">= 5.0"
25+
gem "tzinfo-data", platforms: %i[windows jruby]
26+
gem "rspec-rails"

README.md

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
# Typelizer
2+
3+
[![Gem Version](https://badge.fury.io/rb/typelizer.svg)](https://rubygems.org/gems/typelizer)
4+
5+
Typelizer is a Ruby gem that automatically generates TypeScript interfaces from your Ruby serializers, bridging the gap between your Ruby backend and TypeScript frontend. It supports multiple serializer libraries and provides a flexible configuration system, making it easier to maintain type consistency across your full-stack application.
6+
7+
## Table of Contents
8+
9+
- [Features](#features)
10+
- [Installation](#installation)
11+
- [Usage](#usage)
12+
- [Basic Setup](#basic-setup)
13+
- [Manual Typing](#manual-typing)
14+
- [TypeScript Integration](#typescript-integration)
15+
- [Manual Generation](#manual-generation)
16+
- [Automatic Generation in Development](#automatic-generation-in-development)
17+
- [Configuration](#configuration)
18+
- [Global Configuration](#global-configuration)
19+
- [Config Options](#config-options)
20+
- [Per-Serializer Configuration](#per-serializer-configuration)
21+
- [Credits](#credits)
22+
- [License](#license)
23+
24+
<a href="https://evilmartians.com/?utm_source=typelizer&utm_campaign=project_page">
25+
<img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Built by Evil Martians" width="236" height="54">
26+
</a>
27+
28+
## Features
29+
30+
- Automatic TypeScript interface generation
31+
- Support for multiple serializer libraries (`Alba`, `ActiveModel::Serializer`, `Oj::Serializer`)
32+
- File watching and automatic regeneration in development
33+
34+
## Installation
35+
36+
To install Typelizer, add the following line to your `Gemfile` and run `bundle install`:
37+
38+
```ruby
39+
gem "typelizer"
40+
```
41+
42+
## Usage
43+
44+
### Basic Setup
45+
46+
Include the Typelizer DSL in your serializers:
47+
48+
```ruby
49+
class ApplicationResource
50+
include Alba::Resource
51+
include Typelizer::DSL
52+
end
53+
54+
class PostResource < ApplicationResource
55+
attributes :id, :title, :body
56+
57+
has_one :author, serializer: AuthorResource
58+
end
59+
60+
class AuthorResource < ApplicationResource
61+
# specify the model to infer types from (optional)
62+
typelize_from User
63+
64+
attributes :id, :name
65+
end
66+
```
67+
68+
Typelizer will automatically generate TypeScript interfaces based on your serializer definitions using information from your models.
69+
70+
### Manual Typing
71+
72+
You can manually specify TypeScript types in your serializers:
73+
74+
```ruby
75+
class PostResource < ApplicationResource
76+
attributes :id, :title, :body, :published_at
77+
78+
typelize "string"
79+
attribute :author_name do |post|
80+
post.author.name
81+
end
82+
end
83+
```
84+
85+
### TypeScript Integration
86+
87+
Typelizer generates TypeScript interfaces in the specified output directory:
88+
89+
```typescript
90+
// app/javascript/types/serializers/Post.ts
91+
export interface Post {
92+
id: number;
93+
title: string;
94+
body: string;
95+
published_at: string | null;
96+
author_name: string;
97+
}
98+
```
99+
100+
All generated interfaces are automatically imported in a single file:
101+
102+
```typescript
103+
// app/javascript/types/serializers/index.ts
104+
export * from "./post";
105+
export * from "./author";
106+
```
107+
108+
We recommend importing this file in a central location:
109+
110+
```typescript
111+
// app/javascript/types/index.ts
112+
import "@/types/serializers";
113+
// Custom types can be added here
114+
// ...
115+
```
116+
117+
With such a setup, you can import all generated interfaces in your TypeScript files:
118+
119+
```typescript
120+
import { Post } from "@/types";
121+
```
122+
123+
This setup also allows you to use custom types in your serializers:
124+
125+
```ruby
126+
class PostWithMetaResource < ApplicationResource
127+
attributes :id, :title
128+
typelize "PostMeta"
129+
attribute :meta do |post|
130+
{ likes: post.likes, comments: post.comments }
131+
end
132+
end
133+
```
134+
135+
```typescript
136+
// app/javascript/types/serializers/PostWithMeta.ts
137+
138+
import { PostMeta } from "@/types";
139+
140+
export interface Post {
141+
id: number;
142+
title: string;
143+
meta: PostMeta;
144+
}
145+
```
146+
147+
The `"@/types"` import path is configurable:
148+
149+
```ruby
150+
Typelizer.configure do |config|
151+
config.types_import_path = "@/types";
152+
end
153+
```
154+
155+
See the [Configuration](#configuration) section for more options.
156+
157+
### Manual Generation
158+
159+
To manually generate TypeScript interfaces:
160+
161+
```
162+
$ rails typelizer:generate
163+
```
164+
165+
### Automatic Generation in Development
166+
167+
When [Listen](https://github.com/guard/listen) is installed, Typelizer automatically watches for changes and regenerates interfaces in development mode. You can disable this behavior:
168+
169+
```ruby
170+
Typelizer.listen = false
171+
```
172+
173+
## Configuration
174+
175+
### Global Configuration
176+
177+
Typelizer provides several global configuration options:
178+
179+
```ruby
180+
# Directories to search for serializers:
181+
Typelizer.dirs = [Rails.root.join("app", "resources"), Rails.root.join("app", "serializers")]
182+
# Reject specific classes from being typelized:
183+
Typelizer.reject_class = ->(serializer:) { false }
184+
# Logger for debugging:
185+
Typelizer.logger = Logger.new($stdout, level: :info)
186+
# Force enable or disable file watching with Listen:
187+
Typelizer.listen = nil
188+
```
189+
190+
### Config Options
191+
192+
`Typelizer::Config` offers fine-grained control over the gem's behavior. Here's a list of available options:
193+
194+
```ruby
195+
Typelizer.configure do |config|
196+
# Determines how serializer names are mapped to TypeScript interface names
197+
config.serializer_name_mapper = ->(serializer) { ... }
198+
199+
# Maps serializers to their corresponding model classes
200+
config.serializer_model_mapper = ->(serializer) { ... }
201+
202+
# Custom transformation for generated properties
203+
config.properties_transformer = ->(properties) { ... }
204+
205+
# Plugin for model type inference (default: ModelPlugins::ActiveRecord)
206+
config.model_plugin = Typelizer::ModelPlugins::ActiveRecord
207+
208+
# Plugin for serializer parsing (default: SerializerPlugins::Auto)
209+
config.serializer_plugin = Typelizer::SerializerPlugins::Auto
210+
211+
# Additional configurations for specific plugins
212+
config.plugin_configs = { alba: { ts_mapper: {...} } }
213+
214+
# Custom DB to TypeScript type mapping
215+
config.type_mapping = config.type_mapping.merge(jsonb: "Record<string, undefined>", ... )
216+
217+
# Strategy for handling null values (:nullable, :optional, or :nullable_and_optional)
218+
config.null_strategy = :nullable
219+
220+
# Directory where TypeScript interfaces will be generated
221+
config.output_dir = Rails.root.join("app/javascript/types/serializers")
222+
223+
# Import path for generated types in TypeScript files
224+
# (e.g., `import { MyType } from "@/types"`)
225+
config.types_import_path = "@/types"
226+
227+
# List of type names that should be considered global in TypeScript
228+
# (i.e. not prefixed with the import path)
229+
config.types_global << %w[Array Date Record File FileList]
230+
end
231+
```
232+
233+
### Per-Serializer Configuration
234+
235+
You can also configure Typelizer on a per-serializer basis:
236+
237+
```ruby
238+
class PostResource < ApplicationResource
239+
typelizer_config do |config|
240+
config.type_mapping = config.type_mapping.merge(jsonb: "Record<string, undefined>", ... )
241+
config.null_strategy = :nullable
242+
# ...
243+
end
244+
end
245+
```
246+
247+
## Credits
248+
249+
Typelizer is inspired by [types_from_serializers](https://github.com/ElMassimo/types_from_serializers).
250+
251+
## License
252+
253+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).

Rakefile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# frozen_string_literal: true
2+
3+
require "bundler/gem_tasks"
4+
require "rspec/core/rake_task"
5+
6+
RSpec::Core::RakeTask.new(:spec)
7+
8+
require "standard/rake"
9+
10+
task default: %i[spec standard]

bin/console

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
require "bundler/setup"
5+
require "typelizer"
6+
7+
# You can add fixtures and/or initialization code here to make experimenting
8+
# with your gem easier. You can also use a different console, if you like.
9+
10+
require "irb"
11+
IRB.start(__FILE__)

0 commit comments

Comments
 (0)