Skip to content

Commit

Permalink
Initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
skryukov committed Aug 2, 2024
0 parents commit 5cf1cdd
Show file tree
Hide file tree
Showing 109 changed files with 2,952 additions and 0 deletions.
52 changes: 52 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Ruby

on:
push:
branches:
- main

pull_request:

jobs:
lint:
runs-on: ubuntu-latest
name: Linter
env:
BUNDLE_JOBS: 4
BUNDLE_RETRY: 3
steps:
- uses: actions/checkout@v3
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: "3.3"
bundler-cache: true
- name: Run StandardRB
run: bundle exec standardrb

build:
runs-on: ubuntu-latest
name: Ruby ${{ matrix.ruby }}
env:
BUNDLE_JOBS: 4
BUNDLE_RETRY: 3
strategy:
matrix:
ruby:
- "3.3"
- "3.2"
- "3.1"
- "3.0"
- "2.7"
- "jruby"
- "truffleruby"

steps:
- uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
- name: Run tests
run: bundle exec rspec
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/.bundle/
/.yardoc
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/

# rspec failure tracking
.rspec_status

Gemfile.lock
3 changes: 3 additions & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
--format documentation
--color
--require spec_helper
3 changes: 3 additions & 0 deletions .standard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# For available configuration options, see:
# https://github.com/standardrb/standard
ruby_version: 2.7
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog],
and this project adheres to [Semantic Versioning].

## [Unreleased]

## [0.1.0] - 2024-08-02

- Initial release ([@skryukov])

[@skryukov]: https://github.com/skryukov

[Unreleased]: https://github.com/skryukov/typelizer/compare/v0.1.0...HEAD
[0.1.0]: https://github.com/skryukov/typelizer/commits/v0.1.0

[Keep a Changelog]: https://keepachangelog.com/en/1.0.0/
[Semantic Versioning]: https://semver.org/spec/v2.0.0.html
26 changes: 26 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# frozen_string_literal: true

source "https://rubygems.org"

gemspec

gem "rake", "~> 13.0"

gem "rspec", "~> 3.0"

gem "rspec-snapshot", "~> 2.0"

gem "standard", "~> 1.3"

gem "oj_serializers"

gem "active_model_serializers"

gem "alba"

# Rails app
gem "rails", "~> 7.1.3"
gem "sqlite3", "~> 1.4"
gem "puma", ">= 5.0"
gem "tzinfo-data", platforms: %i[windows jruby]
gem "rspec-rails"
253 changes: 253 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
# Typelizer

