Repository is archived
No release in over 3 years
Low commit activity in last 3 years
Generate TypeScript that includes routes definition and request / response JSON type from type signature of Rails controller actions.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

Runtime

<= 6.1.0.alpha, >= 6.0.3.1
< 0.7.0, >= 0.6.0
 Project Readme

RbsTsGenerator

Generate TypeScript that includes routes definition and request / response JSON type from type signature of Rails controller actions.

Sample repository: hanachin/rbs_ts_bbs

Usage

Write type signature of your controller actions in ruby/rbs.

# sig/app/controllers/boards_controller.rbs
class BoardsController < ::ApplicationController
  @board: Board
  @boards: Board::ActiveRecord_Relation

  def index: () -> Array[{ id: Integer, title: String }]
  def create: (String title) -> { id: Integer, message: String } | Array[String]
  def update: (Integer id, String title) -> { id: Integer, message: String } | Array[String]
  def destroy: (Integer id) -> { message: String }
end

The return type of the action method is type of json record. But action does not explicitly return json record. To pass the ruby type checking, add | void to each signatures.

class BoardsController < ::ApplicationController
  @board: Board
  @boards: Board::ActiveRecord_Relation

  def index: () -> (Array[{ id: Integer, title: String }] | void)
  def create: (String title) -> ({ id: Integer, message: String } | Array[String] | void)
  def update: (Integer id, String title) -> ({ id: Integer, message: String } | Array[String] | void)
  def destroy: (Integer id) -> ({ message: String } | void)
end

I use Steep to type checking the ruby code. Setup the Steepfile like following and run steep check.

# Steepfile
target :app do
  signature "sig"

  check "app"
  typing_options :strict
end
$ bundle exec steep check
[Steep 0.20.0] [target=app] [target#type_check(target_sources: [app/channels/application_cable/channel.rb, app/channels/application_cable/connection.rb, app/controllers/application_controller.rb, app/controllers/boards_controller.rb, app/helpers/application_helper.rb, app/helpers/boards_helper.rb, app/jobs/application_job.rb, app/mailboxes/application_mailbox.rb, app/mailers/application_mailer.rb, app/models/application_record.rb, app/models/board.rb], validate_signatures: true)] [synthesize:(1:1)] [synthesize:(2:3)] [synthesize:(2:3)] [(*::Symbol, ?model_name: ::string, **untyped) -> void] Method call with rest keywords type is detected. Rough approximation to be improved.

When you passed the ruby type check, next generate TypeScript from those signatures.

$ rails generate rbs_ts

This will generate those routes definition in app/javascript/packs/rbs_ts_routes.ts.

type BoardsUpdateParams = { id: number; title: string }
type BoardsDestroyParams = { id: number }
type BoardsIndexParams = {}
type BoardsCreateParams = { title: string }

type BoardsUpdateReturn = Exclude<{ url: string; message: string } | string[] | void, void>
type BoardsDestroyReturn = Exclude<{ url: string; message: string } | void, void>
type BoardsIndexReturn = Exclude<{ id: number; title: string }[] | void, void>
type BoardsCreateReturn = Exclude<{ url: string; message: string } | string[] | void, void>

export const boards = {
  path: ({ format }: any) => "/" + "boards" + (() => { try { return "." + (() => { if (format) return format; throw "format" })() } catch { return "" } })(),
  names: ["format"]
} as {
  path: (args: any) => string
  names: ["format"]
  Methods?: "GET" | "POST"
  Params?: {
    GET: BoardsIndexParams,
    POST: BoardsCreateParams
  }
  Return?: {
    GET: BoardsIndexReturn,
    POST: BoardsCreateReturn
  }
}
export const board = {
  path: ({ id, format }: any) => "/" + "boards" + "/" + (() => { if (id) return id; throw "id" })() + (() => { try { return "." + (() => { if (format) return format; throw "format" })() } catch { return "" } })(),
  names: ["id","format"]
} as {
  path: (args: any) => string
  names: ["id","format"]
  Methods?: "PATCH" | "PUT" | "DELETE"
  Params?: {
    PATCH: BoardsUpdateParams,
    PUT: BoardsUpdateParams,
    DELETE: BoardsDestroyParams
  }
  Return?: {
    PATCH: BoardsUpdateReturn,
    PUT: BoardsUpdateReturn,
    DELETE: BoardsDestroyReturn
  }
}

And generate default runtime in app/javascript/packs/rbs_ts_runtime.ts

In your TypeScript code, you can use those routes definition and the default runtime like following

import { boards } from './rbs_ts_routes'
import { railsApi } from './rbs_ts_runtime'

const params = { title: 'test' }
railsApi('POST', boards, params).then(({ json }) => {
  if (json instanceof Array) {
    return Promise.reject(json)
  } else {
    window.location.href = json.url
    return Promise.resolve()
  }
})

Installation

Add this line to your application's Gemfile:

gem 'rbs_ts_generator', group: :development

And then execute:

$ bundle

Or install it yourself as:

$ gem install rbs_ts_generator

Contributing

https://github.com/hanachin/rbs_ts_generator

License

The gem is available as open source under the terms of the MIT License.