├── .github └── workflows │ └── main.yml ├── .gitignore ├── .rspec ├── .standard.yml ├── CHANGELOG.md ├── Gemfile ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── lib ├── tasks │ └── generate.rake ├── typelizer.rb └── typelizer │ ├── config.rb │ ├── dsl.rb │ ├── generator.rb │ ├── interface.rb │ ├── listen.rb │ ├── model_plugins │ ├── active_record.rb │ ├── auto.rb │ └── poro.rb │ ├── property.rb │ ├── railtie.rb │ ├── renderer.rb │ ├── serializer_plugins │ ├── alba.rb │ ├── ams.rb │ ├── auto.rb │ ├── base.rb │ ├── oj_serializers.rb │ └── panko.rb │ ├── templates │ ├── comment.ts.erb │ ├── fingerprint.ts.erb │ ├── index.ts.erb │ ├── inheritance.ts.erb │ ├── inline_type.ts.erb │ └── interface.ts.erb │ ├── version.rb │ └── writer.rb ├── spec ├── __snapshots__ │ ├── AlbaArPost.ts.snap │ ├── AlbaArUser.ts.snap │ ├── AlbaInheritedCustomTypeUser.ts.snap │ ├── AlbaInheritedDeepUser.ts.snap │ ├── AlbaInheritedEmptyUser.ts.snap │ ├── AlbaInheritedExtendedUser.ts.snap │ ├── AlbaInheritedNestedRootUser.ts.snap │ ├── AlbaInheritedRootUser.ts.snap │ ├── AlbaInheritedTransformKeysUser.ts.snap │ ├── AlbaInline.ts.snap │ ├── AlbaMeta.ts.snap │ ├── AlbaMetaNil.ts.snap │ ├── AlbaPoro.ts.snap │ ├── AlbaPost.ts.snap │ ├── AlbaTransformKeys.ts.snap │ ├── AlbaUser.ts.snap │ ├── AlbaUserAuthor.ts.snap │ ├── AlbaUserEmptyNested.ts.snap │ ├── AlbaUserSerializerFoo.ts.snap │ ├── AlbaVerbatimModuleSyntax.ts.snap │ ├── AmsArPost.ts.snap │ ├── AmsArUser.ts.snap │ ├── AmsInheritedCustomTypeUser.ts.snap │ ├── AmsInheritedDeepUser.ts.snap │ ├── AmsInheritedEmptyUser.ts.snap │ ├── AmsInheritedExtendedUser.ts.snap │ ├── AmsInheritedTransformKeysUser.ts.snap │ ├── AmsPost.ts.snap │ ├── AmsTransformKeys.ts.snap │ ├── AmsUser.ts.snap │ ├── AmsUserAuthor.ts.snap │ ├── AmsUserEmptyNested.ts.snap │ ├── AmsUserSerializerFoo.ts.snap │ ├── AmsVerbatimModuleSyntax.ts.snap │ ├── OjSerializersArPost.ts.snap │ ├── OjSerializersArUser.ts.snap │ ├── OjSerializersFlatUser.ts.snap │ ├── OjSerializersInheritedCustomTypeUser.ts.snap │ ├── OjSerializersInheritedDeepUser.ts.snap │ ├── OjSerializersInheritedEmptyUser.ts.snap │ ├── OjSerializersInheritedExtendedUser.ts.snap │ ├── OjSerializersInheritedTransformKeysUser.ts.snap │ ├── OjSerializersPost.ts.snap │ ├── OjSerializersTransformKeys.ts.snap │ ├── OjSerializersUser.ts.snap │ ├── OjSerializersUserAuthor.ts.snap │ ├── OjSerializersUserEmptyNested.ts.snap │ ├── OjSerializersUserSerializerFoo.ts.snap │ ├── OjSerializersVerbatimModuleSyntax.ts.snap │ ├── PankoArPost.ts.snap │ ├── PankoArUser.ts.snap │ ├── PankoInheritedCustomTypeUser.ts.snap │ ├── PankoInheritedDeepUser.ts.snap │ ├── PankoInheritedEmptyUser.ts.snap │ ├── PankoInheritedExtendedUser.ts.snap │ ├── PankoPost.ts.snap │ ├── PankoUser.ts.snap │ ├── PankoUserAuthor.ts.snap │ ├── PankoUserEmptyNested.ts.snap │ ├── PankoUserSerializerFoo.ts.snap │ ├── PankoVerbatimModuleSyntax.ts.snap │ └── index.ts.snap ├── app │ ├── .dockerignore │ ├── .gitattributes │ ├── .gitignore │ ├── Gemfile │ ├── Rakefile │ ├── app │ │ ├── controllers │ │ │ ├── application_controller.rb │ │ │ └── concerns │ │ │ │ └── .keep │ │ ├── helpers │ │ │ └── application_helper.rb │ │ ├── models │ │ │ ├── application_record.rb │ │ │ ├── concerns │ │ │ │ └── .keep │ │ │ ├── poro.rb │ │ │ ├── post.rb │ │ │ └── user.rb │ │ ├── serializers │ │ │ ├── alba │ │ │ │ ├── ar │ │ │ │ │ ├── post_serializer.rb │ │ │ │ │ └── user_serializer.rb │ │ │ │ ├── base_serializer.rb │ │ │ │ ├── inherited │ │ │ │ │ ├── custom_type_user_serializer.rb │ │ │ │ │ ├── deep_user_serializer.rb │ │ │ │ │ ├── empty_user_serializer.rb │ │ │ │ │ ├── extended_user_serializer.rb │ │ │ │ │ ├── nested_root_user_serializer.rb │ │ │ │ │ ├── root_user_serializer.rb │ │ │ │ │ └── transform_keys_user_serializer.rb │ │ │ │ ├── inline_serializer.rb │ │ │ │ ├── meta_nil_serializer.rb │ │ │ │ ├── meta_serializer.rb │ │ │ │ ├── poro_serializer.rb │ │ │ │ ├── post_serializer.rb │ │ │ │ ├── transform_keys_serializer.rb │ │ │ │ ├── user │ │ │ │ │ ├── author_serializer.rb │ │ │ │ │ └── empty_nested_serializer.rb │ │ │ │ ├── user_serializer.rb │ │ │ │ └── verbatim_module_syntax_serializer.rb │ │ │ ├── ams │ │ │ │ ├── ar │ │ │ │ │ ├── post_serializer.rb │ │ │ │ │ └── user_serializer.rb │ │ │ │ ├── base_serializer.rb │ │ │ │ ├── inherited │ │ │ │ │ ├── custom_type_user_serializer.rb │ │ │ │ │ ├── deep_user_serializer.rb │ │ │ │ │ ├── empty_user_serializer.rb │ │ │ │ │ ├── extended_user_serializer.rb │ │ │ │ │ └── transform_keys_user_serializer.rb │ │ │ │ ├── post_serializer.rb │ │ │ │ ├── transform_keys_serializer.rb │ │ │ │ ├── user │ │ │ │ │ ├── author_serializer.rb │ │ │ │ │ └── empty_nested_serializer.rb │ │ │ │ ├── user_serializer.rb │ │ │ │ └── verbatim_module_syntax_serializer.rb │ │ │ ├── oj_serializers │ │ │ │ ├── ar │ │ │ │ │ ├── post_serializer.rb │ │ │ │ │ └── user_serializer.rb │ │ │ │ ├── base_serializer.rb │ │ │ │ ├── flat_user_serializer.rb │ │ │ │ ├── inherited │ │ │ │ │ ├── custom_type_user_serializer.rb │ │ │ │ │ ├── deep_user_serializer.rb │ │ │ │ │ ├── empty_user_serializer.rb │ │ │ │ │ ├── extended_user_serializer.rb │ │ │ │ │ └── transform_keys_user_serializer.rb │ │ │ │ ├── post_serializer.rb │ │ │ │ ├── transform_keys_serializer.rb │ │ │ │ ├── user │ │ │ │ │ ├── author_serializer.rb │ │ │ │ │ └── empty_nested_serializer.rb │ │ │ │ ├── user_serializer.rb │ │ │ │ └── verbatim_module_syntax_serializer.rb │ │ │ └── panko │ │ │ │ ├── ar │ │ │ │ ├── post_serializer.rb │ │ │ │ └── user_serializer.rb │ │ │ │ ├── base_serializer.rb │ │ │ │ ├── inherited │ │ │ │ ├── custom_type_user_serializer.rb │ │ │ │ ├── deep_user_serializer.rb │ │ │ │ ├── empty_user_serializer.rb │ │ │ │ └── extended_user_serializer.rb │ │ │ │ ├── post_serializer.rb │ │ │ │ ├── user │ │ │ │ ├── author_serializer.rb │ │ │ │ └── empty_nested_serializer.rb │ │ │ │ ├── user_serializer.rb │ │ │ │ └── verbatim_module_syntax_serializer.rb │ │ └── views │ │ │ └── layouts │ │ │ └── application.html.erb │ ├── bin │ │ ├── bundle │ │ ├── rails │ │ ├── rake │ │ └── setup │ ├── config.ru │ ├── config │ │ ├── application.rb │ │ ├── boot.rb │ │ ├── credentials.yml.enc │ │ ├── database.yml │ │ ├── environment.rb │ │ ├── environments │ │ │ ├── development.rb │ │ │ ├── production.rb │ │ │ └── test.rb │ │ ├── initializers │ │ │ ├── filter_parameter_logging.rb │ │ │ └── typelizer.rb │ │ ├── locales │ │ │ └── en.yml │ │ ├── puma.rb │ │ └── routes.rb │ ├── db │ │ ├── migrate │ │ │ ├── 20240707052900_create_users.rb │ │ │ └── 20240707052907_create_posts.rb │ │ ├── schema.rb │ │ └── seeds.rb │ ├── lib │ │ ├── assets │ │ │ └── .keep │ │ └── tasks │ │ │ └── .keep │ ├── log │ │ └── .keep │ ├── public │ │ ├── 404.html │ │ ├── 422.html │ │ ├── 500.html │ │ ├── apple-touch-icon-precomposed.png │ │ ├── apple-touch-icon.png │ │ ├── favicon.ico │ │ └── robots.txt │ └── storage │ │ └── .keep ├── spec_helper.rb └── typelizer_spec.rb └── typelizer.gemspec /.github/workflows/main.yml: -------------------------------------------------------------------------------- 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.4" 23 | bundler: latest 24 | bundler-cache: true 25 | - name: Run StandardRB 26 | run: bundle exec standardrb 27 | 28 | build: 29 | runs-on: ubuntu-latest 30 | name: Ruby ${{ matrix.ruby }} 31 | env: 32 | BUNDLE_JOBS: 4 33 | BUNDLE_RETRY: 3 34 | strategy: 35 | matrix: 36 | ruby: 37 | - "3.4" 38 | - "3.3" 39 | - "3.2" 40 | - "3.1" 41 | - "3.0" 42 | 43 | steps: 44 | - uses: actions/checkout@v4 45 | - name: Set up Ruby 46 | uses: ruby/setup-ruby@v1 47 | with: 48 | ruby-version: ${{ matrix.ruby }} 49 | bundler-cache: true 50 | bundler: latest 51 | - name: Setup dummy database 52 | run: spec/app/bin/setup 53 | - name: Run tests 54 | run: bundle exec rspec 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 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 14 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /.standard.yml: -------------------------------------------------------------------------------- 1 | # For available configuration options, see: 2 | # https://github.com/standardrb/standard 3 | ruby_version: 2.7 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 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 | ## [0.4.0] - 2025-05-03 9 | 10 | ### Added 11 | 12 | - Support for `panko_serializer` gem ([@PedroAugustoRamalhoDuarte], [@skryukov]) 13 | - Mark `has_one` and `belongs_to` association as nullable. ([@skryukov]) 14 | 15 | By default, `has_one` associations are marked as nullable in TypeScript interfaces. 16 | `belongs_to` associations are marked as nullable if the database column is nullable. 17 | Use the new `config.associations_strategy = :active_record` configuration option to mark associations as nullable based on the `required`/`optional` options. 18 | You can also use the type hint `typelize latest_post: {nullable: false}` in the serializer to override the defaults. 19 | 20 | - Support inherited typelization. ([@skryukov]) 21 | 22 | Set `config.inheritance_strategy = :inheritance` to make Typelizer respect the inheritance hierarchy of serializers: 23 | 24 | ```ruby 25 | class AdminSerializer < UserSerializer 26 | attributes :admin_level 27 | end 28 | ``` 29 | 30 | ```typescript 31 | // app/javascript/types/serializers/Admin.ts 32 | import { User } from "@/types"; 33 | 34 | export type Admin = User & { 35 | admin_level: number; 36 | } 37 | ``` 38 | 39 | ### Fixed 40 | 41 | - Alba: always use strings for keys in properties. ([@skryukov]) 42 | This change will fire update of all hashes for Alba serializers, but it's necessary to support inheritance strategy. 43 | 44 | ## [0.3.0] - 2025-02-28 45 | 46 | ### Added 47 | 48 | - Support transform keys. ([@patvice], [@skryukov]) 49 | 50 | Typelizer now respects `transform_keys`/`key_transform` configurations for all plugins. 51 | 52 | - Support typing method def in Alba. ([@patvice]) 53 | 54 | The `typelize` helper now can be used before a method definition: 55 | 56 | ```ruby 57 | class UserResource < ApplicationResource 58 | attributes :id, :name, :email, :chars_in_name 59 | 60 | typelize :number 61 | def chars_in_name(obj) 62 | obj.name.chars.count 63 | end 64 | end 65 | ``` 66 | 67 | - Support for deprecated attributes. ([@Envek]) 68 | 69 | They will be marked as deprecated using JSDoc [`@deprecated` tag](https://jsdoc.app/tags-deprecated) in TypeScript interface comments. 70 | 71 | In ActiveModel::Serializer attributes `deprecated` option is recognized. 72 | 73 | For other serializers, you can use `deprecated` option of `typelize` method. 74 | 75 | ### Fixed 76 | 77 | - Ignore `nil` values on fingerprint calculation. ([@Envek]) 78 | 79 | ## [0.2.0] - 2024-11-26 80 | 81 | ## Added 82 | 83 | - Add support for enum attributes declared using `ActiveRecord::Enum` or explicitly in serializers ([@Envek]) 84 | - Add support for comments in generated TypeScript interfaces ([@Envek]) 85 | - Add TypeScript verbatim module syntax support through `verbatim_module_syntax` config option ([@patvice]) 86 | - Add `typelizer:generate:refresh` command to clean output directory and regenerate all interfaces ([@patvice]) 87 | - Allow disabling Typelizer in Rails development with `DISABLE_TYPELIZER` environment variable to `true` ([@okuramasafumi]) 88 | - Allow to get interfaces without generating TypeScript files ([@Envek]) 89 | 90 | ## Fixed 91 | 92 | - Do not override `Typelizer.dirs` in the railtie initializer ([@patvice]) 93 | - Do not raise on empty nested serializers ([@skryukov]) 94 | - Attribute options merging in inherited serializers ([@Envek]) 95 | - Allow recursive type definition ([@okuramasafumi]) 96 | 97 | ## [0.1.5] - 2024-10-07 98 | 99 | ## Fixed 100 | 101 | - Fix the duplicated import with multiple same association ([@okuramasafumi]) 102 | 103 | ## [0.1.4] - 2024-10-04 104 | 105 | ## Added 106 | 107 | - PORO model plugin ([@okuramasafumi]) 108 | - Auto model plugin ([@skryukov]) 109 | 110 | ## [0.1.3] - 2024-09-27 111 | 112 | ## Added 113 | 114 | - Support inline associations ([@okuramasafumi], [@skryukov]) 115 | 116 | Example of Alba serializer with inline associations (note the `helper Typelizer::DSL`, see [Alba's docs](https://github.com/okuramasafumi/alba?tab=readme-ov-file#helper) for more details): 117 | 118 | ```ruby 119 | class FooSerializer 120 | include Alba::Resource 121 | helper Typelizer::DSL 122 | 123 | many :bars do 124 | typelize_from Bar 125 | 126 | attributes :id, :name 127 | end 128 | end 129 | ``` 130 | 131 | ## [0.1.2] - 2024-09-05 132 | 133 | ### Fixed 134 | 135 | - Prevent Alba's `meta nil` raising an error ([@okuramasafumi]) 136 | 137 | ## [0.1.1] - 2024-08-26 138 | 139 | ### Fixed 140 | 141 | - Failing method inspection ([@skryukov], [@davidrunger]) 142 | 143 | ## [0.1.0] - 2024-08-02 144 | 145 | - Initial release ([@skryukov]) 146 | 147 | [@davidrunger]: https://github.com/davidrunger 148 | [@Envek]: https://github.com/Envek 149 | [@okuramasafumi]: https://github.com/okuramasafumi 150 | [@patvice]: https://github.com/patvice 151 | [@skryukov]: https://github.com/skryukov 152 | 153 | [Unreleased]: https://github.com/skryukov/typelizer/compare/v0.4.0...HEAD 154 | [0.4.0]: https://github.com/skryukov/typelizer/compare/v0.3.0...v0.4.0 155 | [0.3.0]: https://github.com/skryukov/typelizer/compare/v0.2.0...v0.3.0 156 | [0.2.0]: https://github.com/skryukov/typelizer/compare/v0.1.5...v0.2.0 157 | [0.1.5]: https://github.com/skryukov/typelizer/compare/v0.1.4...v0.1.5 158 | [0.1.4]: https://github.com/skryukov/typelizer/compare/v0.1.3...v0.1.4 159 | [0.1.3]: https://github.com/skryukov/typelizer/compare/v0.1.2...v0.1.3 160 | [0.1.2]: https://github.com/skryukov/typelizer/compare/v0.1.1...v0.1.2 161 | [0.1.1]: https://github.com/skryukov/typelizer/compare/v0.1.0...v0.1.1 162 | [0.1.0]: https://github.com/skryukov/typelizer/commits/v0.1.0 163 | 164 | [Keep a Changelog]: https://keepachangelog.com/en/1.0.0/ 165 | [Semantic Versioning]: https://semver.org/spec/v2.0.0.html 166 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 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 | gem "panko_serializer" 22 | 23 | # Rails app 24 | gem "rails", "~> 7.1.3" 25 | gem "sqlite3", "~> 1.4" 26 | gem "puma", ">= 5.0" 27 | gem "tzinfo-data", platforms: %i[windows jruby] 28 | gem "rspec-rails" 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 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 | - [Disabling Typelizer](#disabling-typelizer) 18 | - [Configuration](#configuration) 19 | - [Global Configuration](#global-configuration) 20 | - [Config Options](#config-options) 21 | - [Per-Serializer Configuration](#per-serializer-configuration) 22 | - [Credits](#credits) 23 | - [License](#license) 24 | 25 | 26 | Built by Evil Martians 27 | 28 | 29 | ## Features 30 | 31 | - Automatic TypeScript interface generation 32 | - Support for multiple serializer libraries (`Alba`, `ActiveModel::Serializer`, `Oj::Serializer`, `Panko::Serializer`) 33 | - File watching and automatic regeneration in development 34 | 35 | ## Installation 36 | 37 | To install Typelizer, add the following line to your `Gemfile` and run `bundle install`: 38 | 39 | ```ruby 40 | gem "typelizer" 41 | ``` 42 | 43 | ## Usage 44 | 45 | ### Basic Setup 46 | 47 | Include the Typelizer DSL in your serializers: 48 | 49 | ```ruby 50 | class ApplicationResource 51 | include Alba::Resource 52 | include Typelizer::DSL 53 | 54 | # For Alba, we recommend using the `helper` method instead of `include`. 55 | # See the documentation: https://github.com/okuramasafumi/alba/blob/main/README.md#helper 56 | # helper Typelizer::DSL 57 | end 58 | 59 | class PostResource < ApplicationResource 60 | attributes :id, :title, :body 61 | 62 | has_one :author, serializer: AuthorResource 63 | end 64 | 65 | class AuthorResource < ApplicationResource 66 | # specify the model to infer types from (optional) 67 | typelize_from User 68 | 69 | attributes :id, :name 70 | end 71 | ``` 72 | 73 | Typelizer will automatically generate TypeScript interfaces based on your serializer definitions using information from your models. 74 | 75 | ### Manual Typing 76 | 77 | You can manually specify TypeScript types in your serializers: 78 | 79 | ```ruby 80 | class PostResource < ApplicationResource 81 | attributes :id, :title, :body, :published_at 82 | 83 | typelize "string" 84 | attribute :author_name do |post| 85 | post.author.name 86 | end 87 | 88 | typelize :string, nullable: true, comment: "Author's avatar URL" 89 | attribute :avatar do 90 | "https://example.com/avatar.png" if active? 91 | end 92 | end 93 | ``` 94 | 95 | `typelize` can be used with a Hash to specify multiple types at once. 96 | 97 | ```ruby 98 | class PostResource < ApplicationResource 99 | attributes :id, :title, :body, :published_at 100 | 101 | attribute :author_name do |post| 102 | post.author.name 103 | end 104 | 105 | typelize author_name: :string, published_at: :string 106 | end 107 | ``` 108 | 109 | You can also specify more complex type definitions using a lower-level API: 110 | 111 | ```ruby 112 | typelize attribute_name: ["string", "Date", optional: true, nullable: true, multi: true, enum: %w[foo bar], comment: "Attribute description", deprecated: "Use `another_attribute` instead"] 113 | ``` 114 | 115 | ### TypeScript Integration 116 | 117 | Typelizer generates TypeScript interfaces in the specified output directory: 118 | 119 | ```typescript 120 | // app/javascript/types/serializers/Post.ts 121 | export interface Post { 122 | id: number; 123 | title: string; 124 | category?: "news" | "article" | "blog" | null; 125 | body: string; 126 | published_at: string | null; 127 | author_name: string; 128 | } 129 | ``` 130 | 131 | All generated interfaces are automatically imported in a single file: 132 | 133 | ```typescript 134 | // app/javascript/types/serializers/index.ts 135 | export * from "./post"; 136 | export * from "./author"; 137 | ``` 138 | 139 | We recommend importing this file in a central location: 140 | 141 | ```typescript 142 | // app/javascript/types/index.ts 143 | import "@/types/serializers"; 144 | // Custom types can be added here 145 | // ... 146 | ``` 147 | 148 | With such a setup, you can import all generated interfaces in your TypeScript files: 149 | 150 | ```typescript 151 | import { Post } from "@/types"; 152 | ``` 153 | 154 | This setup also allows you to use custom types in your serializers: 155 | 156 | ```ruby 157 | class PostWithMetaResource < ApplicationResource 158 | attributes :id, :title 159 | typelize "PostMeta" 160 | attribute :meta do |post| 161 | { likes: post.likes, comments: post.comments } 162 | end 163 | end 164 | ``` 165 | 166 | ```typescript 167 | // app/javascript/types/serializers/PostWithMeta.ts 168 | 169 | import { PostMeta } from "@/types"; 170 | 171 | export interface Post { 172 | id: number; 173 | title: string; 174 | meta: PostMeta; 175 | } 176 | ``` 177 | 178 | The `"@/types"` import path is configurable: 179 | 180 | ```ruby 181 | Typelizer.configure do |config| 182 | config.types_import_path = "@/types"; 183 | end 184 | ``` 185 | 186 | See the [Configuration](#configuration) section for more options. 187 | 188 | ### Manual Generation 189 | 190 | To manually generate TypeScript interfaces use one of the following commands: 191 | 192 | ```bash 193 | # Generate new interfaces 194 | rails typelizer:generate 195 | 196 | # Clean output directory and regenerate all interfaces 197 | rails typelizer:generate:refresh 198 | ```` 199 | 200 | ### Automatic Generation in Development 201 | 202 | 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: 203 | 204 | ```ruby 205 | Typelizer.listen = false 206 | ``` 207 | 208 | ### Disabling Typelizer 209 | 210 | Sometimes we want to use Typelizer only with manual generation. To disable Typelizer during development, we can set `DISABLE_TYPELIZER` environment variable to `true`. This doesn't affect manual generation. 211 | 212 | ## Configuration 213 | 214 | ### Global Configuration 215 | 216 | Typelizer provides several global configuration options: 217 | 218 | ```ruby 219 | # Directories to search for serializers: 220 | Typelizer.dirs = [Rails.root.join("app", "resources"), Rails.root.join("app", "serializers")] 221 | # Reject specific classes from being typelized: 222 | Typelizer.reject_class = ->(serializer:) { false } 223 | # Logger for debugging: 224 | Typelizer.logger = Logger.new($stdout, level: :info) 225 | # Force enable or disable file watching with Listen: 226 | Typelizer.listen = nil 227 | ``` 228 | 229 | ### Config Options 230 | 231 | `Typelizer::Config` offers fine-grained control over the gem's behavior. Here's a list of available options: 232 | 233 | ```ruby 234 | Typelizer.configure do |config| 235 | # Determines how serializer names are mapped to TypeScript interface names 236 | config.serializer_name_mapper = ->(serializer) { ... } 237 | 238 | # Maps serializers to their corresponding model classes 239 | config.serializer_model_mapper = ->(serializer) { ... } 240 | 241 | # Custom transformation for generated properties 242 | config.properties_transformer = ->(properties) { ... } 243 | 244 | # Plugin for model type inference (default: ModelPlugins::Auto) 245 | config.model_plugin = Typelizer::ModelPlugins::Auto 246 | 247 | # Plugin for serializer parsing (default: SerializerPlugins::Auto) 248 | config.serializer_plugin = Typelizer::SerializerPlugins::Auto 249 | 250 | # Additional configurations for specific plugins 251 | config.plugin_configs = { alba: { ts_mapper: {...} } } 252 | 253 | # Custom DB to TypeScript type mapping 254 | config.type_mapping = config.type_mapping.merge(jsonb: "Record", ... ) 255 | 256 | # Strategy for handling null values (:nullable, :optional, or :nullable_and_optional) 257 | config.null_strategy = :nullable 258 | 259 | # Strategy for handling serializer inheritance (:none, :inheritance) 260 | # :none - lists all attributes of the serializer in the type 261 | # :inheritance - extends the type from the parent serializer 262 | config.inheritance_strategy = :none 263 | 264 | # Strategy for handling `has_one` and `belongs_to` associations nullability (:database, :active_record) 265 | # :database - uses the database column nullability 266 | # :active_record - uses the `required` / `optional` association options 267 | config.associations_strategy = :database 268 | 269 | # Directory where TypeScript interfaces will be generated 270 | config.output_dir = Rails.root.join("app/javascript/types/serializers") 271 | 272 | # Import path for generated types in TypeScript files 273 | # (e.g., `import { MyType } from "@/types"`) 274 | config.types_import_path = "@/types" 275 | 276 | # List of type names that should be considered global in TypeScript 277 | # (i.e. not prefixed with the import path) 278 | config.types_global << %w[Array Date Record File FileList] 279 | 280 | # Support TypeScript's Verbatim module syntax option (default: false) 281 | # Will change imports and exports of types from default to support this syntax option 282 | config.verbatim_module_syntax = false 283 | 284 | # Support comments in generated TypeScript interfaces (default: false) 285 | # Will add comments to the generated interfaces 286 | config.comments = false 287 | end 288 | ``` 289 | 290 | ### Per-Serializer Configuration 291 | 292 | You can also configure Typelizer on a per-serializer basis: 293 | 294 | ```ruby 295 | class PostResource < ApplicationResource 296 | typelizer_config do |config| 297 | config.type_mapping = config.type_mapping.merge(jsonb: "Record", ... ) 298 | config.null_strategy = :nullable 299 | # ... 300 | end 301 | end 302 | ``` 303 | 304 | ## Credits 305 | 306 | Typelizer is inspired by [types_from_serializers](https://github.com/ElMassimo/types_from_serializers). 307 | 308 | ## License 309 | 310 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 311 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 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] 11 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 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__) 12 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /lib/tasks/generate.rake: -------------------------------------------------------------------------------- 1 | namespace :typelizer do 2 | desc "Generate TypeScript interfaces from serializers" 3 | task generate: :environment do 4 | benchmark do 5 | Typelizer::Generator.call 6 | end 7 | end 8 | 9 | desc "Removes all files in output folder and refreshs all generate TypeScript interfaces from serializers" 10 | task "generate:refresh": :environment do 11 | benchmark do 12 | Typelizer::Generator.call(force: true) 13 | end 14 | end 15 | 16 | def benchmark(&block) 17 | require "benchmark" 18 | 19 | ENV["DISABLE_TYPELIZER"] = "false" 20 | 21 | puts "Generating TypeScript interfaces..." 22 | serializers = [] 23 | time = Benchmark.realtime do 24 | serializers = block.call 25 | end 26 | 27 | puts "Finished in #{time} seconds" 28 | puts "Found #{serializers.size} serializers:" 29 | puts serializers.map { |s| "\t#{s.name}" }.join("\n") 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/typelizer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "typelizer/version" 4 | require_relative "typelizer/config" 5 | require_relative "typelizer/property" 6 | require_relative "typelizer/interface" 7 | require_relative "typelizer/renderer" 8 | require_relative "typelizer/writer" 9 | require_relative "typelizer/generator" 10 | 11 | require_relative "typelizer/dsl" 12 | 13 | require_relative "typelizer/serializer_plugins/auto" 14 | require_relative "typelizer/serializer_plugins/oj_serializers" 15 | require_relative "typelizer/serializer_plugins/alba" 16 | require_relative "typelizer/serializer_plugins/ams" 17 | require_relative "typelizer/serializer_plugins/panko" 18 | 19 | require_relative "typelizer/model_plugins/active_record" 20 | require_relative "typelizer/model_plugins/poro" 21 | require_relative "typelizer/model_plugins/auto" 22 | 23 | require_relative "typelizer/railtie" if defined?(Rails) 24 | 25 | require "logger" 26 | 27 | module Typelizer 28 | class << self 29 | def enabled? 30 | return false if ENV["DISABLE_TYPELIZER"] == "true" || ENV["DISABLE_TYPELIZER"] == "1" 31 | 32 | ENV["RAILS_ENV"] == "development" || ENV["RACK_ENV"] == "development" || ENV["DISABLE_TYPELIZER"] == "false" 33 | end 34 | 35 | attr_accessor :dirs 36 | attr_accessor :reject_class 37 | attr_accessor :logger 38 | attr_accessor :listen 39 | 40 | # @private 41 | attr_reader :base_classes 42 | 43 | def configure 44 | yield Config 45 | end 46 | 47 | private 48 | 49 | attr_writer :base_classes 50 | end 51 | 52 | # Set in the Railtie 53 | self.dirs = [] 54 | self.reject_class = ->(serializer:) { false } 55 | self.logger = Logger.new($stdout, level: :info) 56 | self.listen = nil 57 | 58 | self.base_classes = Set.new 59 | end 60 | -------------------------------------------------------------------------------- /lib/typelizer/config.rb: -------------------------------------------------------------------------------- 1 | module Typelizer 2 | TYPE_MAPPING = { 3 | boolean: :boolean, 4 | date: :string, 5 | datetime: :string, 6 | decimal: :number, 7 | integer: :number, 8 | string: :string, 9 | text: :string, 10 | citext: :string 11 | }.tap do |types| 12 | types.default = :unknown 13 | end 14 | 15 | class Config < Struct.new( 16 | :serializer_name_mapper, 17 | :serializer_model_mapper, 18 | :properties_transformer, 19 | :model_plugin, 20 | :serializer_plugin, 21 | :plugin_configs, 22 | :type_mapping, 23 | :null_strategy, 24 | :output_dir, 25 | :types_import_path, 26 | :types_global, 27 | :verbatim_module_syntax, 28 | :inheritance_strategy, 29 | :associations_strategy, 30 | :comments, 31 | keyword_init: true 32 | ) do 33 | class << self 34 | def instance 35 | @instance ||= new( 36 | serializer_name_mapper: ->(serializer) do 37 | return "" if serializer.name.nil? 38 | 39 | serializer.name.ends_with?("Serializer") ? serializer.name&.delete_suffix("Serializer") : serializer.name&.delete_suffix("Resource") 40 | end, 41 | serializer_model_mapper: ->(serializer) do 42 | base_class = serializer_name_mapper.call(serializer) 43 | Object.const_get(base_class) if Object.const_defined?(base_class) 44 | end, 45 | 46 | model_plugin: ModelPlugins::Auto, 47 | serializer_plugin: SerializerPlugins::Auto, 48 | plugin_configs: {}, 49 | 50 | type_mapping: TYPE_MAPPING, 51 | null_strategy: :nullable, 52 | inheritance_strategy: :none, 53 | associations_strategy: :database, 54 | comments: false, 55 | 56 | output_dir: js_root.join("types/serializers"), 57 | 58 | types_import_path: "@/types", 59 | types_global: %w[Array Date Record File FileList], 60 | 61 | properties_transformer: nil, 62 | verbatim_module_syntax: false 63 | ) 64 | end 65 | 66 | private 67 | 68 | def js_root 69 | root_path = defined?(Rails) ? Rails.root : Pathname.pwd 70 | js_root = defined?(ViteRuby) ? ViteRuby.config.source_code_dir : "app/javascript" 71 | root_path.join(js_root) 72 | end 73 | 74 | def respond_to_missing?(name, include_private = false) 75 | Typelizer.respond_to?(name) || 76 | instance.respond_to?(name, include_private) 77 | end 78 | 79 | def method_missing(method, *args, &block) 80 | return Typelizer.send(method, *args, &block) if Typelizer.respond_to?(method) 81 | instance.send(method, *args, &block) 82 | end 83 | end 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /lib/typelizer/dsl.rb: -------------------------------------------------------------------------------- 1 | module Typelizer 2 | module DSL 3 | # typelize_from Model 4 | # typelize attribute_name: ["string", "Date", optional: true, nullable: true, multi: true] 5 | 6 | def self.included(base) 7 | Typelizer.base_classes << base.to_s if base.name 8 | base.extend(ClassMethods) 9 | end 10 | 11 | def self.extended(base) 12 | Typelizer.base_classes << base.to_s if base.name 13 | base.extend(ClassMethods) 14 | end 15 | 16 | module ClassMethods 17 | def typelizer_config 18 | @typelizer_config ||= 19 | begin 20 | parent_config = superclass.respond_to?(:typelizer_config) ? superclass.typelizer_config : Config 21 | Config.new(parent_config.to_h.transform_values(&:dup)) 22 | end 23 | yield @typelizer_config if block_given? 24 | @typelizer_config 25 | end 26 | 27 | def typelizer_interface 28 | @typelizer_interface ||= Interface.new(serializer: self) 29 | end 30 | 31 | # save association of serializer to model 32 | def typelize_from(model) 33 | return unless Typelizer.enabled? 34 | 35 | define_singleton_method(:_typelizer_model_name) { model } 36 | end 37 | 38 | # save association of serializer attributes to type 39 | # can be invoked multiple times 40 | def typelize(type = nil, type_params = {}, **attributes) 41 | if type 42 | @keyless_type = [type, type_params.merge(attributes)] 43 | else 44 | assign_type_information(:_typelizer_attributes, attributes) 45 | end 46 | end 47 | 48 | attr_accessor :keyless_type 49 | 50 | def typelize_meta(**attributes) 51 | assign_type_information(:_typelizer_meta_attributes, attributes) 52 | end 53 | 54 | private 55 | 56 | def assign_type_information(attribute_name, attributes) 57 | return unless Typelizer.enabled? 58 | 59 | instance_variable = "@#{attribute_name}" 60 | 61 | unless instance_variable_get(instance_variable) 62 | instance_variable_set(instance_variable, {}) 63 | end 64 | 65 | unless respond_to?(attribute_name) 66 | define_singleton_method(attribute_name) do 67 | result = instance_variable_get(instance_variable) || {} 68 | if superclass.respond_to?(attribute_name) 69 | result.merge(superclass.send(attribute_name)) do |key, currentdef, supervaldef| 70 | supervaldef.merge(currentdef) 71 | end 72 | else 73 | result 74 | end 75 | end 76 | end 77 | 78 | attributes.each do |name, attrs| 79 | next unless name 80 | 81 | attrs = [attrs] if attrs && !attrs.is_a?(Array) 82 | options = attrs.last.is_a?(Hash) ? attrs.pop : {} 83 | 84 | options[:type] = attrs.join(" | ") if attrs.any? 85 | instance_variable_get(instance_variable)[name.to_sym] ||= {} 86 | instance_variable_get(instance_variable)[name.to_sym].merge!(options) 87 | end 88 | end 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /lib/typelizer/generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Typelizer 4 | class Generator 5 | def self.call(**args) 6 | new.call(**args) 7 | end 8 | 9 | def initialize(config = Typelizer::Config) 10 | @config = config 11 | @writer = Writer.new 12 | end 13 | 14 | attr_reader :config, :writer 15 | 16 | def call(force: false) 17 | return unless Typelizer.enabled? 18 | 19 | writer.call(interfaces, force: force) 20 | 21 | interfaces 22 | end 23 | 24 | def interfaces 25 | @interfaces ||= begin 26 | read_serializers 27 | target_serializers.map(&:typelizer_interface).reject(&:empty?) 28 | end 29 | end 30 | 31 | private 32 | 33 | def target_serializers 34 | base_classes = Typelizer.base_classes.filter_map do |base_class| 35 | Object.const_get(base_class) if Object.const_defined?(base_class) 36 | end.tap do |base_classes| 37 | raise ArgumentError, "Please ensure all your serializers include Typelizer::DSL." if base_classes.none? 38 | end 39 | 40 | (base_classes + base_classes.flat_map(&:descendants)).uniq 41 | .reject { |serializer| Typelizer.reject_class.call(serializer: serializer) } 42 | .sort_by(&:name) 43 | end 44 | 45 | def read_serializers(files = nil) 46 | files ||= Typelizer.dirs.flat_map { |dir| Dir["#{dir}/**/*.rb"] } 47 | 48 | files.each do |file| 49 | trace = TracePoint.new(:call) do |tp| 50 | begin 51 | next unless tp.self.methods.include?(:typelizer_interface) 52 | rescue WeakRef::RefError 53 | next 54 | end 55 | serializer_plugin = tp.self.typelizer_interface.serializer_plugin 56 | 57 | if tp.callee_id.in?(serializer_plugin.methods_to_typelize) 58 | type, attrs = tp.self.keyless_type 59 | name = tp.binding.local_variable_get(:name) if tp.binding.local_variable_defined?(:name) 60 | tp.self.typelize(**serializer_plugin.typelize_method_transform(method: tp.callee_id, binding: tp.binding, name: name, type: type, attrs: attrs || {})) 61 | tp.self.keyless_type = nil 62 | end 63 | end 64 | 65 | trace.enable 66 | require file 67 | trace.disable 68 | end 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /lib/typelizer/interface.rb: -------------------------------------------------------------------------------- 1 | module Typelizer 2 | class Interface 3 | attr_reader :serializer, :serializer_plugin 4 | 5 | def config 6 | serializer.typelizer_config 7 | end 8 | 9 | def initialize(serializer:) 10 | @serializer = serializer 11 | @serializer_plugin = config.serializer_plugin.new(serializer: serializer, config: config) 12 | end 13 | 14 | def inline? 15 | !serializer.is_a?(Class) || serializer.name.nil? 16 | end 17 | 18 | def name 19 | if inline? 20 | Renderer.call("inline_type.ts.erb", properties: properties).strip 21 | else 22 | config.serializer_name_mapper.call(serializer).tr_s(":", "") 23 | end 24 | end 25 | 26 | def filename 27 | name.gsub("::", "/") 28 | end 29 | 30 | def root_key 31 | serializer_plugin.root_key 32 | end 33 | 34 | def empty? 35 | meta_fields.empty? && properties.empty? 36 | end 37 | 38 | def meta_fields 39 | @meta_fields ||= begin 40 | props = serializer_plugin.meta_fields || [] 41 | props = infer_types(props, :_typelizer_meta_attributes) 42 | props = config.properties_transformer.call(props) if config.properties_transformer 43 | props 44 | end 45 | end 46 | 47 | def properties 48 | @properties ||= begin 49 | props = serializer_plugin.properties 50 | props = infer_types(props) 51 | props = config.properties_transformer.call(props) if config.properties_transformer 52 | props 53 | end 54 | end 55 | 56 | def overwritten_properties 57 | return [] unless parent_interface 58 | 59 | @overwritten_properties ||= parent_interface.properties - properties 60 | end 61 | 62 | def own_properties 63 | @own_properties ||= properties - (parent_interface&.properties || []) 64 | end 65 | 66 | def properties_to_print 67 | parent_interface ? own_properties : properties 68 | end 69 | 70 | def parent_interface 71 | return if config.inheritance_strategy == :none 72 | return unless serializer.superclass.respond_to?(:typelizer_interface) 73 | 74 | interface = serializer.superclass.typelizer_interface 75 | return if interface.empty? 76 | 77 | interface 78 | end 79 | 80 | def imports 81 | @imports ||= begin 82 | association_serializers, attribute_types = properties_to_print.filter_map(&:type) 83 | .uniq 84 | .partition { |type| type.is_a?(Interface) } 85 | 86 | serializer_types = association_serializers 87 | .filter_map { |interface| interface.name if interface.name != name && !interface.inline? } 88 | 89 | custom_type_imports = attribute_types 90 | .flat_map { |type| extract_typescript_types(type.to_s) } 91 | .uniq 92 | .reject { |type| global_type?(type) } 93 | 94 | (custom_type_imports + serializer_types + Array(parent_interface&.name)).uniq - Array(self_type_name) 95 | end 96 | end 97 | 98 | def inspect 99 | "<#{self.class.name} #{name} properties=#{properties.inspect}>" 100 | end 101 | 102 | def fingerprint 103 | "<#{self.class.name} #{name} properties=[#{properties_to_print.map(&:fingerprint).join(", ")}]>" 104 | end 105 | 106 | private 107 | 108 | def self_type_name 109 | serializer.name.match(/(\w+::)?(\w+)(Serializer|Resource)/)[2] 110 | end 111 | 112 | def extract_typescript_types(type) 113 | type.split(/[<>\[\],\s|]+/) 114 | end 115 | 116 | def global_type?(type) 117 | type[0] == type[0].downcase || config.types_global.include?(type) 118 | end 119 | 120 | def infer_types(props, hash_name = :_typelizer_attributes) 121 | props.map do |prop| 122 | if serializer.respond_to?(hash_name) 123 | dsl_type = serializer.public_send(hash_name)[prop.column_name.to_sym] 124 | if dsl_type&.any? 125 | next Property.new(prop.to_h.merge(dsl_type)).tap do |property| 126 | property.comment ||= model_plugin.comment_for(property) if config.comments && property.comment != false 127 | property.enum ||= model_plugin.enum_for(property) if property.enum != false 128 | end 129 | end 130 | end 131 | 132 | model_plugin.infer_types(prop) 133 | end 134 | end 135 | 136 | def model_class 137 | return serializer._typelizer_model_name if serializer.respond_to?(:_typelizer_model_name) 138 | 139 | config.serializer_model_mapper.call(serializer) 140 | rescue NameError 141 | nil 142 | end 143 | 144 | def model_plugin 145 | @model_plugin ||= config.model_plugin.new(model_class: model_class, config: config) 146 | end 147 | end 148 | end 149 | -------------------------------------------------------------------------------- /lib/typelizer/listen.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Typelizer 4 | module Listen 5 | class << self 6 | attr_accessor :started 7 | 8 | def call( 9 | run_on_start: true, 10 | options: {}, 11 | &block 12 | ) 13 | return if started 14 | return unless Typelizer.enabled? 15 | 16 | @block = block 17 | @generator = Typelizer::Generator.new 18 | 19 | gem "listen" 20 | require "listen" 21 | 22 | self.started = true 23 | 24 | locales_dirs = Typelizer.dirs.filter(&:exist?).map { |path| File.expand_path(path) } 25 | 26 | relative_paths = locales_dirs.map { |path| relative_path(path) } 27 | 28 | debug("Watching #{relative_paths.inspect}") 29 | 30 | listener(locales_dirs.map(&:to_s), options).start 31 | @generator.call if run_on_start 32 | end 33 | 34 | def relative_path(path) 35 | root_path = defined?(Rails) ? Rails.root : Pathname.pwd 36 | Pathname.new(path).relative_path_from(root_path).to_s 37 | end 38 | 39 | def debug(message) 40 | Typelizer.logger.debug(message) 41 | end 42 | 43 | def listener(paths, options) 44 | ::Listen.to(*paths, options) do |changed, added, removed| 45 | changes = compute_changes(paths, changed, added, removed) 46 | 47 | next unless changes.any? 48 | 49 | debug(changes.map { |key, value| "#{key}=#{value.inspect}" }.join(", ")) 50 | 51 | @block.call 52 | end 53 | end 54 | 55 | def compute_changes(paths, changed, added, removed) 56 | paths = paths.map { |path| relative_path(path) } 57 | 58 | { 59 | changed: included_on_watched_paths(paths, changed), 60 | added: included_on_watched_paths(paths, added), 61 | removed: included_on_watched_paths(paths, removed) 62 | }.select { |_k, v| v.any? } 63 | end 64 | 65 | def included_on_watched_paths(paths, changes) 66 | changes.map { |change| relative_path(change) }.select do |change| 67 | paths.any? { |path| change.start_with?(path) } 68 | end 69 | end 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /lib/typelizer/model_plugins/active_record.rb: -------------------------------------------------------------------------------- 1 | module Typelizer 2 | module ModelPlugins 3 | class ActiveRecord 4 | def initialize(model_class:, config:) 5 | @model_class = model_class 6 | @config = config 7 | end 8 | 9 | attr_reader :model_class, :config 10 | 11 | def infer_types(prop) 12 | if (association = model_class&.reflect_on_association(prop.column_name.to_sym)) 13 | case association.macro 14 | when :belongs_to 15 | foreign_key = association.foreign_key 16 | column = model_class&.columns_hash&.dig(foreign_key.to_s) 17 | if config.associations_strategy == :database 18 | prop.nullable = column.null if column 19 | elsif config.associations_strategy == :active_record 20 | prop.nullable = !association.options[:required] || association.options[:optional] 21 | else 22 | raise "Unknown associations strategy: #{config.associations_strategy}" 23 | end 24 | when :has_one 25 | if config.associations_strategy == :database 26 | prop.nullable = true 27 | elsif config.associations_strategy == :active_record 28 | prop.nullable = !association.options[:required] 29 | else 30 | raise "Unknown associations strategy: #{config.associations_strategy}" 31 | end 32 | end 33 | return prop 34 | end 35 | 36 | column = model_class&.columns_hash&.dig(prop.column_name.to_s) 37 | return prop unless column 38 | 39 | prop.multi = !!column.try(:array) 40 | case config.null_strategy 41 | when :nullable 42 | prop.nullable = column.null 43 | when :optional 44 | prop.optional = column.null 45 | when :nullable_and_optional 46 | prop.nullable = column.null 47 | prop.optional = column.null 48 | else 49 | raise "Unknown null strategy: #{config.null_strategy}" 50 | end 51 | 52 | prop.type = @config.type_mapping[column.type] 53 | prop.comment = comment_for(prop) 54 | prop.enum = enum_for(prop) 55 | prop.type = :string if prop.enum # Ignore underlying column type for enums 56 | 57 | prop 58 | end 59 | 60 | def comment_for(prop) 61 | column = model_class&.columns_hash&.dig(prop.column_name.to_s) 62 | return nil unless column 63 | 64 | prop.comment = column.comment 65 | end 66 | 67 | def enum_for(prop) 68 | return unless model_class&.defined_enums&.key?(prop.column_name.to_s) 69 | 70 | prop.enum = model_class.defined_enums[prop.column_name.to_s].keys 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/typelizer/model_plugins/auto.rb: -------------------------------------------------------------------------------- 1 | module Typelizer 2 | module ModelPlugins 3 | module Auto 4 | class << self 5 | def new(model_class:, config:) 6 | plugin(model_class).new(model_class: model_class, config: config) 7 | end 8 | 9 | def plugin(model_class) 10 | if model_class && model_class < ::ActiveRecord::Base 11 | ActiveRecord 12 | else 13 | Poro 14 | end 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/typelizer/model_plugins/poro.rb: -------------------------------------------------------------------------------- 1 | module Typelizer 2 | module ModelPlugins 3 | class Poro 4 | # We don't care about initialization 5 | def initialize(...) 6 | end 7 | 8 | def infer_types(prop) 9 | prop 10 | end 11 | 12 | def comment_for(prop) 13 | nil 14 | end 15 | 16 | def enum_for(prop) 17 | nil 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/typelizer/property.rb: -------------------------------------------------------------------------------- 1 | module Typelizer 2 | Property = Struct.new( 3 | :name, :type, :optional, :nullable, 4 | :multi, :column_name, :comment, :enum, :deprecated, 5 | keyword_init: true 6 | ) do 7 | def inspect 8 | props = to_h.merge(type: type_name).map { |k, v| "#{k}=#{v.inspect}" }.join(" ") 9 | "<#{self.class.name} #{props}>" 10 | end 11 | 12 | def eql?(other) 13 | return false unless other.is_a?(self.class) 14 | 15 | fingerprint == other.fingerprint 16 | end 17 | 18 | def to_s 19 | type_str = type_name 20 | type_str = "Array<#{type_str}>" if multi 21 | type_str = "#{type_str} | null" if nullable 22 | 23 | "#{name}#{"?" if optional}: #{type_str}" 24 | end 25 | 26 | def fingerprint 27 | props = to_h.merge(type: type_name).reject { |_, v| v.nil? }.map { |k, v| "#{k}=#{v.inspect}" }.join(" ") 28 | "<#{self.class.name} #{props}>" 29 | end 30 | 31 | private 32 | 33 | def type_name 34 | return enum.map { |v| v.to_s.inspect }.join(" | ") if enum 35 | 36 | type.respond_to?(:name) ? type.name : type || "unknown" 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/typelizer/railtie.rb: -------------------------------------------------------------------------------- 1 | module Typelizer 2 | class Railtie < Rails::Railtie 3 | rake_tasks do 4 | load "tasks/generate.rake" 5 | end 6 | 7 | initializer "typelizer.configure" do 8 | Typelizer.configure do |c| 9 | if c.dirs.empty? 10 | c.dirs = [ 11 | Rails.root.join("app", "resources"), 12 | Rails.root.join("app", "serializers") 13 | ] 14 | end 15 | end 16 | end 17 | 18 | initializer "typelizer.generate" do |app| 19 | next unless Typelizer.enabled? 20 | 21 | generator = Typelizer::Generator.new 22 | 23 | if Typelizer.listen == true || Gem.loaded_specs["listen"] && Typelizer.listen != false 24 | require_relative "listen" 25 | app.config.after_initialize do 26 | Typelizer::Listen.call do 27 | Rails.application.reloader.reload! 28 | end 29 | end 30 | end 31 | 32 | app.config.to_prepare do 33 | generator.call 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/typelizer/renderer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "erb" 4 | 5 | module Typelizer 6 | class Renderer 7 | def self.call(template, **context) 8 | new(template).call(**context) 9 | end 10 | 11 | def initialize(template) 12 | @erb = ERB.new(File.read(File.join(File.dirname(__FILE__), "templates/#{template}")), trim_mode: "-") 13 | end 14 | 15 | def call(**context) 16 | b = binding 17 | context.each_pair do |key, value| 18 | b.local_variable_set(key, value) 19 | end 20 | erb.result(b) 21 | end 22 | 23 | private 24 | 25 | attr_reader :erb 26 | 27 | def indent(content, multiplier = 2) 28 | spaces = " " * multiplier 29 | content.to_s.each_line.map { |line| line.blank? ? line : "#{spaces}#{line}" }.join 30 | end 31 | 32 | def render(template, **context) 33 | Renderer.call(template, **context) 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/typelizer/serializer_plugins/alba.rb: -------------------------------------------------------------------------------- 1 | require_relative "base" 2 | 3 | module Typelizer 4 | module SerializerPlugins 5 | class Alba < Base 6 | ALBA_TS_MAPPER = { 7 | "String" => {type: :string}, 8 | "Integer" => {type: :number}, 9 | "Boolean" => {type: :boolean}, 10 | "ArrayOfString" => {type: :string, multi: true}, 11 | "ArrayOfInteger" => {type: :number, multi: true} 12 | } 13 | 14 | def properties 15 | serializer._attributes.map do |name, attr| 16 | build_property(name.is_a?(Symbol) ? name.name : name, attr) 17 | end 18 | end 19 | 20 | def methods_to_typelize 21 | [ 22 | :association, :one, :has_one, 23 | :many, :has_many, 24 | :attributes, :attribute, 25 | :method_added, 26 | :nested_attribute, :nested, 27 | :meta 28 | ] 29 | end 30 | 31 | def typelize_method_transform(method:, name:, binding:, type:, attrs:) 32 | if method == :method_added && binding.local_variable_defined?(:method_name) 33 | name = binding.local_variable_get(:method_name) 34 | end 35 | 36 | if [:many, :has_many].include?(method) 37 | return {name => [type, attrs.merge(multi: true)]} 38 | end 39 | 40 | super 41 | end 42 | 43 | def root_key 44 | root = serializer.new({}).send(:_key) 45 | if !root.nil? && has_transform_key?(serializer) && should_transform_root_key?(serializer) 46 | fetch_key(serializer, root) 47 | else 48 | root 49 | end 50 | end 51 | 52 | def meta_fields 53 | return nil unless serializer._meta 54 | 55 | name = serializer._meta.first 56 | return nil unless name 57 | 58 | [ 59 | build_property(name, name) 60 | ] 61 | end 62 | 63 | private 64 | 65 | def build_property(name, attr, **options) 66 | column_name = name 67 | 68 | if has_transform_key?(serializer) 69 | name = fetch_key(serializer, name) 70 | end 71 | 72 | case attr 73 | when Symbol 74 | Property.new( 75 | name: name, 76 | type: nil, 77 | optional: false, 78 | nullable: false, 79 | multi: false, 80 | column_name: column_name, 81 | **options 82 | ) 83 | when Proc 84 | Property.new( 85 | name: name, 86 | type: nil, 87 | optional: false, 88 | nullable: false, 89 | multi: false, 90 | column_name: column_name, 91 | **options 92 | ) 93 | when ::Alba::Association 94 | resource = attr.instance_variable_get(:@resource) 95 | Property.new( 96 | name: name, 97 | type: Interface.new(serializer: resource), 98 | optional: false, 99 | nullable: false, 100 | multi: false, # we override this in typelize_method_transform 101 | column_name: column_name, 102 | **options 103 | ) 104 | when ::Alba::TypedAttribute 105 | alba_type = attr.instance_variable_get(:@type) 106 | Property.new( 107 | name: name, 108 | optional: false, 109 | # not sure if that's a good default tbh 110 | nullable: !alba_type.instance_variable_get(:@auto_convert), 111 | multi: false, 112 | column_name: column_name, 113 | **ts_mapper[alba_type.name.to_s], 114 | **options 115 | ) 116 | when ::Alba::NestedAttribute 117 | Property.new( 118 | name: name, 119 | type: nil, 120 | optional: false, 121 | nullable: false, 122 | multi: false, 123 | column_name: column_name, 124 | **options 125 | ) 126 | when ::Alba::ConditionalAttribute 127 | build_property(name, attr.instance_variable_get(:@body), optional: true) 128 | else 129 | raise ArgumentError, "Unsupported attribute type: #{attr.class}" 130 | end 131 | end 132 | 133 | def has_transform_key?(serializer) 134 | serializer._transform_type != :none 135 | end 136 | 137 | def should_transform_root_key?(serializer) 138 | serializer._transforming_root_key 139 | end 140 | 141 | def fetch_key(serializer, key) 142 | ::Alba.transform_key(key, transform_type: serializer._transform_type) 143 | end 144 | 145 | private 146 | 147 | def ts_mapper 148 | config.plugin_configs.dig(:alba, :ts_mapper) || ALBA_TS_MAPPER 149 | end 150 | end 151 | end 152 | end 153 | -------------------------------------------------------------------------------- /lib/typelizer/serializer_plugins/ams.rb: -------------------------------------------------------------------------------- 1 | require_relative "base" 2 | 3 | module Typelizer 4 | module SerializerPlugins 5 | class AMS < Base 6 | def methods_to_typelize 7 | [ 8 | :has_many, :has_one, :belongs_to, 9 | :attribute, :attributes 10 | ] 11 | end 12 | 13 | def typelize_method_transform(method:, name:, binding:, type:, attrs:) 14 | return {binding.local_variable_get(:attr) => [type, attrs]} if method == :attribute 15 | 16 | super 17 | end 18 | 19 | def properties 20 | serializer._attributes_data.merge(serializer._reflections).flat_map do |key, association| 21 | type = association.options[:serializer] ? Interface.new(serializer: association.options[:serializer]) : nil 22 | adapter = ActiveModelSerializers::Adapter.configured_adapter 23 | Property.new( 24 | name: adapter.transform_key_casing!(key.to_s, association.options), 25 | type: type, 26 | optional: association.options.key?(:if) || association.options.key?(:unless), 27 | multi: association.respond_to?(:collection?) && association.collection?, 28 | deprecated: (association.options[:deprecated] if association.options.key?(:deprecated)), 29 | column_name: association.name.to_s 30 | ) 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/typelizer/serializer_plugins/auto.rb: -------------------------------------------------------------------------------- 1 | module Typelizer 2 | module SerializerPlugins 3 | module Auto 4 | class << self 5 | def new(serializer:, config:) 6 | plugin(serializer).new(serializer: serializer, config: config) 7 | end 8 | 9 | def plugin(serializer) 10 | if defined?(::Oj::Serializer) && serializer.ancestors.include?(::Oj::Serializer) 11 | OjSerializers 12 | elsif defined?(::Alba) && serializer.ancestors.include?(::Alba::Resource) 13 | Alba 14 | elsif defined?(ActiveModel::Serializer) && serializer.ancestors.include?(ActiveModel::Serializer) 15 | AMS 16 | elsif defined?(::Panko::Serializer) && serializer.ancestors.include?(::Panko::Serializer) 17 | Panko 18 | else 19 | raise "Can't guess serializer plugin for #{serializer}. " \ 20 | "Please specify it with `config.serializer_plugin`." 21 | end 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/typelizer/serializer_plugins/base.rb: -------------------------------------------------------------------------------- 1 | module Typelizer 2 | module SerializerPlugins 3 | class Base 4 | def initialize(serializer:, config:) 5 | @serializer = serializer 6 | @config = config 7 | end 8 | 9 | def root_key 10 | nil 11 | end 12 | 13 | def meta_fields 14 | nil 15 | end 16 | 17 | def typelize_method_transform(method:, name:, binding:, type:, attrs:) 18 | {name => [type, attrs]} 19 | end 20 | 21 | def methods_to_typelize 22 | [] 23 | end 24 | 25 | def properties 26 | [] 27 | end 28 | 29 | private 30 | 31 | attr_reader :serializer, :config 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/typelizer/serializer_plugins/oj_serializers.rb: -------------------------------------------------------------------------------- 1 | require_relative "base" 2 | 3 | module Typelizer 4 | module SerializerPlugins 5 | class OjSerializers < Base 6 | def methods_to_typelize 7 | [ 8 | :has_many, :has_one, :belongs_to, 9 | :flat_one, :attribute, :attributes 10 | ] 11 | end 12 | 13 | def properties 14 | transform_keys = serializer.try(:_transform_keys) 15 | attributes = serializer._attributes 16 | attributes = attributes.transform_keys(&transform_keys) if transform_keys 17 | 18 | attributes 19 | .flat_map do |key, options| 20 | if options[:association] == :flat 21 | Interface.new(serializer: options.fetch(:serializer)).properties 22 | else 23 | type = options[:serializer] ? Interface.new(serializer: options[:serializer]) : options[:type] 24 | Property.new( 25 | name: key, 26 | type: type, 27 | optional: options[:optional] || options.key?(:if), 28 | nullable: options[:nullable], 29 | multi: options[:association] == :many, 30 | column_name: options.fetch(:value_from) 31 | ) 32 | end 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/typelizer/serializer_plugins/panko.rb: -------------------------------------------------------------------------------- 1 | require_relative "base" 2 | 3 | module Typelizer 4 | module SerializerPlugins 5 | class Panko < Base 6 | def methods_to_typelize 7 | [:has_many, :has_one, :attributes, :method_added] 8 | end 9 | 10 | def properties 11 | descriptor = serializer.new.instance_variable_get(:@descriptor) 12 | attributes = descriptor.attributes 13 | methods_attributes = descriptor.method_fields 14 | has_many_associations = descriptor.has_many_associations 15 | has_one_associations = descriptor.has_one_associations 16 | 17 | attributes.map do |att| 18 | attribute_property(att) 19 | end + methods_attributes.map do |att| 20 | attribute_property(att) 21 | end + has_many_associations.map do |assoc| 22 | association_property(assoc, multi: true) 23 | end + has_one_associations.map do |assoc| 24 | association_property(assoc, multi: false) 25 | end 26 | end 27 | 28 | def typelize_method_transform(method:, name:, binding:, type:, attrs:) 29 | if method == :method_added && binding.local_variable_defined?(:method) 30 | name = binding.local_variable_get(:method) 31 | end 32 | 33 | super 34 | end 35 | 36 | private 37 | 38 | def attribute_property(att) 39 | Property.new( 40 | name: att.alias_name || att.name, 41 | optional: false, 42 | nullable: false, 43 | multi: false, 44 | column_name: att.name 45 | ) 46 | end 47 | 48 | def association_property(assoc, multi: false) 49 | key = assoc.name_str 50 | serializer = assoc.descriptor.type 51 | type = serializer ? Interface.new(serializer: serializer) : nil 52 | Property.new( 53 | name: key, 54 | type: type, 55 | optional: false, 56 | nullable: false, 57 | multi: multi, 58 | column_name: key 59 | ) 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/typelizer/templates/comment.ts.erb: -------------------------------------------------------------------------------- 1 | <% 2 | comment = "" 3 | comment += property.comment.to_s if interface.config.comments 4 | if property.deprecated 5 | comment += "\n@deprecated #{property.deprecated.is_a?(String) ? property.deprecated : ''}" 6 | end 7 | -%> 8 | <%= indent("/** #{comment.strip.split("\n").map(&:strip).join("\n * ")} */\n") unless comment.empty? -%> 9 | -------------------------------------------------------------------------------- /lib/typelizer/templates/fingerprint.ts.erb: -------------------------------------------------------------------------------- 1 | // Typelizer digest <%= Digest::MD5.hexdigest(fingerprint) %> 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | -------------------------------------------------------------------------------- /lib/typelizer/templates/index.ts.erb: -------------------------------------------------------------------------------- 1 | <%- interfaces.each do |interface| -%> 2 | <%- if interface.config.verbatim_module_syntax -%> 3 | export type { <%= interface.name %> } from './<%= interface.filename %>' 4 | <%- else -%> 5 | export type { default as <%= interface.name %> } from './<%= interface.filename %>' 6 | <%- end -%> 7 | <%- end -%> 8 | -------------------------------------------------------------------------------- /lib/typelizer/templates/inheritance.ts.erb: -------------------------------------------------------------------------------- 1 | <%= interface.overwritten_properties.any? ? "Omit<" : "" %><%= interface.parent_interface.name %><%= "['#{interface.parent_interface.root_key}']" if interface.parent_interface.root_key %><%= interface.overwritten_properties.any? ? ", " + interface.overwritten_properties.map { |pr| "'#{pr.name}'" }.join(' | ') + ">" : ""%> 2 | -------------------------------------------------------------------------------- /lib/typelizer/templates/inline_type.ts.erb: -------------------------------------------------------------------------------- 1 | { 2 | <%- properties.each do |property| -%> 3 | <%= indent(property) %>; 4 | <%- end -%> 5 | } 6 | -------------------------------------------------------------------------------- /lib/typelizer/templates/interface.ts.erb: -------------------------------------------------------------------------------- 1 | <% if interface.imports.any? -%> 2 | import type {<%= interface.imports.join(", ") %>} from '<%= interface.config.types_import_path %>' 3 | <% end -%> 4 | 5 | type <%= interface.name %><%= "Data" if interface.root_key %> = <%= 6 | render("inheritance.ts.erb", interface: interface).strip if interface.parent_interface 7 | -%> 8 | <% unless interface.parent_interface && interface.properties_to_print.empty? -%> 9 | <%= " & " if interface.parent_interface %>{ 10 | <% interface.properties_to_print.each do |property| -%> 11 | <%= render("comment.ts.erb", interface: interface, property: property) -%> 12 | <%= indent(property) %>; 13 | <% end -%> 14 | } 15 | <% end %><% if interface.root_key %> 16 | type <%= interface.name %> = { 17 | <%= indent(interface.root_key) %>: <%= interface.name %>Data; 18 | <% interface.meta_fields&.each do |property| -%> 19 | <%= indent(property) %>; 20 | <% end -%> 21 | } 22 | <% end -%> 23 | 24 | <% if interface.config.verbatim_module_syntax -%> 25 | export type { <%= interface.name %> }; 26 | <% else -%> 27 | export default <%= interface.name %>; 28 | <% end -%> 29 | -------------------------------------------------------------------------------- /lib/typelizer/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Typelizer 4 | VERSION = "0.4.0" 5 | end 6 | -------------------------------------------------------------------------------- /lib/typelizer/writer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "fileutils" 4 | 5 | module Typelizer 6 | class Writer 7 | def initialize 8 | @template_cache = {} 9 | @config = Config 10 | end 11 | 12 | attr_reader :config, :template_cache 13 | 14 | def call(interfaces, force:) 15 | cleanup_output_dir if force 16 | 17 | written_files = interfaces.map { |interface| write_interface(interface) } 18 | written_files << write_index(interfaces) 19 | 20 | existing_files = Dir[File.join(config.output_dir, "**/*.ts")] 21 | files_to_delete = existing_files - written_files 22 | 23 | File.delete(*files_to_delete) unless files_to_delete.empty? 24 | end 25 | 26 | private 27 | 28 | def write_index(interfaces) 29 | write_file("index.ts", interfaces.map(&:filename).join) do 30 | render_template("index.ts.erb", interfaces: interfaces) 31 | end 32 | end 33 | 34 | def write_interface(interface) 35 | write_file("#{interface.filename}.ts", interface.fingerprint) do 36 | render_template("interface.ts.erb", interface: interface) 37 | end 38 | end 39 | 40 | def write_file(filename, fingerprint) 41 | output_file = File.join(config.output_dir, filename) 42 | existing_content = File.exist?(output_file) ? File.read(output_file) : nil 43 | digest = render_template("fingerprint.ts.erb", fingerprint: fingerprint) 44 | 45 | return output_file if existing_content&.start_with?(digest) 46 | 47 | content = yield 48 | 49 | FileUtils.mkdir_p(File.dirname(output_file)) 50 | 51 | File.write(output_file, digest + content) 52 | output_file 53 | end 54 | 55 | def render_template(template, **context) 56 | template_cache[template] ||= Renderer.new(template) 57 | template_cache[template].call(**context) 58 | end 59 | 60 | def cleanup_output_dir 61 | FileUtils.rm_rf(config.output_dir) 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /spec/__snapshots__/AlbaArPost.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 3bc70e01a03431f2290729fcd38dcd0b 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {AlbaArUser} from '@/types' 5 | 6 | type AlbaArPost = { 7 | /** Unique identifier */ 8 | id: string; 9 | title?: string | null; 10 | user: AlbaArUser | null; 11 | } 12 | 13 | export default AlbaArPost; 14 | -------------------------------------------------------------------------------- /spec/__snapshots__/AlbaArUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 19208f72cf7116e4d31eece3db7ebfcf 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {AlbaArPost} from '@/types' 5 | 6 | type AlbaArUser = { 7 | /** Unique identifier */ 8 | id: string; 9 | username: string; 10 | invitor: AlbaArUser | null; 11 | posts: Array; 12 | latest_post: AlbaArPost; 13 | } 14 | 15 | export default AlbaArUser; 16 | -------------------------------------------------------------------------------- /spec/__snapshots__/AlbaInheritedCustomTypeUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 07496c17e996b72df90947b4d18b4940 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {AlbaUser} from '@/types' 5 | 6 | type AlbaInheritedCustomTypeUser = Omit & { 7 | /** Unique identifier */ 8 | id?: number | null; 9 | } 10 | 11 | export default AlbaInheritedCustomTypeUser; 12 | -------------------------------------------------------------------------------- /spec/__snapshots__/AlbaInheritedDeepUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 9b48b7a2e352cf6e892d9cd49fcdd9c2 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {AlbaInheritedExtendedUser} from '@/types' 5 | 6 | type AlbaInheritedDeepUser = AlbaInheritedExtendedUser & { 7 | updated_at: string; 8 | } 9 | 10 | export default AlbaInheritedDeepUser; 11 | -------------------------------------------------------------------------------- /spec/__snapshots__/AlbaInheritedEmptyUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 815ea7029b37d1acc0f9ee9495a4716e 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {AlbaUser} from '@/types' 5 | 6 | type AlbaInheritedEmptyUser = AlbaUser 7 | export default AlbaInheritedEmptyUser; 8 | -------------------------------------------------------------------------------- /spec/__snapshots__/AlbaInheritedExtendedUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 4e6d41fd64683f1a68258ace48c7adad 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {AlbaUser} from '@/types' 5 | 6 | type AlbaInheritedExtendedUser = AlbaUser & { 7 | full_name: string; 8 | } 9 | 10 | export default AlbaInheritedExtendedUser; 11 | -------------------------------------------------------------------------------- /spec/__snapshots__/AlbaInheritedNestedRootUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest ca9217fd14005db40f9eeae0dde33c05 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {AlbaInheritedRootUser} from '@/types' 5 | 6 | type AlbaInheritedNestedRootUserData = AlbaInheritedRootUser['root_user'] & { 7 | updated_at: string; 8 | } 9 | 10 | type AlbaInheritedNestedRootUser = { 11 | nested_root_user: AlbaInheritedNestedRootUserData; 12 | } 13 | 14 | export default AlbaInheritedNestedRootUser; 15 | -------------------------------------------------------------------------------- /spec/__snapshots__/AlbaInheritedRootUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 4ae310a1251d74e6f28b68d806dd3a5a 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {AlbaUser} from '@/types' 5 | 6 | type AlbaInheritedRootUserData = AlbaUser 7 | type AlbaInheritedRootUser = { 8 | root_user: AlbaInheritedRootUserData; 9 | } 10 | 11 | export default AlbaInheritedRootUser; 12 | -------------------------------------------------------------------------------- /spec/__snapshots__/AlbaInheritedTransformKeysUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 6fa6585a742153124518fb88de3d88c5 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {AlbaPost, AlbaUser} from '@/types' 5 | 6 | type AlbaInheritedTransformKeysUser = Omit & { 7 | /** This is sir name from the name */ 8 | sirName: string; 9 | latestPost: AlbaPost | null; 10 | firstName: string; 11 | } 12 | 13 | export default AlbaInheritedTransformKeysUser; 14 | -------------------------------------------------------------------------------- /spec/__snapshots__/AlbaInline.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 57897f9e0a00411b586bb6bf1cd3b911 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | 5 | type AlbaInline = { 6 | /** Unique identifier */ 7 | id: string; 8 | username: string; 9 | active: boolean; 10 | untyped_posts: Array<{ 11 | id: unknown; 12 | title: unknown; 13 | }>; 14 | posts: Array<{ 15 | id: number; 16 | title: string; 17 | }>; 18 | deep_posts: Array<{ 19 | id: number; 20 | title: string | null; 21 | user: { 22 | id: number; 23 | username: string; 24 | }; 25 | }>; 26 | } 27 | 28 | export default AlbaInline; 29 | -------------------------------------------------------------------------------- /spec/__snapshots__/AlbaMeta.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 295062293a0ef5e6554a5fdbb9a58e10 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | 5 | type AlbaMetaData = { 6 | username: string; 7 | full_name: string | null; 8 | address: {city: string, zipcode: string}; 9 | } 10 | 11 | type AlbaMeta = { 12 | meta: AlbaMetaData; 13 | metadata: {foo: 'bar'}; 14 | } 15 | 16 | export default AlbaMeta; 17 | -------------------------------------------------------------------------------- /spec/__snapshots__/AlbaMetaNil.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 5ee874bf4e3a3a2b3e9f283060188bec 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | 5 | type AlbaMetaNil = { 6 | /** Unique identifier */ 7 | id: string; 8 | } 9 | 10 | export default AlbaMetaNil; 11 | -------------------------------------------------------------------------------- /spec/__snapshots__/AlbaPoro.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 912a7b0b2fe99e9d6451c56612a2f28c 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | 5 | type AlbaPoro = { 6 | foo: unknown; 7 | bar: string | null; 8 | } 9 | 10 | export default AlbaPoro; 11 | -------------------------------------------------------------------------------- /spec/__snapshots__/AlbaPost.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 9690b24a4c3d40e97c7320071406ad34 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {AlbaUser} from '@/types' 5 | 6 | type AlbaPost = { 7 | id: number; 8 | title?: string | null; 9 | category?: "news" | "article" | "blog" | null; 10 | body?: string | null; 11 | published_at?: string | null; 12 | user: AlbaUser; 13 | next_post: Post; 14 | /** This is name 15 | * @deprecated */ 16 | name: string; 17 | } 18 | 19 | export default AlbaPost; 20 | -------------------------------------------------------------------------------- /spec/__snapshots__/AlbaTransformKeys.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 6a64f84afbb7e4fccf52c263834db403 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {AlbaUser, AlbaPost} from '@/types' 5 | 6 | type AlbaTransformKeys = { 7 | /** Unique identifier */ 8 | id: string | null; 9 | username: string; 10 | active: boolean; 11 | name: string; 12 | /** This is sir name from the name */ 13 | sirName: string; 14 | invitor: AlbaUser | null; 15 | posts: Array; 16 | latestPost: AlbaPost | null; 17 | firstName: string; 18 | createdAt: string; 19 | } 20 | 21 | export default AlbaTransformKeys; 22 | -------------------------------------------------------------------------------- /spec/__snapshots__/AlbaUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 65dcf9a1f4976005e107214c3f70175d 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {AlbaPost} from '@/types' 5 | 6 | type AlbaUser = { 7 | /** Unique identifier */ 8 | id: string | null; 9 | username: string; 10 | active: boolean; 11 | name: string; 12 | /** This is sir name from the name */ 13 | sir_name: string; 14 | invitor: AlbaUser | null; 15 | posts: Array; 16 | latest_post: AlbaPost | null; 17 | first_name: string; 18 | } 19 | 20 | export default AlbaUser; 21 | -------------------------------------------------------------------------------- /spec/__snapshots__/AlbaUserAuthor.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 058abbdfa7d9df7390be0b61cfb10989 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {AlbaPost, AlbaUser} from '@/types' 5 | 6 | type AlbaUserAuthor = { 7 | /** Unique identifier */ 8 | id: string; 9 | /** Author login handle */ 10 | username: string | null; 11 | posts?: Array; 12 | invitor: AlbaUser; 13 | avatar: unknown; 14 | /** Typed avatar URL 15 | * Active user only */ 16 | typed_avatar: string | null; 17 | } 18 | 19 | export default AlbaUserAuthor; 20 | -------------------------------------------------------------------------------- /spec/__snapshots__/AlbaUserEmptyNested.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 856a2762a106c17132aa659b4e062964 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {AlbaPost, AlbaUser} from '@/types' 5 | 6 | type AlbaUserEmptyNested = { 7 | /** Unique identifier */ 8 | id: string; 9 | /** Author login handle */ 10 | username: string | null; 11 | posts?: Array; 12 | invitor: AlbaUser; 13 | avatar: unknown; 14 | /** Typed avatar URL 15 | * Active user only */ 16 | typed_avatar: string | null; 17 | } 18 | 19 | export default AlbaUserEmptyNested; 20 | -------------------------------------------------------------------------------- /spec/__snapshots__/AlbaUserSerializerFoo.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 7c6fc8d7bf96e1d418d3a04e27d74014 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {AlbaUser, AlbaPost} from '@/types' 5 | 6 | type AlbaUserSerializerFoo = { 7 | /** Unique identifier */ 8 | id?: number | null; 9 | username: string; 10 | active: boolean; 11 | name: string; 12 | /** This is sir name from the name */ 13 | sir_name: string; 14 | invitor: AlbaUser | null; 15 | posts: Array; 16 | latest_post: AlbaPost | null; 17 | first_name: string; 18 | created_at: string; 19 | } 20 | 21 | export default AlbaUserSerializerFoo; 22 | -------------------------------------------------------------------------------- /spec/__snapshots__/AlbaVerbatimModuleSyntax.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 8c0e713748fd03eb3f5c738d02dd9e78 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | 5 | type AlbaVerbatimModuleSyntax = { 6 | /** Unique identifier */ 7 | id: string; 8 | username: string; 9 | } 10 | 11 | export type { AlbaVerbatimModuleSyntax }; 12 | -------------------------------------------------------------------------------- /spec/__snapshots__/AmsArPost.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest e17e9e5e27464ded626bedca9280369b 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {AmsArUser} from '@/types' 5 | 6 | type AmsArPost = { 7 | /** Unique identifier */ 8 | id: string; 9 | title?: string | null; 10 | user: AmsArUser | null; 11 | } 12 | 13 | export default AmsArPost; 14 | -------------------------------------------------------------------------------- /spec/__snapshots__/AmsArUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest f7defdf2bfebe6a978c9f2b0752370e7 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {AmsArPost} from '@/types' 5 | 6 | type AmsArUser = { 7 | /** Unique identifier */ 8 | id: string; 9 | username: string; 10 | invitor: AmsArUser | null; 11 | posts: Array; 12 | latest_post: AmsArPost; 13 | } 14 | 15 | export default AmsArUser; 16 | -------------------------------------------------------------------------------- /spec/__snapshots__/AmsInheritedCustomTypeUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 9cc1667984d1ebfe330e93e17a97334c 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {AmsUser} from '@/types' 5 | 6 | type AmsInheritedCustomTypeUser = Omit & { 7 | /** Unique identifier */ 8 | id?: number | null; 9 | } 10 | 11 | export default AmsInheritedCustomTypeUser; 12 | -------------------------------------------------------------------------------- /spec/__snapshots__/AmsInheritedDeepUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 7e81d4146ace24377e931eafa373c928 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {AmsInheritedExtendedUser} from '@/types' 5 | 6 | type AmsInheritedDeepUser = AmsInheritedExtendedUser & { 7 | updated_at: string; 8 | } 9 | 10 | export default AmsInheritedDeepUser; 11 | -------------------------------------------------------------------------------- /spec/__snapshots__/AmsInheritedEmptyUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 64321c41872e3c56d8f834786b650ed4 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {AmsUser} from '@/types' 5 | 6 | type AmsInheritedEmptyUser = AmsUser 7 | export default AmsInheritedEmptyUser; 8 | -------------------------------------------------------------------------------- /spec/__snapshots__/AmsInheritedExtendedUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 73161c24a4d3936ef276f6759ee38e87 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {AmsUser} from '@/types' 5 | 6 | type AmsInheritedExtendedUser = AmsUser & { 7 | full_name: string; 8 | } 9 | 10 | export default AmsInheritedExtendedUser; 11 | -------------------------------------------------------------------------------- /spec/__snapshots__/AmsInheritedTransformKeysUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 175e0128f2ff31a838f1086c74a0e4bb 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {AmsUser} from '@/types' 5 | 6 | type AmsInheritedTransformKeysUser = AmsUser & { 7 | createdAt: string; 8 | } 9 | 10 | export default AmsInheritedTransformKeysUser; 11 | -------------------------------------------------------------------------------- /spec/__snapshots__/AmsPost.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest fed90a2a0067ee99be1389af39b259ea 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {AmsUser} from '@/types' 5 | 6 | type AmsPost = { 7 | id: number; 8 | title?: string | null; 9 | category?: "news" | "article" | "blog" | null; 10 | body?: string | null; 11 | published_at?: string | null; 12 | /** @deprecated Use 'title' instead. */ 13 | name: string; 14 | user: AmsUser; 15 | } 16 | 17 | export default AmsPost; 18 | -------------------------------------------------------------------------------- /spec/__snapshots__/AmsTransformKeys.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 53c3aebe239b343f0b0743a9632d1e09 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {AmsUser, AmsPost} from '@/types' 5 | 6 | type AmsTransformKeys = { 7 | /** Unique identifier */ 8 | id: string | null; 9 | username: string; 10 | active: boolean; 11 | name: string; 12 | first_name: string; 13 | createdAt: string; 14 | invitor: AmsUser | null; 15 | posts: Array; 16 | } 17 | 18 | export default AmsTransformKeys; 19 | -------------------------------------------------------------------------------- /spec/__snapshots__/AmsUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest fd33bc6a70a8ed8dd2ccf0563e53e4d8 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {AmsPost} from '@/types' 5 | 6 | type AmsUser = { 7 | /** Unique identifier */ 8 | id: string | null; 9 | username: string; 10 | active: boolean; 11 | name: string; 12 | first_name: string; 13 | invitor: AmsUser | null; 14 | posts: Array; 15 | } 16 | 17 | export default AmsUser; 18 | -------------------------------------------------------------------------------- /spec/__snapshots__/AmsUserAuthor.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 76917cd789c2c5deb2d09b685f633e1e 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {AmsPost} from '@/types' 5 | 6 | type AmsUserAuthor = { 7 | /** Unique identifier */ 8 | id: string; 9 | /** Author login handle */ 10 | username: string | null; 11 | avatar: unknown; 12 | typed_avatar: string | null; 13 | posts?: Array; 14 | } 15 | 16 | export default AmsUserAuthor; 17 | -------------------------------------------------------------------------------- /spec/__snapshots__/AmsUserEmptyNested.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 7135c395958101112cb5fe8808117388 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {AmsPost} from '@/types' 5 | 6 | type AmsUserEmptyNested = { 7 | /** Unique identifier */ 8 | id: string; 9 | /** Author login handle */ 10 | username: string | null; 11 | avatar: unknown; 12 | typed_avatar: string | null; 13 | posts?: Array; 14 | } 15 | 16 | export default AmsUserEmptyNested; 17 | -------------------------------------------------------------------------------- /spec/__snapshots__/AmsUserSerializerFoo.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 4f711d79f31e9635ef16174621ce7ac0 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {AmsUser, AmsPost} from '@/types' 5 | 6 | type AmsUserSerializerFoo = { 7 | /** Unique identifier */ 8 | id?: number | null; 9 | username: string; 10 | active: boolean; 11 | name: string; 12 | first_name: string; 13 | created_at: string; 14 | invitor: AmsUser | null; 15 | posts: Array; 16 | } 17 | 18 | export default AmsUserSerializerFoo; 19 | -------------------------------------------------------------------------------- /spec/__snapshots__/AmsVerbatimModuleSyntax.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 42601a39caafcfbfc24ebecb8ec213ed 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | 5 | type AmsVerbatimModuleSyntax = { 6 | /** Unique identifier */ 7 | id: string; 8 | username: string; 9 | } 10 | 11 | export type { AmsVerbatimModuleSyntax }; 12 | -------------------------------------------------------------------------------- /spec/__snapshots__/OjSerializersArPost.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 8bde7778ea00cb0d0c8fa59ddc034c6b 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {OjSerializersArUser} from '@/types' 5 | 6 | type OjSerializersArPost = { 7 | /** Unique identifier */ 8 | id: string; 9 | title?: string | null; 10 | user: OjSerializersArUser | null; 11 | } 12 | 13 | export default OjSerializersArPost; 14 | -------------------------------------------------------------------------------- /spec/__snapshots__/OjSerializersArUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 9cd6196d38c0f63888098e817649c005 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {OjSerializersArPost} from '@/types' 5 | 6 | type OjSerializersArUser = { 7 | /** Unique identifier */ 8 | id: string; 9 | username: string; 10 | invitor: OjSerializersArUser | null; 11 | posts: Array; 12 | latest_post: OjSerializersArPost; 13 | } 14 | 15 | export default OjSerializersArUser; 16 | -------------------------------------------------------------------------------- /spec/__snapshots__/OjSerializersFlatUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 8ca60c4cafff50bb27bdd26d8533152b 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {OjSerializersUser, OjSerializersPost} from '@/types' 5 | 6 | type OjSerializersFlatUser = { 7 | /** Unique identifier */ 8 | id: string; 9 | username: string; 10 | active: boolean; 11 | name: string; 12 | invitor: OjSerializersUser | null; 13 | posts: Array; 14 | first_name: string; 15 | } 16 | 17 | export default OjSerializersFlatUser; 18 | -------------------------------------------------------------------------------- /spec/__snapshots__/OjSerializersInheritedCustomTypeUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest e221ccf74c0c13438d2673d8b79a9180 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {OjSerializersUser} from '@/types' 5 | 6 | type OjSerializersInheritedCustomTypeUser = Omit & { 7 | /** Unique identifier */ 8 | id?: number; 9 | } 10 | 11 | export default OjSerializersInheritedCustomTypeUser; 12 | -------------------------------------------------------------------------------- /spec/__snapshots__/OjSerializersInheritedDeepUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest e3e6693b0c30c604f4515ba8c8555353 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {OjSerializersInheritedExtendedUser} from '@/types' 5 | 6 | type OjSerializersInheritedDeepUser = OjSerializersInheritedExtendedUser & { 7 | updated_at: string; 8 | } 9 | 10 | export default OjSerializersInheritedDeepUser; 11 | -------------------------------------------------------------------------------- /spec/__snapshots__/OjSerializersInheritedEmptyUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest c1d5d9847711b51c5baf4ca873eb5a44 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {OjSerializersUser} from '@/types' 5 | 6 | type OjSerializersInheritedEmptyUser = OjSerializersUser 7 | export default OjSerializersInheritedEmptyUser; 8 | -------------------------------------------------------------------------------- /spec/__snapshots__/OjSerializersInheritedExtendedUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 19fb42198dcc43526838631805dd34d2 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {OjSerializersUser} from '@/types' 5 | 6 | type OjSerializersInheritedExtendedUser = OjSerializersUser & { 7 | full_name: string; 8 | } 9 | 10 | export default OjSerializersInheritedExtendedUser; 11 | -------------------------------------------------------------------------------- /spec/__snapshots__/OjSerializersInheritedTransformKeysUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 4f44b26c2565dc07f7cbee6fdfd68176 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {OjSerializersUser} from '@/types' 5 | 6 | type OjSerializersInheritedTransformKeysUser = Omit & { 7 | firstName: string; 8 | } 9 | 10 | export default OjSerializersInheritedTransformKeysUser; 11 | -------------------------------------------------------------------------------- /spec/__snapshots__/OjSerializersPost.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 49b173359c44e5d76adebe1e429422b2 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {OjSerializersUser} from '@/types' 5 | 6 | type OjSerializersPost = { 7 | id: number; 8 | title?: string | null; 9 | category?: "news" | "article" | "blog" | null; 10 | body?: string | null; 11 | published_at?: string | null; 12 | user: OjSerializersUser; 13 | /** @deprecated Use 'title' instead. */ 14 | name: string; 15 | } 16 | 17 | export default OjSerializersPost; 18 | -------------------------------------------------------------------------------- /spec/__snapshots__/OjSerializersTransformKeys.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 32c416ec97e9c98986b48a9d014fce24 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {OjSerializersUser, OjSerializersPost} from '@/types' 5 | 6 | type OjSerializersTransformKeys = { 7 | /** Unique identifier */ 8 | id: string; 9 | username: string; 10 | active: boolean; 11 | name: string; 12 | invitor: OjSerializersUser | null; 13 | posts: Array; 14 | firstName: string; 15 | createdAt: string; 16 | } 17 | 18 | export default OjSerializersTransformKeys; 19 | -------------------------------------------------------------------------------- /spec/__snapshots__/OjSerializersUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 2bd66e161ece1b7322abcece7dfd6340 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {OjSerializersPost} from '@/types' 5 | 6 | type OjSerializersUser = { 7 | /** Unique identifier */ 8 | id: string; 9 | username: string; 10 | active: boolean; 11 | name: string; 12 | invitor: OjSerializersUser | null; 13 | posts: Array; 14 | first_name: string; 15 | } 16 | 17 | export default OjSerializersUser; 18 | -------------------------------------------------------------------------------- /spec/__snapshots__/OjSerializersUserAuthor.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 0e0e127674bb9d0759a9cf31c45e9546 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {OjSerializersPost} from '@/types' 5 | 6 | type OjSerializersUserAuthor = { 7 | /** Unique identifier */ 8 | id: string; 9 | /** Author login handle */ 10 | username: string | null; 11 | posts?: Array; 12 | avatar: unknown; 13 | typed_avatar: string | null; 14 | } 15 | 16 | export default OjSerializersUserAuthor; 17 | -------------------------------------------------------------------------------- /spec/__snapshots__/OjSerializersUserEmptyNested.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 672abb444029862dca53baac2d10e77d 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {OjSerializersPost} from '@/types' 5 | 6 | type OjSerializersUserEmptyNested = { 7 | /** Unique identifier */ 8 | id: string; 9 | /** Author login handle */ 10 | username: string | null; 11 | posts?: Array; 12 | avatar: unknown; 13 | typed_avatar: string | null; 14 | } 15 | 16 | export default OjSerializersUserEmptyNested; 17 | -------------------------------------------------------------------------------- /spec/__snapshots__/OjSerializersUserSerializerFoo.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 6dfd066f698c6b21c692b9b6c0f7613d 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {OjSerializersUser, OjSerializersPost} from '@/types' 5 | 6 | type OjSerializersUserSerializerFoo = { 7 | /** Unique identifier */ 8 | id: string; 9 | username: string; 10 | active: boolean; 11 | name: string; 12 | invitor: OjSerializersUser | null; 13 | posts: Array; 14 | first_name: string; 15 | created_at: string; 16 | } 17 | 18 | export default OjSerializersUserSerializerFoo; 19 | -------------------------------------------------------------------------------- /spec/__snapshots__/OjSerializersVerbatimModuleSyntax.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 778702a5b905940a6944a178c23b0a29 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | 5 | type OjSerializersVerbatimModuleSyntax = { 6 | /** Unique identifier */ 7 | id: string; 8 | username: string; 9 | } 10 | 11 | export type { OjSerializersVerbatimModuleSyntax }; 12 | -------------------------------------------------------------------------------- /spec/__snapshots__/PankoArPost.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest abf7dbcfe4a14086e540eb48d1c051bb 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {PankoArUser} from '@/types' 5 | 6 | type PankoArPost = { 7 | /** Unique identifier */ 8 | id: string; 9 | title?: string | null; 10 | user: PankoArUser | null; 11 | } 12 | 13 | export default PankoArPost; 14 | -------------------------------------------------------------------------------- /spec/__snapshots__/PankoArUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 60ba7e7ef8f900858624b0a80ad12240 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {PankoArPost} from '@/types' 5 | 6 | type PankoArUser = { 7 | /** Unique identifier */ 8 | id: string; 9 | username: string; 10 | posts: Array; 11 | invitor: PankoArUser | null; 12 | latest_post: PankoArPost; 13 | } 14 | 15 | export default PankoArUser; 16 | -------------------------------------------------------------------------------- /spec/__snapshots__/PankoInheritedCustomTypeUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 148a7e4ca111f45e37ce8e43f4c0eed8 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {PankoUser} from '@/types' 5 | 6 | type PankoInheritedCustomTypeUser = Omit & { 7 | /** Unique identifier */ 8 | id?: number | null; 9 | } 10 | 11 | export default PankoInheritedCustomTypeUser; 12 | -------------------------------------------------------------------------------- /spec/__snapshots__/PankoInheritedDeepUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 57e40f780198060a83767b644812bb2d 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {PankoInheritedExtendedUser} from '@/types' 5 | 6 | type PankoInheritedDeepUser = PankoInheritedExtendedUser & { 7 | updated_at: string; 8 | } 9 | 10 | export default PankoInheritedDeepUser; 11 | -------------------------------------------------------------------------------- /spec/__snapshots__/PankoInheritedEmptyUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 0e88d05864b985105504ebc1aadb8bca 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {PankoUser} from '@/types' 5 | 6 | type PankoInheritedEmptyUser = PankoUser 7 | export default PankoInheritedEmptyUser; 8 | -------------------------------------------------------------------------------- /spec/__snapshots__/PankoInheritedExtendedUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest b882e08c3f8828b1b51d43f3cba57d47 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {PankoUser} from '@/types' 5 | 6 | type PankoInheritedExtendedUser = PankoUser & { 7 | full_name: string; 8 | } 9 | 10 | export default PankoInheritedExtendedUser; 11 | -------------------------------------------------------------------------------- /spec/__snapshots__/PankoPost.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest b5b6398ec4dafb1944ddc7754cfe1f55 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {PankoUser} from '@/types' 5 | 6 | type PankoPost = { 7 | id: number; 8 | title?: string | null; 9 | category?: "news" | "article" | "blog" | null; 10 | body?: string | null; 11 | published_at?: string | null; 12 | user: PankoUser; 13 | } 14 | 15 | export default PankoPost; 16 | -------------------------------------------------------------------------------- /spec/__snapshots__/PankoUser.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest c2a938244ea957c47ddd86ece1dd9f47 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {PankoPost} from '@/types' 5 | 6 | type PankoUser = { 7 | /** Unique identifier */ 8 | id: string | null; 9 | username: string; 10 | active: boolean; 11 | name: string; 12 | first_name: string; 13 | posts: Array; 14 | invitor: PankoUser | null; 15 | latest_post: PankoPost | null; 16 | } 17 | 18 | export default PankoUser; 19 | -------------------------------------------------------------------------------- /spec/__snapshots__/PankoUserAuthor.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest fa6b902e904d31f5da7ceafaefa8f644 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {PankoPost} from '@/types' 5 | 6 | type PankoUserAuthor = { 7 | /** Unique identifier */ 8 | id: string; 9 | /** Author login handle */ 10 | username: string | null; 11 | avatar: unknown; 12 | typed_avatar: string | null; 13 | posts: Array; 14 | } 15 | 16 | export default PankoUserAuthor; 17 | -------------------------------------------------------------------------------- /spec/__snapshots__/PankoUserEmptyNested.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 3c0d37869384e60571eb45b4abf8df57 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {PankoPost} from '@/types' 5 | 6 | type PankoUserEmptyNested = { 7 | /** Unique identifier */ 8 | id: string; 9 | /** Author login handle */ 10 | username: string | null; 11 | avatar: unknown; 12 | typed_avatar: string | null; 13 | posts: Array; 14 | } 15 | 16 | export default PankoUserEmptyNested; 17 | -------------------------------------------------------------------------------- /spec/__snapshots__/PankoUserSerializerFoo.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest cf5e10bb8f594b912b46d1b4e5d27fc2 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | import type {PankoPost, PankoUser} from '@/types' 5 | 6 | type PankoUserSerializerFoo = { 7 | /** Unique identifier */ 8 | id?: number | null; 9 | username: string; 10 | active: boolean; 11 | name: string; 12 | created_at: string; 13 | first_name: string; 14 | posts: Array; 15 | invitor: PankoUser | null; 16 | latest_post: PankoPost | null; 17 | } 18 | 19 | export default PankoUserSerializerFoo; 20 | -------------------------------------------------------------------------------- /spec/__snapshots__/PankoVerbatimModuleSyntax.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 38ece3b6e4814b4e1a2216a744744ea0 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | 5 | type PankoVerbatimModuleSyntax = { 6 | /** Unique identifier */ 7 | id: string; 8 | username: string; 9 | } 10 | 11 | export type { PankoVerbatimModuleSyntax }; 12 | -------------------------------------------------------------------------------- /spec/__snapshots__/index.ts.snap: -------------------------------------------------------------------------------- 1 | // Typelizer digest 978438f359ff1ab294cb1155fe7ca604 2 | // 3 | // DO NOT MODIFY: This file was automatically generated by Typelizer. 4 | export type { default as AlbaArPost } from './AlbaArPost' 5 | export type { default as AlbaArUser } from './AlbaArUser' 6 | export type { default as AlbaInheritedCustomTypeUser } from './AlbaInheritedCustomTypeUser' 7 | export type { default as AlbaInheritedDeepUser } from './AlbaInheritedDeepUser' 8 | export type { default as AlbaInheritedEmptyUser } from './AlbaInheritedEmptyUser' 9 | export type { default as AlbaInheritedExtendedUser } from './AlbaInheritedExtendedUser' 10 | export type { default as AlbaInheritedNestedRootUser } from './AlbaInheritedNestedRootUser' 11 | export type { default as AlbaInheritedRootUser } from './AlbaInheritedRootUser' 12 | export type { default as AlbaInheritedTransformKeysUser } from './AlbaInheritedTransformKeysUser' 13 | export type { default as AlbaInline } from './AlbaInline' 14 | export type { default as AlbaMetaNil } from './AlbaMetaNil' 15 | export type { default as AlbaMeta } from './AlbaMeta' 16 | export type { default as AlbaPoro } from './AlbaPoro' 17 | export type { default as AlbaPost } from './AlbaPost' 18 | export type { default as AlbaTransformKeys } from './AlbaTransformKeys' 19 | export type { default as AlbaUserAuthor } from './AlbaUserAuthor' 20 | export type { default as AlbaUserEmptyNested } from './AlbaUserEmptyNested' 21 | export type { default as AlbaUser } from './AlbaUser' 22 | export type { default as AlbaUserSerializerFoo } from './AlbaUserSerializerFoo' 23 | export type { AlbaVerbatimModuleSyntax } from './AlbaVerbatimModuleSyntax' 24 | export type { default as AmsArPost } from './AmsArPost' 25 | export type { default as AmsArUser } from './AmsArUser' 26 | export type { default as AmsInheritedCustomTypeUser } from './AmsInheritedCustomTypeUser' 27 | export type { default as AmsInheritedDeepUser } from './AmsInheritedDeepUser' 28 | export type { default as AmsInheritedEmptyUser } from './AmsInheritedEmptyUser' 29 | export type { default as AmsInheritedExtendedUser } from './AmsInheritedExtendedUser' 30 | export type { default as AmsInheritedTransformKeysUser } from './AmsInheritedTransformKeysUser' 31 | export type { default as AmsPost } from './AmsPost' 32 | export type { default as AmsTransformKeys } from './AmsTransformKeys' 33 | export type { default as AmsUserAuthor } from './AmsUserAuthor' 34 | export type { default as AmsUserEmptyNested } from './AmsUserEmptyNested' 35 | export type { default as AmsUser } from './AmsUser' 36 | export type { default as AmsUserSerializerFoo } from './AmsUserSerializerFoo' 37 | export type { AmsVerbatimModuleSyntax } from './AmsVerbatimModuleSyntax' 38 | export type { default as OjSerializersArPost } from './OjSerializersArPost' 39 | export type { default as OjSerializersArUser } from './OjSerializersArUser' 40 | export type { default as OjSerializersFlatUser } from './OjSerializersFlatUser' 41 | export type { default as OjSerializersInheritedCustomTypeUser } from './OjSerializersInheritedCustomTypeUser' 42 | export type { default as OjSerializersInheritedDeepUser } from './OjSerializersInheritedDeepUser' 43 | export type { default as OjSerializersInheritedEmptyUser } from './OjSerializersInheritedEmptyUser' 44 | export type { default as OjSerializersInheritedExtendedUser } from './OjSerializersInheritedExtendedUser' 45 | export type { default as OjSerializersInheritedTransformKeysUser } from './OjSerializersInheritedTransformKeysUser' 46 | export type { default as OjSerializersPost } from './OjSerializersPost' 47 | export type { default as OjSerializersTransformKeys } from './OjSerializersTransformKeys' 48 | export type { default as OjSerializersUserAuthor } from './OjSerializersUserAuthor' 49 | export type { default as OjSerializersUserEmptyNested } from './OjSerializersUserEmptyNested' 50 | export type { default as OjSerializersUser } from './OjSerializersUser' 51 | export type { default as OjSerializersUserSerializerFoo } from './OjSerializersUserSerializerFoo' 52 | export type { OjSerializersVerbatimModuleSyntax } from './OjSerializersVerbatimModuleSyntax' 53 | export type { default as PankoArPost } from './PankoArPost' 54 | export type { default as PankoArUser } from './PankoArUser' 55 | export type { default as PankoInheritedCustomTypeUser } from './PankoInheritedCustomTypeUser' 56 | export type { default as PankoInheritedDeepUser } from './PankoInheritedDeepUser' 57 | export type { default as PankoInheritedEmptyUser } from './PankoInheritedEmptyUser' 58 | export type { default as PankoInheritedExtendedUser } from './PankoInheritedExtendedUser' 59 | export type { default as PankoPost } from './PankoPost' 60 | export type { default as PankoUserAuthor } from './PankoUserAuthor' 61 | export type { default as PankoUserEmptyNested } from './PankoUserEmptyNested' 62 | export type { default as PankoUser } from './PankoUser' 63 | export type { default as PankoUserSerializerFoo } from './PankoUserSerializerFoo' 64 | export type { PankoVerbatimModuleSyntax } from './PankoVerbatimModuleSyntax' 65 | -------------------------------------------------------------------------------- /spec/app/.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files. 2 | 3 | # Ignore git directory. 4 | /.git/ 5 | 6 | # Ignore bundler config. 7 | /.bundle 8 | 9 | # Ignore all environment files (except templates). 10 | /.env* 11 | !/.env*.erb 12 | 13 | # Ignore all default key files. 14 | /config/master.key 15 | /config/credentials/*.key 16 | 17 | # Ignore all logfiles and tempfiles. 18 | /log/* 19 | /tmp/* 20 | !/log/.keep 21 | !/tmp/.keep 22 | 23 | # Ignore pidfiles, but keep the directory. 24 | /tmp/pids/* 25 | !/tmp/pids/.keep 26 | 27 | # Ignore storage (uploaded files in development and any SQLite databases). 28 | /storage/* 29 | !/storage/.keep 30 | /tmp/storage/* 31 | !/tmp/storage/.keep 32 | 33 | # Ignore assets. 34 | /node_modules/ 35 | /app/assets/builds/* 36 | !/app/assets/builds/.keep 37 | /public/assets 38 | -------------------------------------------------------------------------------- /spec/app/.gitattributes: -------------------------------------------------------------------------------- 1 | # See https://git-scm.com/docs/gitattributes for more about git attribute files. 2 | 3 | # Mark the database schema as having been generated. 4 | db/schema.rb linguist-generated 5 | 6 | # Mark any vendored files as having been vendored. 7 | vendor/* linguist-vendored 8 | config/credentials/*.yml.enc diff=rails_credentials 9 | config/credentials.yml.enc diff=rails_credentials 10 | -------------------------------------------------------------------------------- /spec/app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all environment files (except templates). 11 | /.env* 12 | !/.env*.erb 13 | 14 | # Ignore all logfiles and tempfiles. 15 | /log/* 16 | /tmp/* 17 | !/log/.keep 18 | !/tmp/.keep 19 | 20 | # Ignore pidfiles, but keep the directory. 21 | /tmp/pids/* 22 | !/tmp/pids/ 23 | !/tmp/pids/.keep 24 | 25 | # Ignore storage (uploaded files in development and any SQLite databases). 26 | /storage/* 27 | !/storage/.keep 28 | /tmp/storage/* 29 | !/tmp/storage/ 30 | !/tmp/storage/.keep 31 | 32 | /public/assets 33 | 34 | # Ignore master key for decrypting credentials and more. 35 | /config/master.key 36 | 37 | Gemfile.lock 38 | -------------------------------------------------------------------------------- /spec/app/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | ruby ">= 3.0" 4 | gem "rails", "~> 7.1.3" 5 | gem "sqlite3", "~> 1.4" 6 | gem "puma", ">= 5.0" 7 | gem "tzinfo-data", platforms: %i[windows jruby] 8 | 9 | gem "oj_serializers" 10 | gem "alba" 11 | gem "active_model_serializers" 12 | gem "panko_serializer" 13 | 14 | gem "typelizer", path: "../.." 15 | 16 | gem "rspec-rails" 17 | -------------------------------------------------------------------------------- /spec/app/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative "config/application" 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /spec/app/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | end 3 | -------------------------------------------------------------------------------- /spec/app/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skryukov/typelizer/ca67584ae3d903194966b1628a806573402ebec6/spec/app/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /spec/app/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /spec/app/app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | primary_abstract_class 3 | end 4 | -------------------------------------------------------------------------------- /spec/app/app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skryukov/typelizer/ca67584ae3d903194966b1628a806573402ebec6/spec/app/app/models/concerns/.keep -------------------------------------------------------------------------------- /spec/app/app/models/poro.rb: -------------------------------------------------------------------------------- 1 | class Poro 2 | def foo 3 | "This is foo method" 4 | end 5 | 6 | def as_json 7 | {foo: foo} 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/app/app/models/post.rb: -------------------------------------------------------------------------------- 1 | class Post < ApplicationRecord 2 | belongs_to :user, optional: true 3 | 4 | enum category: {news: 1, article: 2, blog: 3} 5 | 6 | def next_post 7 | # Returns Post 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/app/app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ApplicationRecord 2 | belongs_to :invitor, class_name: "User" 3 | 4 | has_many :friends, class_name: "User", foreign_key: :invitor_id 5 | has_many :posts 6 | has_one :latest_post, class_name: "Post", foreign_key: :user_id, required: true 7 | end 8 | -------------------------------------------------------------------------------- /spec/app/app/serializers/alba/ar/post_serializer.rb: -------------------------------------------------------------------------------- 1 | module Alba 2 | module Ar 3 | class PostSerializer < BaseSerializer 4 | typelize_from ::Post 5 | 6 | attributes :id, :title 7 | 8 | has_one :user, serializer: UserSerializer 9 | 10 | typelizer_config.associations_strategy = :active_record 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/app/app/serializers/alba/ar/user_serializer.rb: -------------------------------------------------------------------------------- 1 | module Alba 2 | module Ar 3 | class UserSerializer < BaseSerializer 4 | typelize_from ::User 5 | attributes :id, :username 6 | 7 | has_one :invitor, serializer: UserSerializer 8 | 9 | has_many :posts, serializer: PostSerializer 10 | has_one :latest_post, serializer: PostSerializer 11 | 12 | typelizer_config.associations_strategy = :active_record 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/app/app/serializers/alba/base_serializer.rb: -------------------------------------------------------------------------------- 1 | module Alba 2 | class BaseSerializer 3 | include Alba::Resource 4 | helper Typelizer::DSL 5 | 6 | typelizer_config.null_strategy = :nullable_and_optional 7 | 8 | typelize id: [:string, comment: "Unique identifier"] 9 | def id 10 | Base64.urlsafe_encode64(object.id.to_s) 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/app/app/serializers/alba/inherited/custom_type_user_serializer.rb: -------------------------------------------------------------------------------- 1 | module Alba 2 | module Inherited 3 | class CustomTypeUserSerializer < UserSerializer 4 | typelizer_config.inheritance_strategy = :inheritance 5 | 6 | typelize id: [:number, optional: true] 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/app/app/serializers/alba/inherited/deep_user_serializer.rb: -------------------------------------------------------------------------------- 1 | module Alba 2 | module Inherited 3 | class DeepUserSerializer < ExtendedUserSerializer 4 | attributes :updated_at 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/app/app/serializers/alba/inherited/empty_user_serializer.rb: -------------------------------------------------------------------------------- 1 | module Alba 2 | module Inherited 3 | class EmptyUserSerializer < UserSerializer 4 | typelizer_config.inheritance_strategy = :inheritance 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/app/app/serializers/alba/inherited/extended_user_serializer.rb: -------------------------------------------------------------------------------- 1 | module Alba 2 | module Inherited 3 | class ExtendedUserSerializer < UserSerializer 4 | typelizer_config.inheritance_strategy = :inheritance 5 | 6 | typelize :string 7 | attribute :full_name do |object| 8 | object.username 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/app/app/serializers/alba/inherited/nested_root_user_serializer.rb: -------------------------------------------------------------------------------- 1 | module Alba 2 | module Inherited 3 | class NestedRootUserSerializer < RootUserSerializer 4 | attributes :updated_at 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/app/app/serializers/alba/inherited/root_user_serializer.rb: -------------------------------------------------------------------------------- 1 | module Alba 2 | module Inherited 3 | class RootUserSerializer < UserSerializer 4 | typelizer_config.inheritance_strategy = :inheritance 5 | 6 | root_key! 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/app/app/serializers/alba/inherited/transform_keys_user_serializer.rb: -------------------------------------------------------------------------------- 1 | module Alba 2 | module Inherited 3 | class TransformKeysUserSerializer < UserSerializer 4 | typelizer_config.inheritance_strategy = :inheritance 5 | 6 | transform_keys :lower_camel 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/app/app/serializers/alba/inline_serializer.rb: -------------------------------------------------------------------------------- 1 | module Alba 2 | class InlineSerializer < BaseSerializer 3 | typelize_from ::User 4 | attributes :id, :username, :active 5 | 6 | has_many :untyped_posts do 7 | attributes :id, :title 8 | end 9 | 10 | has_many :posts do 11 | typelize id: :number 12 | 13 | attributes :id, title: [String, true] 14 | end 15 | 16 | has_many :deep_posts do 17 | typelize_from ::Post 18 | attributes :id, :title 19 | 20 | has_one :user do 21 | typelize_from ::User 22 | attributes :id, :username 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/app/app/serializers/alba/meta_nil_serializer.rb: -------------------------------------------------------------------------------- 1 | module Alba 2 | class MetaNilSerializer < BaseSerializer 3 | typelize_from ::User 4 | attributes :id 5 | 6 | meta nil 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/app/app/serializers/alba/meta_serializer.rb: -------------------------------------------------------------------------------- 1 | module Alba 2 | class MetaSerializer < BaseSerializer 3 | root_key! 4 | 5 | attributes username: [String, true], full_name: String 6 | 7 | typelize address: "{city: string, zipcode: string}" 8 | nested_attribute :address do 9 | attributes :city, :zipcode 10 | end 11 | 12 | typelize_meta metadata: "{foo: 'bar'}" 13 | meta :metadata do 14 | {foo: :bar} 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/app/app/serializers/alba/poro_serializer.rb: -------------------------------------------------------------------------------- 1 | module Alba 2 | class PoroSerializer 3 | include Alba::Serializer 4 | include Typelizer::DSL 5 | 6 | typelize_from Poro 7 | 8 | attributes :foo, bar: :String 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/app/app/serializers/alba/post_serializer.rb: -------------------------------------------------------------------------------- 1 | module Alba 2 | class PostSerializer 3 | include Alba::Resource 4 | include Typelizer::DSL 5 | 6 | typelizer_config do |c| 7 | c.null_strategy = :nullable_and_optional 8 | c.serializer_model_mapper = lambda { |serializer| 9 | Object.const_get(serializer.name.gsub("Serializer", "").gsub("Alba::", "")) 10 | } 11 | end 12 | 13 | attributes :id, :title, :category, :body, :published_at 14 | 15 | has_one :user, serializer: UserSerializer 16 | 17 | attributes :next_post 18 | typelize next_post: "Post" 19 | 20 | attribute :name, &:title 21 | typelize name: [:string, comment: "This is name", deprecated: true] 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/app/app/serializers/alba/transform_keys_serializer.rb: -------------------------------------------------------------------------------- 1 | module Alba 2 | class TransformKeysSerializer < UserSerializer 3 | attributes :created_at 4 | 5 | transform_keys :lower_camel 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/app/app/serializers/alba/user/author_serializer.rb: -------------------------------------------------------------------------------- 1 | module Alba 2 | module User 3 | class AuthorSerializer < BaseSerializer 4 | typelize_from ::User 5 | 6 | typelize username: [:string, nullable: true, comment: "Author login handle"] 7 | attributes :id, :username 8 | 9 | has_many :posts, resource: PostSerializer, if: ->(u) { u.posts.any? } 10 | 11 | typelize invitor: {nullable: false} 12 | has_one :invitor, resource: UserSerializer 13 | 14 | attribute :avatar do 15 | "https://example.com/avatar.png" if active? 16 | end 17 | 18 | # typelize typed_avatar: [:string, nullable: true] 19 | # typelize ["string", "null"] 20 | # typelize "string | null" 21 | typelize :string, nullable: true, comment: <<~TXT 22 | Typed avatar URL 23 | Active user only 24 | TXT 25 | attribute :typed_avatar do 26 | "https://example.com/avatar.png" if active? 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/app/app/serializers/alba/user/empty_nested_serializer.rb: -------------------------------------------------------------------------------- 1 | module Alba 2 | module User 3 | class EmptyNestedSerializer < AuthorSerializer 4 | end 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /spec/app/app/serializers/alba/user_serializer.rb: -------------------------------------------------------------------------------- 1 | module Alba 2 | class UserSerializer < BaseSerializer 3 | typelize_from ::User 4 | attributes :id, :username, :active, :name, :sir_name 5 | 6 | has_one :invitor, resource: UserSerializer 7 | 8 | has_many :posts, resource: PostSerializer 9 | has_one :latest_post, resource: PostSerializer # Duplicated association 10 | 11 | typelize id: [:string, nullable: true] 12 | 13 | typelize :string, comment: "This is sir name from the name" 14 | def sir_name(object) 15 | object.username.split(" ").last 16 | end 17 | 18 | typelize :string 19 | attribute :first_name do |object| 20 | object.username.split(" ").first 21 | end 22 | 23 | class FooSerializer < UserSerializer 24 | typelize_from ::User 25 | attributes :created_at 26 | 27 | typelize id: [:number, optional: true] 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/app/app/serializers/alba/verbatim_module_syntax_serializer.rb: -------------------------------------------------------------------------------- 1 | module Alba 2 | class VerbatimModuleSyntaxSerializer < BaseSerializer 3 | typelize_from ::User 4 | 5 | attributes :id, :username 6 | 7 | typelizer_config.verbatim_module_syntax = true 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/app/app/serializers/ams/ar/post_serializer.rb: -------------------------------------------------------------------------------- 1 | module Ams 2 | module Ar 3 | class PostSerializer < BaseSerializer 4 | typelize_from ::Post 5 | 6 | attributes :id, :title 7 | 8 | has_one :user, serializer: UserSerializer 9 | 10 | typelizer_config.associations_strategy = :active_record 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/app/app/serializers/ams/ar/user_serializer.rb: -------------------------------------------------------------------------------- 1 | module Ams 2 | module Ar 3 | class UserSerializer < BaseSerializer 4 | typelize_from ::User 5 | attributes :id, :username 6 | 7 | has_one :invitor, serializer: UserSerializer 8 | 9 | has_many :posts, serializer: PostSerializer 10 | has_one :latest_post, serializer: PostSerializer # Duplicated association 11 | 12 | typelizer_config.associations_strategy = :active_record 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/app/app/serializers/ams/base_serializer.rb: -------------------------------------------------------------------------------- 1 | module Ams 2 | class BaseSerializer < ActiveModel::Serializer 3 | include Typelizer::DSL 4 | 5 | typelizer_config.null_strategy = :nullable_and_optional 6 | 7 | typelize id: [:string, comment: "Unique identifier"] 8 | def id 9 | Base64.urlsafe_encode64(object.id.to_s) 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/app/app/serializers/ams/inherited/custom_type_user_serializer.rb: -------------------------------------------------------------------------------- 1 | module Ams 2 | module Inherited 3 | class CustomTypeUserSerializer < UserSerializer 4 | typelizer_config.inheritance_strategy = :inheritance 5 | 6 | typelize id: [:number, optional: true] 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/app/app/serializers/ams/inherited/deep_user_serializer.rb: -------------------------------------------------------------------------------- 1 | module Ams 2 | module Inherited 3 | class DeepUserSerializer < ExtendedUserSerializer 4 | attributes :updated_at 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/app/app/serializers/ams/inherited/empty_user_serializer.rb: -------------------------------------------------------------------------------- 1 | module Ams 2 | module Inherited 3 | class EmptyUserSerializer < UserSerializer 4 | typelizer_config.inheritance_strategy = :inheritance 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/app/app/serializers/ams/inherited/extended_user_serializer.rb: -------------------------------------------------------------------------------- 1 | module Ams 2 | module Inherited 3 | class ExtendedUserSerializer < UserSerializer 4 | typelizer_config.inheritance_strategy = :inheritance 5 | 6 | typelize :string 7 | attribute :full_name do |object| 8 | object.username 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/app/app/serializers/ams/inherited/transform_keys_user_serializer.rb: -------------------------------------------------------------------------------- 1 | module Ams 2 | module Inherited 3 | class TransformKeysUserSerializer < UserSerializer 4 | typelizer_config.inheritance_strategy = :inheritance 5 | 6 | attribute :created_at, key_transform: :camel_lower 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/app/app/serializers/ams/post_serializer.rb: -------------------------------------------------------------------------------- 1 | module Ams 2 | class PostSerializer < ActiveModel::Serializer 3 | include Typelizer::DSL 4 | 5 | typelize_from ::Post 6 | typelizer_config.null_strategy = :nullable_and_optional 7 | 8 | attributes :id, :title, :category, :body, :published_at 9 | 10 | has_one :user, serializer: UserSerializer 11 | 12 | typelize :string 13 | attribute :name, deprecated: "Use 'title' instead." 14 | def name 15 | title 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/app/app/serializers/ams/transform_keys_serializer.rb: -------------------------------------------------------------------------------- 1 | module Ams 2 | class TransformKeysSerializer < UserSerializer 3 | attribute :created_at, key_transform: :camel_lower 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/app/app/serializers/ams/user/author_serializer.rb: -------------------------------------------------------------------------------- 1 | module Ams 2 | module User 3 | class AuthorSerializer < BaseSerializer 4 | typelize_from ::User 5 | 6 | typelize username: [:string, nullable: true, comment: "Author login handle"] 7 | attributes :id, :username 8 | 9 | has_many :posts, serializer: PostSerializer, if: ->(u) { u.posts.any? } 10 | 11 | attribute :avatar do 12 | "https://example.com/avatar.png" if active? 13 | end 14 | 15 | # typelize typed_avatar: [:string, nullable: true] 16 | # typelize ["string", "null"] 17 | # typelize "string | null" 18 | typelize :string, nullable: true 19 | attribute :typed_avatar do 20 | "https://example.com/avatar.png" if active? 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/app/app/serializers/ams/user/empty_nested_serializer.rb: -------------------------------------------------------------------------------- 1 | module Ams 2 | module User 3 | class EmptyNestedSerializer < AuthorSerializer 4 | end 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /spec/app/app/serializers/ams/user_serializer.rb: -------------------------------------------------------------------------------- 1 | module Ams 2 | class UserSerializer < BaseSerializer 3 | typelize_from ::User 4 | attributes :id, :username, :active, :name 5 | 6 | has_one :invitor, serializer: UserSerializer 7 | 8 | has_many :posts, serializer: PostSerializer 9 | 10 | typelize id: [:string, nullable: true] 11 | 12 | typelize :string 13 | attribute :first_name do |object| 14 | object.username.split(" ").first 15 | end 16 | 17 | class FooSerializer < UserSerializer 18 | typelize_from ::User 19 | attributes :created_at 20 | 21 | typelize id: [:number, optional: true] 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/app/app/serializers/ams/verbatim_module_syntax_serializer.rb: -------------------------------------------------------------------------------- 1 | module Ams 2 | class VerbatimModuleSyntaxSerializer < BaseSerializer 3 | typelize_from ::User 4 | 5 | attributes :id, :username 6 | 7 | typelizer_config.verbatim_module_syntax = true 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/app/app/serializers/oj_serializers/ar/post_serializer.rb: -------------------------------------------------------------------------------- 1 | module OjSerializers 2 | module Ar 3 | class PostSerializer < BaseSerializer 4 | typelize_from ::Post 5 | 6 | attributes :id, :title 7 | 8 | has_one :user, serializer: UserSerializer 9 | 10 | typelizer_config.associations_strategy = :active_record 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/app/app/serializers/oj_serializers/ar/user_serializer.rb: -------------------------------------------------------------------------------- 1 | module OjSerializers 2 | module Ar 3 | class UserSerializer < BaseSerializer 4 | typelize_from ::User 5 | attributes :id, :username 6 | 7 | has_one :invitor, serializer: UserSerializer 8 | 9 | has_many :posts, serializer: PostSerializer 10 | has_one :latest_post, serializer: PostSerializer # Duplicated association 11 | 12 | typelizer_config.associations_strategy = :active_record 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/app/app/serializers/oj_serializers/base_serializer.rb: -------------------------------------------------------------------------------- 1 | module OjSerializers 2 | class BaseSerializer < Oj::Serializer 3 | include Typelizer::DSL 4 | 5 | typelizer_config.null_strategy = :nullable_and_optional 6 | 7 | typelize id: [:string, comment: "Unique identifier"] 8 | def id 9 | Base64.urlsafe_encode64(object.id.to_s) 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/app/app/serializers/oj_serializers/flat_user_serializer.rb: -------------------------------------------------------------------------------- 1 | module OjSerializers 2 | class FlatUserSerializer < BaseSerializer 3 | flat_one :invitor, serializer: UserSerializer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/app/app/serializers/oj_serializers/inherited/custom_type_user_serializer.rb: -------------------------------------------------------------------------------- 1 | module OjSerializers 2 | module Inherited 3 | class CustomTypeUserSerializer < UserSerializer 4 | typelizer_config.inheritance_strategy = :inheritance 5 | 6 | typelize id: [:number, optional: true] 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/app/app/serializers/oj_serializers/inherited/deep_user_serializer.rb: -------------------------------------------------------------------------------- 1 | module OjSerializers 2 | module Inherited 3 | class DeepUserSerializer < ExtendedUserSerializer 4 | attributes :updated_at 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/app/app/serializers/oj_serializers/inherited/empty_user_serializer.rb: -------------------------------------------------------------------------------- 1 | module OjSerializers 2 | module Inherited 3 | class EmptyUserSerializer < UserSerializer 4 | typelizer_config.inheritance_strategy = :inheritance 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/app/app/serializers/oj_serializers/inherited/extended_user_serializer.rb: -------------------------------------------------------------------------------- 1 | module OjSerializers 2 | module Inherited 3 | class ExtendedUserSerializer < UserSerializer 4 | typelizer_config.inheritance_strategy = :inheritance 5 | 6 | typelize :string 7 | attribute :full_name do |object| 8 | object.username 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/app/app/serializers/oj_serializers/inherited/transform_keys_user_serializer.rb: -------------------------------------------------------------------------------- 1 | module OjSerializers 2 | module Inherited 3 | class TransformKeysUserSerializer < UserSerializer 4 | typelizer_config.inheritance_strategy = :inheritance 5 | 6 | transform_keys :camel_case 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/app/app/serializers/oj_serializers/post_serializer.rb: -------------------------------------------------------------------------------- 1 | module OjSerializers 2 | class PostSerializer < Oj::Serializer 3 | include Typelizer::DSL 4 | 5 | typelize_from ::Post 6 | typelizer_config.null_strategy = :nullable_and_optional 7 | 8 | attributes :id, :title, :category, :body, :published_at 9 | 10 | has_one :user, serializer: UserSerializer 11 | 12 | attribute :name, &:title 13 | typelize name: [:string, deprecated: "Use 'title' instead."] 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/app/app/serializers/oj_serializers/transform_keys_serializer.rb: -------------------------------------------------------------------------------- 1 | module OjSerializers 2 | class TransformKeysSerializer < UserSerializer 3 | attribute :created_at 4 | 5 | transform_keys :camel_case 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/app/app/serializers/oj_serializers/user/author_serializer.rb: -------------------------------------------------------------------------------- 1 | module OjSerializers 2 | module User 3 | class AuthorSerializer < BaseSerializer 4 | typelize_from ::User 5 | 6 | typelize username: [:string, nullable: true, comment: "Author login handle"] 7 | attributes :id, :username 8 | 9 | has_many :posts, serializer: PostSerializer, if: ->(u) { u.posts.any? } 10 | 11 | attribute :avatar do 12 | "https://example.com/avatar.png" if active? 13 | end 14 | 15 | # typelize typed_avatar: [:string, nullable: true] 16 | # typelize ["string", "null"] 17 | # typelize "string | null" 18 | typelize :string, nullable: true 19 | attribute :typed_avatar do 20 | "https://example.com/avatar.png" if active? 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/app/app/serializers/oj_serializers/user/empty_nested_serializer.rb: -------------------------------------------------------------------------------- 1 | module OjSerializers 2 | module User 3 | class EmptyNestedSerializer < AuthorSerializer 4 | end 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /spec/app/app/serializers/oj_serializers/user_serializer.rb: -------------------------------------------------------------------------------- 1 | module OjSerializers 2 | class UserSerializer < BaseSerializer 3 | typelize_from ::User 4 | attributes :id, :username, :active, :name 5 | 6 | has_one :invitor, serializer: UserSerializer 7 | 8 | has_many :posts, serializer: PostSerializer 9 | 10 | typelize :string 11 | attribute :first_name do |object| 12 | object.username.split(" ").first 13 | end 14 | 15 | class FooSerializer < UserSerializer 16 | typelize_from ::User 17 | attributes :created_at 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/app/app/serializers/oj_serializers/verbatim_module_syntax_serializer.rb: -------------------------------------------------------------------------------- 1 | module OjSerializers 2 | class VerbatimModuleSyntaxSerializer < BaseSerializer 3 | typelize_from ::User 4 | 5 | attributes :id, :username 6 | 7 | typelizer_config.verbatim_module_syntax = true 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/app/app/serializers/panko/ar/post_serializer.rb: -------------------------------------------------------------------------------- 1 | module Panko 2 | module Ar 3 | class PostSerializer < BaseSerializer 4 | typelize_from ::Post 5 | 6 | attributes :id, :title 7 | 8 | has_one :user, serializer: UserSerializer 9 | 10 | typelizer_config.associations_strategy = :active_record 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/app/app/serializers/panko/ar/user_serializer.rb: -------------------------------------------------------------------------------- 1 | module Panko 2 | module Ar 3 | class UserSerializer < BaseSerializer 4 | typelize_from ::User 5 | attributes :id, :username 6 | 7 | has_one :invitor, serializer: UserSerializer 8 | 9 | has_many :posts, serializer: PostSerializer 10 | has_one :latest_post, serializer: PostSerializer # Duplicated association 11 | 12 | typelizer_config.associations_strategy = :active_record 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/app/app/serializers/panko/base_serializer.rb: -------------------------------------------------------------------------------- 1 | module Panko 2 | class BaseSerializer < Panko::Serializer 3 | include Typelizer::DSL 4 | 5 | typelizer_config.null_strategy = :nullable_and_optional 6 | 7 | typelize id: [:string, comment: "Unique identifier"] 8 | def id 9 | Base64.urlsafe_encode64(object.id.to_s) 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/app/app/serializers/panko/inherited/custom_type_user_serializer.rb: -------------------------------------------------------------------------------- 1 | module Panko 2 | module Inherited 3 | class CustomTypeUserSerializer < UserSerializer 4 | typelizer_config.inheritance_strategy = :inheritance 5 | 6 | typelize id: [:number, optional: true] 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/app/app/serializers/panko/inherited/deep_user_serializer.rb: -------------------------------------------------------------------------------- 1 | module Panko 2 | module Inherited 3 | class DeepUserSerializer < ExtendedUserSerializer 4 | attributes :updated_at 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/app/app/serializers/panko/inherited/empty_user_serializer.rb: -------------------------------------------------------------------------------- 1 | module Panko 2 | module Inherited 3 | class EmptyUserSerializer < UserSerializer 4 | typelizer_config.inheritance_strategy = :inheritance 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/app/app/serializers/panko/inherited/extended_user_serializer.rb: -------------------------------------------------------------------------------- 1 | module Panko 2 | module Inherited 3 | class ExtendedUserSerializer < UserSerializer 4 | typelizer_config.inheritance_strategy = :inheritance 5 | 6 | attributes :full_name 7 | 8 | typelize :string 9 | def full_name 10 | object.username 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/app/app/serializers/panko/post_serializer.rb: -------------------------------------------------------------------------------- 1 | module Panko 2 | class PostSerializer < Panko::Serializer 3 | include Typelizer::DSL 4 | 5 | typelize_from ::Post 6 | typelizer_config.null_strategy = :nullable_and_optional 7 | 8 | attributes :id, :title, :category, :body, :published_at 9 | 10 | has_one :user, serializer: UserSerializer 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/app/app/serializers/panko/user/author_serializer.rb: -------------------------------------------------------------------------------- 1 | module Panko 2 | module User 3 | class AuthorSerializer < BaseSerializer 4 | typelize_from ::User 5 | 6 | typelize username: [:string, nullable: true, comment: "Author login handle"] 7 | attributes :id, :username, :avatar, :typed_avatar 8 | 9 | has_many :posts, serializer: PostSerializer 10 | 11 | def avatar 12 | "https://example.com/avatar.png" if active? 13 | end 14 | 15 | # typelize typed_avatar: [:string, nullable: true] 16 | # typelize ["string", "null"] 17 | # typelize "string | null" 18 | typelize :string, nullable: true 19 | def typed_avatar 20 | "https://example.com/avatar.png" if active? 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/app/app/serializers/panko/user/empty_nested_serializer.rb: -------------------------------------------------------------------------------- 1 | module Panko 2 | module User 3 | class EmptyNestedSerializer < AuthorSerializer 4 | end 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /spec/app/app/serializers/panko/user_serializer.rb: -------------------------------------------------------------------------------- 1 | module Panko 2 | class UserSerializer < BaseSerializer 3 | typelize_from ::User 4 | attributes :id, :username, :active, :name, :first_name 5 | 6 | has_one :invitor, serializer: UserSerializer 7 | 8 | has_many :posts, serializer: PostSerializer 9 | has_one :post, resource: PostSerializer, name: :latest_post 10 | 11 | typelize id: [:string, nullable: true] 12 | 13 | typelize :string 14 | def first_name 15 | object.username.split(" ").first 16 | end 17 | 18 | class FooSerializer < UserSerializer 19 | typelize_from ::User 20 | attributes :created_at 21 | 22 | typelize id: [:number, optional: true] 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/app/app/serializers/panko/verbatim_module_syntax_serializer.rb: -------------------------------------------------------------------------------- 1 | module Panko 2 | class VerbatimModuleSyntaxSerializer < BaseSerializer 3 | typelize_from ::User 4 | 5 | attributes :id, :username 6 | 7 | typelizer_config.verbatim_module_syntax = true 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/app/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | App 5 | 6 | <%= csrf_meta_tags %> 7 | <%= csp_meta_tag %> 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /spec/app/bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'bundle' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "rubygems" 12 | 13 | m = Module.new do 14 | module_function 15 | 16 | def invoked_as_script? 17 | File.expand_path($0) == File.expand_path(__FILE__) 18 | end 19 | 20 | def env_var_version 21 | ENV["BUNDLER_VERSION"] 22 | end 23 | 24 | def cli_arg_version 25 | return unless invoked_as_script? # don't want to hijack other binstubs 26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` 27 | bundler_version = nil 28 | update_index = nil 29 | ARGV.each_with_index do |a, i| 30 | if update_index && update_index.succ == i && a.match?(Gem::Version::ANCHORED_VERSION_PATTERN) 31 | bundler_version = a 32 | end 33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/o 34 | bundler_version = $1 35 | update_index = i 36 | end 37 | bundler_version 38 | end 39 | 40 | def gemfile 41 | gemfile = ENV["BUNDLE_GEMFILE"] 42 | return gemfile if gemfile && !gemfile.empty? 43 | 44 | File.expand_path("../Gemfile", __dir__) 45 | end 46 | 47 | def lockfile 48 | lockfile = 49 | case File.basename(gemfile) 50 | when "gems.rb" then gemfile.sub(/\.rb$/, ".locked") 51 | else "#{gemfile}.lock" 52 | end 53 | File.expand_path(lockfile) 54 | end 55 | 56 | def lockfile_version 57 | return unless File.file?(lockfile) 58 | lockfile_contents = File.read(lockfile) 59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/o 60 | Regexp.last_match(1) 61 | end 62 | 63 | def bundler_requirement 64 | @bundler_requirement ||= 65 | env_var_version || 66 | cli_arg_version || 67 | bundler_requirement_for(lockfile_version) 68 | end 69 | 70 | def bundler_requirement_for(version) 71 | return "#{Gem::Requirement.default}.a" unless version 72 | 73 | bundler_gem_version = Gem::Version.new(version) 74 | 75 | bundler_gem_version.approximate_recommendation 76 | end 77 | 78 | def load_bundler! 79 | ENV["BUNDLE_GEMFILE"] ||= gemfile 80 | 81 | activate_bundler 82 | end 83 | 84 | def activate_bundler 85 | gem_error = activation_error_handling do 86 | gem "bundler", bundler_requirement 87 | end 88 | return if gem_error.nil? 89 | require_error = activation_error_handling do 90 | require "bundler/version" 91 | end 92 | return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) 93 | warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" 94 | exit 42 95 | end 96 | 97 | def activation_error_handling 98 | yield 99 | nil 100 | rescue StandardError, LoadError => e 101 | e 102 | end 103 | end 104 | 105 | m.load_bundler! 106 | 107 | if m.invoked_as_script? 108 | load Gem.bin_path("bundler", "bundle") 109 | end 110 | -------------------------------------------------------------------------------- /spec/app/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require_relative "../config/boot" 4 | require "rails/commands" 5 | -------------------------------------------------------------------------------- /spec/app/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "../config/boot" 3 | require "rake" 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /spec/app/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "fileutils" 3 | 4 | # path to your application root. 5 | APP_ROOT = File.expand_path("..", __dir__) 6 | 7 | def system!(*args) 8 | system(*args, exception: true) 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | # This script is a way to set up or update your development environment automatically. 13 | # This script is idempotent, so that you can run it at any time and get an expectable outcome. 14 | # Add necessary setup steps to this file. 15 | 16 | puts "== Installing dependencies ==" 17 | system! "gem install bundler --conservative" 18 | system("bundle check") || system!("bundle install") 19 | 20 | # puts "\n== Copying sample files ==" 21 | # unless File.exist?("config/database.yml") 22 | # FileUtils.cp "config/database.yml.sample", "config/database.yml" 23 | # end 24 | 25 | puts "\n== Preparing database ==" 26 | system! "bin/rails db:prepare" 27 | 28 | puts "\n== Removing old logs and tempfiles ==" 29 | system! "bin/rails log:clear tmp:clear" 30 | 31 | puts "\n== Restarting application server ==" 32 | system! "bin/rails restart" 33 | end 34 | -------------------------------------------------------------------------------- /spec/app/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative "config/environment" 4 | 5 | run Rails.application 6 | Rails.application.load_server 7 | -------------------------------------------------------------------------------- /spec/app/config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative "boot" 2 | 3 | require "rails" 4 | # Pick the frameworks you want: 5 | require "active_model/railtie" 6 | # require "active_job/railtie" 7 | require "active_record/railtie" 8 | # require "active_storage/engine" 9 | require "action_controller/railtie" 10 | # require "action_mailer/railtie" 11 | # require "action_mailbox/engine" 12 | # require "action_text/engine" 13 | require "action_view/railtie" 14 | # require "action_cable/engine" 15 | require "rails/test_unit/railtie" 16 | 17 | # Require the gems listed in Gemfile, including any gems 18 | # you've limited to :test, :development, or :production. 19 | Bundler.require(*Rails.groups) 20 | 21 | module App 22 | class Application < Rails::Application 23 | # Initialize configuration defaults for originally generated Rails version. 24 | config.load_defaults 7.1 25 | 26 | # Please, add to the `ignore` list any other `lib` subdirectories that do 27 | # not contain `.rb` files, or that should not be reloaded or eager loaded. 28 | # Common ones are `templates`, `generators`, or `middleware`, for example. 29 | config.autoload_lib(ignore: %w[assets tasks]) 30 | 31 | # Configuration for the application, engines, and railties goes here. 32 | # 33 | # These settings can be overridden in specific environments using the files 34 | # in config/environments, which are processed later. 35 | # 36 | # config.time_zone = "Central Time (US & Canada)" 37 | # config.eager_load_paths << Rails.root.join("extras") 38 | 39 | # Don't generate system test files. 40 | config.generators.system_tests = nil 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/app/config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 2 | 3 | require "bundler/setup" # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /spec/app/config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | 4W3soEgJJAVA8/VcfLaQwQKDH8kfz/hAYvFDhl20S6OTLiRCXTiPdFqFDCJ/fMin1w8uQy7PwlTCwyQhXmDWtzTrxZS/jH/3tVff0/najMBiA9WPEO3vop3J1XNHZM+3XgdqpMwQkg3guQC9OV+7saYsyTQ0AfHYTrOO499qXuMD/ylVcTSzlC4jKP86SGvD+rZdUywurtZNizGXAEPzft8zD8YptKF/mNnZb5UQJrHs8eC1pnJcOqoxWCu0ttBnM90kxrSa0aHTWavDOEl4we8IT4QUOAUxyHd+hJRE1SWVPuJLduurqHednwczwHBlVP5tE8xQh+tKKPFI+MnVfpXIaP1VDC7ATju5/K/jXaj4wiFcSwo7Y+ldTsZ5n0gYFP4WXNgtRVzXgX1PFCvCCqcMkQ/G--eZs4YzJX5V5tHt/U--OQ18/wST65IpSfmWBeaYvA== -------------------------------------------------------------------------------- /spec/app/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite. Versions 3.8.0 and up are supported. 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem "sqlite3" 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: storage/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: storage/test.sqlite3 22 | 23 | production: 24 | <<: *default 25 | database: storage/production.sqlite3 26 | -------------------------------------------------------------------------------- /spec/app/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative "application" 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /spec/app/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # In the development environment your application's code is reloaded any time 7 | # it changes. This slows down response time but is perfect for development 8 | # since you don't have to restart the web server when you make code changes. 9 | config.enable_reloading = true 10 | 11 | # Do not eager load code on boot. 12 | config.eager_load = false 13 | 14 | # Show full error reports. 15 | config.consider_all_requests_local = true 16 | 17 | # Enable server timing 18 | config.server_timing = true 19 | 20 | # Enable/disable caching. By default caching is disabled. 21 | # Run rails dev:cache to toggle caching. 22 | if Rails.root.join("tmp/caching-dev.txt").exist? 23 | config.action_controller.perform_caching = true 24 | config.action_controller.enable_fragment_cache_logging = true 25 | 26 | config.cache_store = :memory_store 27 | config.public_file_server.headers = { 28 | "Cache-Control" => "public, max-age=#{2.days.to_i}" 29 | } 30 | else 31 | config.action_controller.perform_caching = false 32 | 33 | config.cache_store = :null_store 34 | end 35 | 36 | # Print deprecation notices to the Rails logger. 37 | config.active_support.deprecation = :log 38 | 39 | # Raise exceptions for disallowed deprecations. 40 | config.active_support.disallowed_deprecation = :raise 41 | 42 | # Tell Active Support which deprecation messages to disallow. 43 | config.active_support.disallowed_deprecation_warnings = [] 44 | 45 | # Raise an error on page load if there are pending migrations. 46 | config.active_record.migration_error = :page_load 47 | 48 | # Highlight code that triggered database queries in logs. 49 | config.active_record.verbose_query_logs = true 50 | 51 | # Raises error for missing translations. 52 | # config.i18n.raise_on_missing_translations = true 53 | 54 | # Annotate rendered view with file names. 55 | # config.action_view.annotate_rendered_view_with_filenames = true 56 | 57 | # Raise error when a before_action's only/except options reference missing actions 58 | config.action_controller.raise_on_missing_callback_actions = true 59 | end 60 | -------------------------------------------------------------------------------- /spec/app/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # Code is not reloaded between requests. 7 | config.enable_reloading = false 8 | 9 | # Eager load code on boot. This eager loads most of Rails and 10 | # your application in memory, allowing both threaded web servers 11 | # and those relying on copy on write to perform better. 12 | # Rake tasks automatically ignore this option for performance. 13 | config.eager_load = true 14 | 15 | # Full error reports are disabled and caching is turned on. 16 | config.consider_all_requests_local = false 17 | config.action_controller.perform_caching = true 18 | 19 | # Ensures that a master key has been made available in ENV["RAILS_MASTER_KEY"], config/master.key, or an environment 20 | # key such as config/credentials/production.key. This key is used to decrypt credentials (and other encrypted files). 21 | # config.require_master_key = true 22 | 23 | # Disable serving static files from `public/`, relying on NGINX/Apache to do so instead. 24 | # config.public_file_server.enabled = false 25 | 26 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 27 | # config.asset_host = "http://assets.example.com" 28 | 29 | # Specifies the header that your server uses for sending files. 30 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache 31 | # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX 32 | 33 | # Assume all access to the app is happening through a SSL-terminating reverse proxy. 34 | # Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies. 35 | # config.assume_ssl = true 36 | 37 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 38 | config.force_ssl = true 39 | 40 | # Log to STDOUT by default 41 | config.logger = ActiveSupport::Logger.new($stdout) 42 | .tap { |logger| logger.formatter = ::Logger::Formatter.new } 43 | .then { |logger| ActiveSupport::TaggedLogging.new(logger) } 44 | 45 | # Prepend all log lines with the following tags. 46 | config.log_tags = [:request_id] 47 | 48 | # "info" includes generic and useful information about system operation, but avoids logging too much 49 | # information to avoid inadvertent exposure of personally identifiable information (PII). If you 50 | # want to log everything, set the level to "debug". 51 | config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") 52 | 53 | # Use a different cache store in production. 54 | # config.cache_store = :mem_cache_store 55 | 56 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 57 | # the I18n.default_locale when a translation cannot be found). 58 | config.i18n.fallbacks = true 59 | 60 | # Don't log any deprecations. 61 | config.active_support.report_deprecations = false 62 | 63 | # Do not dump schema after migrations. 64 | config.active_record.dump_schema_after_migration = false 65 | 66 | # Enable DNS rebinding protection and other `Host` header attacks. 67 | # config.hosts = [ 68 | # "example.com", # Allow requests from example.com 69 | # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` 70 | # ] 71 | # Skip DNS rebinding protection for the default health check endpoint. 72 | # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } 73 | end 74 | -------------------------------------------------------------------------------- /spec/app/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | # The test environment is used exclusively to run your application's 4 | # test suite. You never need to work with it otherwise. Remember that 5 | # your test database is "scratch space" for the test suite and is wiped 6 | # and recreated between test runs. Don't rely on the data there! 7 | 8 | Rails.application.configure do 9 | # Settings specified here will take precedence over those in config/application.rb. 10 | 11 | # While tests run files are not watched, reloading is not necessary. 12 | config.enable_reloading = false 13 | 14 | # Eager loading loads your entire application. When running a single test locally, 15 | # this is usually not necessary, and can slow down your test suite. However, it's 16 | # recommended that you enable it in continuous integration systems to ensure eager 17 | # loading is working properly before deploying your code. 18 | config.eager_load = ENV["CI"].present? 19 | 20 | # Configure public file server for tests with Cache-Control for performance. 21 | config.public_file_server.enabled = true 22 | config.public_file_server.headers = { 23 | "Cache-Control" => "public, max-age=#{1.hour.to_i}" 24 | } 25 | 26 | # Show full error reports and disable caching. 27 | config.consider_all_requests_local = true 28 | config.action_controller.perform_caching = false 29 | config.cache_store = :null_store 30 | 31 | # Render exception templates for rescuable exceptions and raise for other exceptions. 32 | config.action_dispatch.show_exceptions = :rescuable 33 | 34 | # Disable request forgery protection in test environment. 35 | config.action_controller.allow_forgery_protection = false 36 | 37 | # Print deprecation notices to the stderr. 38 | config.active_support.deprecation = :stderr 39 | 40 | # Raise exceptions for disallowed deprecations. 41 | config.active_support.disallowed_deprecation = :raise 42 | 43 | # Tell Active Support which deprecation messages to disallow. 44 | config.active_support.disallowed_deprecation_warnings = [] 45 | 46 | # Raises error for missing translations. 47 | # config.i18n.raise_on_missing_translations = true 48 | 49 | # Annotate rendered view with file names. 50 | # config.action_view.annotate_rendered_view_with_filenames = true 51 | 52 | # Raise error when a before_action's only/except options reference missing actions 53 | config.action_controller.raise_on_missing_callback_actions = true 54 | end 55 | -------------------------------------------------------------------------------- /spec/app/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. 4 | # Use this to limit dissemination of sensitive information. 5 | # See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. 6 | Rails.application.config.filter_parameters += [ 7 | :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn 8 | ] 9 | -------------------------------------------------------------------------------- /spec/app/config/initializers/typelizer.rb: -------------------------------------------------------------------------------- 1 | Typelizer.configure do |c| 2 | c.dirs = [ 3 | Rails.root.join("app", "serializers") 4 | ] 5 | 6 | c.types_global = %w[Array Date Record] 7 | 8 | c.comments = true 9 | end 10 | -------------------------------------------------------------------------------- /spec/app/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization and 2 | # are automatically loaded by Rails. If you want to use locales other than 3 | # English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t "hello" 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t("hello") %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more about the API, please read the Rails Internationalization guide 20 | # at https://guides.rubyonrails.org/i18n.html. 21 | # 22 | # Be aware that YAML interprets the following case-insensitive strings as 23 | # booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings 24 | # must be quoted to be interpreted as strings. For example: 25 | # 26 | # en: 27 | # "yes": yup 28 | # enabled: "ON" 29 | 30 | en: 31 | hello: "Hello world" 32 | -------------------------------------------------------------------------------- /spec/app/config/puma.rb: -------------------------------------------------------------------------------- 1 | # This configuration file will be evaluated by Puma. The top-level methods that 2 | # are invoked here are part of Puma's configuration DSL. For more information 3 | # about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. 4 | 5 | # Puma can serve each request in a thread from an internal thread pool. 6 | # The `threads` method setting takes two numbers: a minimum and maximum. 7 | # Any libraries that use thread pools should be configured to match 8 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 9 | # and maximum; this matches the default thread size of Active Record. 10 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 11 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } 12 | threads min_threads_count, max_threads_count 13 | 14 | # Specifies that the worker count should equal the number of processors in production. 15 | if ENV["RAILS_ENV"] == "production" 16 | require "concurrent-ruby" 17 | worker_count = Integer(ENV.fetch("WEB_CONCURRENCY") { Concurrent.physical_processor_count }) 18 | workers worker_count if worker_count > 1 19 | end 20 | 21 | # Specifies the `worker_timeout` threshold that Puma will use to wait before 22 | # terminating a worker in development environments. 23 | worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development" 24 | 25 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 26 | port ENV.fetch("PORT") { 3000 } 27 | 28 | # Specifies the `environment` that Puma will run in. 29 | environment ENV.fetch("RAILS_ENV") { "development" } 30 | 31 | # Specifies the `pidfile` that Puma will use. 32 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } 33 | 34 | # Allow puma to be restarted by `bin/rails restart` command. 35 | plugin :tmp_restart 36 | -------------------------------------------------------------------------------- /spec/app/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html 3 | 4 | # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. 5 | # Can be used by load balancers and uptime monitors to verify that the app is live. 6 | get "up" => "rails/health#show", :as => :rails_health_check 7 | 8 | # Defines the root path route ("/") 9 | # root "posts#index" 10 | end 11 | -------------------------------------------------------------------------------- /spec/app/db/migrate/20240707052900_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration[7.1] 2 | def change 3 | create_table :users do |t| 4 | t.string :name, null: false 5 | t.string :username, null: false 6 | t.boolean :active, null: false, default: false 7 | t.references :invitor, foreign_key: {to_table: "users"} 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/app/db/migrate/20240707052907_create_posts.rb: -------------------------------------------------------------------------------- 1 | class CreatePosts < ActiveRecord::Migration[7.1] 2 | def change 3 | create_table :posts do |t| 4 | t.string :title 5 | t.integer :category 6 | t.text :body 7 | t.datetime :published_at 8 | t.references :user, null: false, foreign_key: true 9 | 10 | t.timestamps 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/app/db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # This file is the source Rails uses to define your schema when running `bin/rails 6 | # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to 7 | # be faster and is potentially less error prone than running all of your 8 | # migrations from scratch. Old migrations may fail to apply correctly if those 9 | # migrations use external dependencies or application code. 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema[7.1].define(version: 2024_07_07_052907) do 14 | create_table "posts", force: :cascade do |t| 15 | t.string "title" 16 | t.integer "category" 17 | t.text "body" 18 | t.datetime "published_at" 19 | t.integer "user_id", null: false 20 | t.datetime "created_at", null: false 21 | t.datetime "updated_at", null: false 22 | t.index ["user_id"], name: "index_posts_on_user_id" 23 | end 24 | 25 | create_table "users", force: :cascade do |t| 26 | t.string "name", null: false 27 | t.string "username", null: false 28 | t.boolean "active", default: false, null: false 29 | t.integer "invitor_id" 30 | t.datetime "created_at", null: false 31 | t.datetime "updated_at", null: false 32 | t.index ["invitor_id"], name: "index_users_on_invitor_id" 33 | end 34 | 35 | add_foreign_key "posts", "users" 36 | add_foreign_key "users", "users", column: "invitor_id" 37 | end 38 | -------------------------------------------------------------------------------- /spec/app/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should ensure the existence of records required to run the application in every environment (production, 2 | # development, test). The code here should be idempotent so that it can be executed at any point in every environment. 3 | # The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). 4 | # 5 | # Example: 6 | # 7 | # ["Action", "Comedy", "Drama", "Horror"].each do |genre_name| 8 | # MovieGenre.find_or_create_by!(name: genre_name) 9 | # end 10 | -------------------------------------------------------------------------------- /spec/app/lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skryukov/typelizer/ca67584ae3d903194966b1628a806573402ebec6/spec/app/lib/assets/.keep -------------------------------------------------------------------------------- /spec/app/lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skryukov/typelizer/ca67584ae3d903194966b1628a806573402ebec6/spec/app/lib/tasks/.keep -------------------------------------------------------------------------------- /spec/app/log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skryukov/typelizer/ca67584ae3d903194966b1628a806573402ebec6/spec/app/log/.keep -------------------------------------------------------------------------------- /spec/app/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /spec/app/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /spec/app/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /spec/app/public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skryukov/typelizer/ca67584ae3d903194966b1628a806573402ebec6/spec/app/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /spec/app/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skryukov/typelizer/ca67584ae3d903194966b1628a806573402ebec6/spec/app/public/apple-touch-icon.png -------------------------------------------------------------------------------- /spec/app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skryukov/typelizer/ca67584ae3d903194966b1628a806573402ebec6/spec/app/public/favicon.ico -------------------------------------------------------------------------------- /spec/app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /spec/app/storage/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skryukov/typelizer/ca67584ae3d903194966b1628a806573402ebec6/spec/app/storage/.keep -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ENV["DISABLE_TYPELIZER"] = "false" 4 | ENV["RAILS_ENV"] = "test" 5 | require File.expand_path("app/config/environment", __dir__) 6 | 7 | RSpec.configure do |config| 8 | # Enable flags like --only-failures and --next-failure 9 | config.example_status_persistence_file_path = ".rspec_status" 10 | 11 | # Disable RSpec exposing methods globally on `Module` and `main` 12 | config.disable_monkey_patching! 13 | 14 | config.expect_with :rspec do |c| 15 | c.syntax = :expect 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/typelizer_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe Typelizer do 4 | let(:config) { Typelizer::Config } 5 | 6 | around(:each) do |example| 7 | FileUtils.rmtree(config.output_dir) 8 | example.run 9 | FileUtils.rmtree(config.output_dir) 10 | end 11 | 12 | it "has a rake task available", aggregate_failures: true do 13 | Rails.application.load_tasks 14 | expect { Rake::Task["typelizer:generate"].invoke }.not_to raise_error 15 | 16 | # check all generated files are equal to the snapshots 17 | config.output_dir.glob("**/*.ts").each do |file| 18 | expect(file.read).to match_snapshot(file.basename) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /typelizer.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "lib/typelizer/version" 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = "typelizer" 7 | spec.version = Typelizer::VERSION 8 | spec.authors = ["Svyatoslav Kryukov"] 9 | spec.email = ["me@skryukov.dev"] 10 | 11 | spec.summary = "A TypeScript type generator for Ruby serializers." 12 | spec.description = "A TypeScript type generator for Ruby serializers." 13 | spec.homepage = "https://github.com/skryukov/typelizer" 14 | spec.license = "MIT" 15 | spec.required_ruby_version = ">= 2.7.0" 16 | 17 | spec.metadata = { 18 | "bug_tracker_uri" => "#{spec.homepage}/issues", 19 | "changelog_uri" => "#{spec.homepage}/blob/main/CHANGELOG.md", 20 | "documentation_uri" => "#{spec.homepage}/blob/main/README.md", 21 | "homepage_uri" => spec.homepage, 22 | "source_code_uri" => spec.homepage, 23 | "rubygems_mfa_required" => "true" 24 | } 25 | 26 | spec.files = Dir["{app,lib}/**/*", "CHANGELOG.md", "LICENSE.txt", "README.md"] 27 | spec.require_paths = ["lib"] 28 | 29 | spec.add_dependency "railties", ">= 6.0.0" 30 | end 31 | --------------------------------------------------------------------------------