[![Gem Version](https://badge.fury.io/rb/typelizer.svg)](https://rubygems.org/gems/typelizer)

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.

## Table of Contents

- [Features](#features)
- [Installation](#installation)
- [Usage](#usage)
- [Basic Setup](#basic-setup)
- [Manual Typing](#manual-typing)
- [TypeScript Integration](#typescript-integration)
- [Manual Generation](#manual-generation)
- [Automatic Generation in Development](#automatic-generation-in-development)
- [Configuration](#configuration)
- [Global Configuration](#global-configuration)
- [Config Options](#config-options)
- [Per-Serializer Configuration](#per-serializer-configuration)
- [Credits](#credits)
- [License](#license)

<a href="https://evilmartians.com/?utm_source=typelizer&utm_campaign=project_page">
<img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Built by Evil Martians" width="236" height="54">
</a>

## Features

- Automatic TypeScript interface generation
- Support for multiple serializer libraries (`Alba`, `ActiveModel::Serializer`, `Oj::Serializer`)
- File watching and automatic regeneration in development

## Installation

To install Typelizer, add the following line to your `Gemfile` and run `bundle install`:

```ruby
gem "typelizer"
```

## Usage

### Basic Setup

Include the Typelizer DSL in your serializers:

```ruby
class ApplicationResource
include Alba::Resource
include Typelizer::DSL
end

class PostResource < ApplicationResource
attributes :id, :title, :body

has_one :author, serializer: AuthorResource
end

class AuthorResource < ApplicationResource
# specify the model to infer types from (optional)
typelize_from User

attributes :id, :name
end
```

Typelizer will automatically generate TypeScript interfaces based on your serializer definitions using information from your models.

### Manual Typing

You can manually specify TypeScript types in your serializers:

```ruby
class PostResource < ApplicationResource
attributes :id, :title, :body, :published_at

typelize "string"
attribute :author_name do |post|
post.author.name
end
end
```

### TypeScript Integration

Typelizer generates TypeScript interfaces in the specified output directory:

```typescript
// app/javascript/types/serializers/Post.ts
export interface Post {
id: number;
title: string;
body: string;
published_at: string | null;
author_name: string;
}
```

All generated interfaces are automatically imported in a single file:

```typescript
// app/javascript/types/serializers/index.ts
export * from "./post";
export * from "./author";
```

We recommend importing this file in a central location:

```typescript
// app/javascript/types/index.ts
import "@/types/serializers";
// Custom types can be added here
// ...
```

With such a setup, you can import all generated interfaces in your TypeScript files:

```typescript
import { Post } from "@/types";
```

This setup also allows you to use custom types in your serializers:

```ruby
class PostWithMetaResource < ApplicationResource
attributes :id, :title
typelize "PostMeta"
attribute :meta do |post|
{ likes: post.likes, comments: post.comments }
end
end
```

```typescript
// app/javascript/types/serializers/PostWithMeta.ts

import { PostMeta } from "@/types";

export interface Post {
id: number;
title: string;
meta: PostMeta;
}
```

The `"@/types"` import path is configurable:

```ruby
Typelizer.configure do |config|
config.types_import_path = "@/types";
end
```

See the [Configuration](#configuration) section for more options.

### Manual Generation

To manually generate TypeScript interfaces:

```
$ rails typelizer:generate
```

### Automatic Generation in Development

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:

```ruby
Typelizer.listen = false
```

## Configuration

### Global Configuration

Typelizer provides several global configuration options:

```ruby
# Directories to search for serializers:
Typelizer.dirs = [Rails.root.join("app", "resources"), Rails.root.join("app", "serializers")]
# Reject specific classes from being typelized:
Typelizer.reject_class = ->(serializer:) { false }
# Logger for debugging:
Typelizer.logger = Logger.new($stdout, level: :info)
# Force enable or disable file watching with Listen:
Typelizer.listen = nil
```

### Config Options

`Typelizer::Config` offers fine-grained control over the gem's behavior. Here's a list of available options:

```ruby
Typelizer.configure do |config|
# Determines how serializer names are mapped to TypeScript interface names
config.serializer_name_mapper = ->(serializer) { ... }

# Maps serializers to their corresponding model classes
config.serializer_model_mapper = ->(serializer) { ... }

# Custom transformation for generated properties
config.properties_transformer = ->(properties) { ... }

# Plugin for model type inference (default: ModelPlugins::ActiveRecord)
config.model_plugin = Typelizer::ModelPlugins::ActiveRecord

# Plugin for serializer parsing (default: SerializerPlugins::Auto)
config.serializer_plugin = Typelizer::SerializerPlugins::Auto

# Additional configurations for specific plugins
config.plugin_configs = { alba: { ts_mapper: {...} } }

# Custom DB to TypeScript type mapping
config.type_mapping = config.type_mapping.merge(jsonb: "Record<string, undefined>", ... )

# Strategy for handling null values (:nullable, :optional, or :nullable_and_optional)
config.null_strategy = :nullable

# Directory where TypeScript interfaces will be generated
config.output_dir = Rails.root.join("app/javascript/types/serializers")

# Import path for generated types in TypeScript files
# (e.g., `import { MyType } from "@/types"`)
config.types_import_path = "@/types"

# List of type names that should be considered global in TypeScript
# (i.e. not prefixed with the import path)
config.types_global << %w[Array Date Record File FileList]
end
```

### Per-Serializer Configuration

You can also configure Typelizer on a per-serializer basis:

```ruby
class PostResource < ApplicationResource
typelizer_config do |config|
config.type_mapping = config.type_mapping.merge(jsonb: "Record<string, undefined>", ... )
config.null_strategy = :nullable
# ...
end
end
```

## Credits

Typelizer is inspired by [types_from_serializers](https://github.com/ElMassimo/types_from_serializers).

## License

The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
10 changes: 10 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

require "bundler/gem_tasks"
require "rspec/core/rake_task"

RSpec::Core::RakeTask.new(:spec)

require "standard/rake"

task default: %i[spec standard]
11 changes: 11 additions & 0 deletions bin/console
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

require "bundler/setup"
require "typelizer"

# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.

require "irb"
IRB.start(__FILE__)
Loading

0 comments on commit 5cf1cdd

Please sign in to comment.