typelizer 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +79 -7
- data/README.md +16 -2
- data/lib/typelizer/config.rb +4 -0
- data/lib/typelizer/interface.rb +42 -12
- data/lib/typelizer/model_plugins/active_record.rb +24 -0
- data/lib/typelizer/model_plugins/poro.rb +1 -1
- data/lib/typelizer/property.rb +12 -1
- data/lib/typelizer/renderer.rb +8 -0
- data/lib/typelizer/serializer_plugins/alba.rb +38 -8
- data/lib/typelizer/serializer_plugins/ams.rb +3 -1
- data/lib/typelizer/serializer_plugins/auto.rb +2 -0
- data/lib/typelizer/serializer_plugins/oj_serializers.rb +5 -1
- data/lib/typelizer/serializer_plugins/panko.rb +63 -0
- data/lib/typelizer/templates/comment.ts.erb +8 -0
- data/lib/typelizer/templates/inheritance.ts.erb +1 -0
- data/lib/typelizer/templates/interface.ts.erb +18 -21
- data/lib/typelizer/version.rb +1 -1
- data/lib/typelizer/writer.rb +1 -1
- data/lib/typelizer.rb +1 -0
- metadata +6 -6
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: ed3e7b6339e4a79168c10afd541bba9ea8c509b00f689465df037501327ac6e2
         | 
| 4 | 
            +
              data.tar.gz: a7f079a7ad377d1f2754cc1efcb98a7bc0e8af11c3910e8f56b648090ddb7f46
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 7687ff8e061c46e11f56421684a5629a823debc4d76ac3018e3ab7ac960416a05088662372b38bb4029a650ba6d81c74fbccb49d9821d7864f86023fdddd819e
         | 
| 7 | 
            +
              data.tar.gz: 860d8dc24a04302c2e70d30a4ef6811f595ee4e55788ab47d6966c10a405370dc756419b06e15b343b00fc38d495897e706d5be5a5fb1816b9a0da3315c009ce
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -5,23 +5,93 @@ All notable changes to this project will be documented in this file. | |
| 5 5 | 
             
            The format is based on [Keep a Changelog],
         | 
| 6 6 | 
             
            and this project adheres to [Semantic Versioning].
         | 
| 7 7 |  | 
| 8 | 
            -
            ## [ | 
| 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])
         | 
| 9 78 |  | 
| 10 79 | 
             
            ## [0.2.0] - 2024-11-26
         | 
| 11 80 |  | 
| 12 81 | 
             
            ## Added
         | 
| 13 82 |  | 
| 14 | 
            -
            - Add support for enum attributes declared using `ActiveRecord::Enum` or explicitly in serializers ([@ | 
| 15 | 
            -
            - Add support for comments in generated TypeScript interfaces ([@ | 
| 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])
         | 
| 16 85 | 
             
            - Add TypeScript verbatim module syntax support through `verbatim_module_syntax` config option ([@patvice])
         | 
| 17 86 | 
             
            - Add `typelizer:generate:refresh` command to clean output directory and regenerate all interfaces ([@patvice])
         | 
| 18 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])
         | 
| 19 89 |  | 
| 20 | 
            -
            ##  | 
| 90 | 
            +
            ## Fixed
         | 
| 21 91 |  | 
| 22 92 | 
             
            - Do not override `Typelizer.dirs` in the railtie initializer ([@patvice])
         | 
| 23 93 | 
             
            - Do not raise on empty nested serializers ([@skryukov])
         | 
| 24 | 
            -
            - Attribute options merging in inherited serializers ([@ | 
| 94 | 
            +
            - Attribute options merging in inherited serializers ([@Envek])
         | 
| 25 95 | 
             
            - Allow recursive type definition ([@okuramasafumi])
         | 
| 26 96 |  | 
| 27 97 | 
             
            ## [0.1.5] - 2024-10-07
         | 
| @@ -75,12 +145,14 @@ and this project adheres to [Semantic Versioning]. | |
| 75 145 | 
             
            - Initial release ([@skryukov])
         | 
| 76 146 |  | 
| 77 147 | 
             
            [@davidrunger]: https://github.com/davidrunger
         | 
| 78 | 
            -
            [@ | 
| 148 | 
            +
            [@Envek]: https://github.com/Envek
         | 
| 79 149 | 
             
            [@okuramasafumi]: https://github.com/okuramasafumi
         | 
| 80 150 | 
             
            [@patvice]: https://github.com/patvice
         | 
| 81 151 | 
             
            [@skryukov]: https://github.com/skryukov
         | 
| 82 152 |  | 
| 83 | 
            -
            [Unreleased]: https://github.com/skryukov/typelizer/compare/v0. | 
| 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
         | 
| 84 156 | 
             
            [0.2.0]: https://github.com/skryukov/typelizer/compare/v0.1.5...v0.2.0
         | 
| 85 157 | 
             
            [0.1.5]: https://github.com/skryukov/typelizer/compare/v0.1.4...v0.1.5
         | 
| 86 158 | 
             
            [0.1.4]: https://github.com/skryukov/typelizer/compare/v0.1.3...v0.1.4
         | 
    
        data/README.md
    CHANGED
    
    | @@ -29,7 +29,7 @@ Typelizer is a Ruby gem that automatically generates TypeScript interfaces from | |
| 29 29 | 
             
            ## Features
         | 
| 30 30 |  | 
| 31 31 | 
             
            - Automatic TypeScript interface generation
         | 
| 32 | 
            -
            - Support for multiple serializer libraries (`Alba`, `ActiveModel::Serializer`, `Oj::Serializer`)
         | 
| 32 | 
            +
            - Support for multiple serializer libraries (`Alba`, `ActiveModel::Serializer`, `Oj::Serializer`, `Panko::Serializer`)
         | 
| 33 33 | 
             
            - File watching and automatic regeneration in development
         | 
| 34 34 |  | 
| 35 35 | 
             
            ## Installation
         | 
| @@ -50,6 +50,10 @@ Include the Typelizer DSL in your serializers: | |
| 50 50 | 
             
            class ApplicationResource
         | 
| 51 51 | 
             
              include Alba::Resource
         | 
| 52 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
         | 
| 53 57 | 
             
            end
         | 
| 54 58 |  | 
| 55 59 | 
             
            class PostResource < ApplicationResource
         | 
| @@ -105,7 +109,7 @@ end | |
| 105 109 | 
             
            You can also specify more complex type definitions using a lower-level API:
         | 
| 106 110 |  | 
| 107 111 | 
             
            ```ruby
         | 
| 108 | 
            -
            typelize attribute_name: ["string", "Date", optional: true, nullable: true, multi: true, enum: %w[foo bar], comment: "Attribute description"]
         | 
| 112 | 
            +
            typelize attribute_name: ["string", "Date", optional: true, nullable: true, multi: true, enum: %w[foo bar], comment: "Attribute description", deprecated: "Use `another_attribute` instead"]
         | 
| 109 113 | 
             
            ```
         | 
| 110 114 |  | 
| 111 115 | 
             
            ### TypeScript Integration
         | 
| @@ -252,6 +256,16 @@ Typelizer.configure do |config| | |
| 252 256 | 
             
              # Strategy for handling null values (:nullable, :optional, or :nullable_and_optional)
         | 
| 253 257 | 
             
              config.null_strategy = :nullable
         | 
| 254 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 | 
            +
             | 
| 255 269 | 
             
              # Directory where TypeScript interfaces will be generated
         | 
| 256 270 | 
             
              config.output_dir = Rails.root.join("app/javascript/types/serializers")
         | 
| 257 271 |  | 
    
        data/lib/typelizer/config.rb
    CHANGED
    
    | @@ -25,6 +25,8 @@ module Typelizer | |
| 25 25 | 
             
                :types_import_path,
         | 
| 26 26 | 
             
                :types_global,
         | 
| 27 27 | 
             
                :verbatim_module_syntax,
         | 
| 28 | 
            +
                :inheritance_strategy,
         | 
| 29 | 
            +
                :associations_strategy,
         | 
| 28 30 | 
             
                :comments,
         | 
| 29 31 | 
             
                keyword_init: true
         | 
| 30 32 | 
             
              ) do
         | 
| @@ -47,6 +49,8 @@ module Typelizer | |
| 47 49 |  | 
| 48 50 | 
             
                      type_mapping: TYPE_MAPPING,
         | 
| 49 51 | 
             
                      null_strategy: :nullable,
         | 
| 52 | 
            +
                      inheritance_strategy: :none,
         | 
| 53 | 
            +
                      associations_strategy: :database,
         | 
| 50 54 | 
             
                      comments: false,
         | 
| 51 55 |  | 
| 52 56 | 
             
                      output_dir: js_root.join("types/serializers"),
         | 
    
        data/lib/typelizer/interface.rb
    CHANGED
    
    | @@ -17,7 +17,7 @@ module Typelizer | |
| 17 17 |  | 
| 18 18 | 
             
                def name
         | 
| 19 19 | 
             
                  if inline?
         | 
| 20 | 
            -
                    Renderer. | 
| 20 | 
            +
                    Renderer.call("inline_type.ts.erb", properties: properties).strip
         | 
| 21 21 | 
             
                  else
         | 
| 22 22 | 
             
                    config.serializer_name_mapper.call(serializer).tr_s(":", "")
         | 
| 23 23 | 
             
                  end
         | 
| @@ -53,26 +53,56 @@ module Typelizer | |
| 53 53 | 
             
                  end
         | 
| 54 54 | 
             
                end
         | 
| 55 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 | 
            +
             | 
| 56 80 | 
             
                def imports
         | 
| 57 | 
            -
                   | 
| 58 | 
            -
                    . | 
| 59 | 
            -
             | 
| 81 | 
            +
                  @imports ||= begin
         | 
| 82 | 
            +
                    association_serializers, attribute_types = properties_to_print.filter_map(&:type)
         | 
| 83 | 
            +
                      .uniq
         | 
| 84 | 
            +
                      .partition { |type| type.is_a?(Interface) }
         | 
| 60 85 |  | 
| 61 | 
            -
             | 
| 62 | 
            -
             | 
| 86 | 
            +
                    serializer_types = association_serializers
         | 
| 87 | 
            +
                      .filter_map { |interface| interface.name if interface.name != name && !interface.inline? }
         | 
| 63 88 |  | 
| 64 | 
            -
             | 
| 65 | 
            -
             | 
| 66 | 
            -
             | 
| 67 | 
            -
             | 
| 89 | 
            +
                    custom_type_imports = attribute_types
         | 
| 90 | 
            +
                      .flat_map { |type| extract_typescript_types(type.to_s) }
         | 
| 91 | 
            +
                      .uniq
         | 
| 92 | 
            +
                      .reject { |type| global_type?(type) }
         | 
| 68 93 |  | 
| 69 | 
            -
             | 
| 94 | 
            +
                    (custom_type_imports + serializer_types + Array(parent_interface&.name)).uniq - Array(self_type_name)
         | 
| 95 | 
            +
                  end
         | 
| 70 96 | 
             
                end
         | 
| 71 97 |  | 
| 72 98 | 
             
                def inspect
         | 
| 73 99 | 
             
                  "<#{self.class.name} #{name} properties=#{properties.inspect}>"
         | 
| 74 100 | 
             
                end
         | 
| 75 101 |  | 
| 102 | 
            +
                def fingerprint
         | 
| 103 | 
            +
                  "<#{self.class.name} #{name} properties=[#{properties_to_print.map(&:fingerprint).join(", ")}]>"
         | 
| 104 | 
            +
                end
         | 
| 105 | 
            +
             | 
| 76 106 | 
             
                private
         | 
| 77 107 |  | 
| 78 108 | 
             
                def self_type_name
         | 
| @@ -90,7 +120,7 @@ module Typelizer | |
| 90 120 | 
             
                def infer_types(props, hash_name = :_typelizer_attributes)
         | 
| 91 121 | 
             
                  props.map do |prop|
         | 
| 92 122 | 
             
                    if serializer.respond_to?(hash_name)
         | 
| 93 | 
            -
                      dsl_type = serializer.public_send(hash_name)[prop. | 
| 123 | 
            +
                      dsl_type = serializer.public_send(hash_name)[prop.column_name.to_sym]
         | 
| 94 124 | 
             
                      if dsl_type&.any?
         | 
| 95 125 | 
             
                        next Property.new(prop.to_h.merge(dsl_type)).tap do |property|
         | 
| 96 126 | 
             
                          property.comment ||= model_plugin.comment_for(property) if config.comments && property.comment != false
         | 
| @@ -9,6 +9,30 @@ module Typelizer | |
| 9 9 | 
             
                  attr_reader :model_class, :config
         | 
| 10 10 |  | 
| 11 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 | 
            +
             | 
| 12 36 | 
             
                    column = model_class&.columns_hash&.dig(prop.column_name.to_s)
         | 
| 13 37 | 
             
                    return prop unless column
         | 
| 14 38 |  | 
    
        data/lib/typelizer/property.rb
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            module Typelizer
         | 
| 2 2 | 
             
              Property = Struct.new(
         | 
| 3 3 | 
             
                :name, :type, :optional, :nullable,
         | 
| 4 | 
            -
                :multi, :column_name, :comment, :enum,
         | 
| 4 | 
            +
                :multi, :column_name, :comment, :enum, :deprecated,
         | 
| 5 5 | 
             
                keyword_init: true
         | 
| 6 6 | 
             
              ) do
         | 
| 7 7 | 
             
                def inspect
         | 
| @@ -9,6 +9,12 @@ module Typelizer | |
| 9 9 | 
             
                  "<#{self.class.name} #{props}>"
         | 
| 10 10 | 
             
                end
         | 
| 11 11 |  | 
| 12 | 
            +
                def eql?(other)
         | 
| 13 | 
            +
                  return false unless other.is_a?(self.class)
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  fingerprint == other.fingerprint
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 12 18 | 
             
                def to_s
         | 
| 13 19 | 
             
                  type_str = type_name
         | 
| 14 20 | 
             
                  type_str = "Array<#{type_str}>" if multi
         | 
| @@ -17,6 +23,11 @@ module Typelizer | |
| 17 23 | 
             
                  "#{name}#{"?" if optional}: #{type_str}"
         | 
| 18 24 | 
             
                end
         | 
| 19 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 | 
            +
             | 
| 20 31 | 
             
                private
         | 
| 21 32 |  | 
| 22 33 | 
             
                def type_name
         | 
    
        data/lib/typelizer/renderer.rb
    CHANGED
    
    | @@ -4,6 +4,10 @@ require "erb" | |
| 4 4 |  | 
| 5 5 | 
             
            module Typelizer
         | 
| 6 6 | 
             
              class Renderer
         | 
| 7 | 
            +
                def self.call(template, **context)
         | 
| 8 | 
            +
                  new(template).call(**context)
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 7 11 | 
             
                def initialize(template)
         | 
| 8 12 | 
             
                  @erb = ERB.new(File.read(File.join(File.dirname(__FILE__), "templates/#{template}")), trim_mode: "-")
         | 
| 9 13 | 
             
                end
         | 
| @@ -24,5 +28,9 @@ module Typelizer | |
| 24 28 | 
             
                  spaces = " " * multiplier
         | 
| 25 29 | 
             
                  content.to_s.each_line.map { |line| line.blank? ? line : "#{spaces}#{line}" }.join
         | 
| 26 30 | 
             
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def render(template, **context)
         | 
| 33 | 
            +
                  Renderer.call(template, **context)
         | 
| 34 | 
            +
                end
         | 
| 27 35 | 
             
              end
         | 
| 28 36 | 
             
            end
         | 
| @@ -13,7 +13,7 @@ module Typelizer | |
| 13 13 |  | 
| 14 14 | 
             
                  def properties
         | 
| 15 15 | 
             
                    serializer._attributes.map do |name, attr|
         | 
| 16 | 
            -
                      build_property(name, attr)
         | 
| 16 | 
            +
                      build_property(name.is_a?(Symbol) ? name.name : name, attr)
         | 
| 17 17 | 
             
                    end
         | 
| 18 18 | 
             
                  end
         | 
| 19 19 |  | 
| @@ -22,19 +22,31 @@ module Typelizer | |
| 22 22 | 
             
                      :association, :one, :has_one,
         | 
| 23 23 | 
             
                      :many, :has_many,
         | 
| 24 24 | 
             
                      :attributes, :attribute,
         | 
| 25 | 
            +
                      :method_added,
         | 
| 25 26 | 
             
                      :nested_attribute, :nested,
         | 
| 26 27 | 
             
                      :meta
         | 
| 27 28 | 
             
                    ]
         | 
| 28 29 | 
             
                  end
         | 
| 29 30 |  | 
| 30 31 | 
             
                  def typelize_method_transform(method:, name:, binding:, type:, attrs:)
         | 
| 31 | 
            -
                     | 
| 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
         | 
| 32 39 |  | 
| 33 40 | 
             
                    super
         | 
| 34 41 | 
             
                  end
         | 
| 35 42 |  | 
| 36 43 | 
             
                  def root_key
         | 
| 37 | 
            -
                    serializer.new({}).send(:_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
         | 
| 38 50 | 
             
                  end
         | 
| 39 51 |  | 
| 40 52 | 
             
                  def meta_fields
         | 
| @@ -51,6 +63,12 @@ module Typelizer | |
| 51 63 | 
             
                  private
         | 
| 52 64 |  | 
| 53 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 | 
            +
             | 
| 54 72 | 
             
                    case attr
         | 
| 55 73 | 
             
                    when Symbol
         | 
| 56 74 | 
             
                      Property.new(
         | 
| @@ -59,7 +77,7 @@ module Typelizer | |
| 59 77 | 
             
                        optional: false,
         | 
| 60 78 | 
             
                        nullable: false,
         | 
| 61 79 | 
             
                        multi: false,
         | 
| 62 | 
            -
                        column_name:  | 
| 80 | 
            +
                        column_name: column_name,
         | 
| 63 81 | 
             
                        **options
         | 
| 64 82 | 
             
                      )
         | 
| 65 83 | 
             
                    when Proc
         | 
| @@ -69,7 +87,7 @@ module Typelizer | |
| 69 87 | 
             
                        optional: false,
         | 
| 70 88 | 
             
                        nullable: false,
         | 
| 71 89 | 
             
                        multi: false,
         | 
| 72 | 
            -
                        column_name:  | 
| 90 | 
            +
                        column_name: column_name,
         | 
| 73 91 | 
             
                        **options
         | 
| 74 92 | 
             
                      )
         | 
| 75 93 | 
             
                    when ::Alba::Association
         | 
| @@ -80,7 +98,7 @@ module Typelizer | |
| 80 98 | 
             
                        optional: false,
         | 
| 81 99 | 
             
                        nullable: false,
         | 
| 82 100 | 
             
                        multi: false, # we override this in typelize_method_transform
         | 
| 83 | 
            -
                        column_name:  | 
| 101 | 
            +
                        column_name: column_name,
         | 
| 84 102 | 
             
                        **options
         | 
| 85 103 | 
             
                      )
         | 
| 86 104 | 
             
                    when ::Alba::TypedAttribute
         | 
| @@ -91,7 +109,7 @@ module Typelizer | |
| 91 109 | 
             
                        # not sure if that's a good default tbh
         | 
| 92 110 | 
             
                        nullable: !alba_type.instance_variable_get(:@auto_convert),
         | 
| 93 111 | 
             
                        multi: false,
         | 
| 94 | 
            -
                        column_name:  | 
| 112 | 
            +
                        column_name: column_name,
         | 
| 95 113 | 
             
                        **ts_mapper[alba_type.name.to_s],
         | 
| 96 114 | 
             
                        **options
         | 
| 97 115 | 
             
                      )
         | 
| @@ -102,7 +120,7 @@ module Typelizer | |
| 102 120 | 
             
                        optional: false,
         | 
| 103 121 | 
             
                        nullable: false,
         | 
| 104 122 | 
             
                        multi: false,
         | 
| 105 | 
            -
                        column_name:  | 
| 123 | 
            +
                        column_name: column_name,
         | 
| 106 124 | 
             
                        **options
         | 
| 107 125 | 
             
                      )
         | 
| 108 126 | 
             
                    when ::Alba::ConditionalAttribute
         | 
| @@ -112,6 +130,18 @@ module Typelizer | |
| 112 130 | 
             
                    end
         | 
| 113 131 | 
             
                  end
         | 
| 114 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 | 
            +
             | 
| 115 145 | 
             
                  private
         | 
| 116 146 |  | 
| 117 147 | 
             
                  def ts_mapper
         | 
| @@ -19,11 +19,13 @@ module Typelizer | |
| 19 19 | 
             
                  def properties
         | 
| 20 20 | 
             
                    serializer._attributes_data.merge(serializer._reflections).flat_map do |key, association|
         | 
| 21 21 | 
             
                      type = association.options[:serializer] ? Interface.new(serializer: association.options[:serializer]) : nil
         | 
| 22 | 
            +
                      adapter = ActiveModelSerializers::Adapter.configured_adapter
         | 
| 22 23 | 
             
                      Property.new(
         | 
| 23 | 
            -
                        name: key.to_s,
         | 
| 24 | 
            +
                        name: adapter.transform_key_casing!(key.to_s, association.options),
         | 
| 24 25 | 
             
                        type: type,
         | 
| 25 26 | 
             
                        optional: association.options.key?(:if) || association.options.key?(:unless),
         | 
| 26 27 | 
             
                        multi: association.respond_to?(:collection?) && association.collection?,
         | 
| 28 | 
            +
                        deprecated: (association.options[:deprecated] if association.options.key?(:deprecated)),
         | 
| 27 29 | 
             
                        column_name: association.name.to_s
         | 
| 28 30 | 
             
                      )
         | 
| 29 31 | 
             
                    end
         | 
| @@ -13,6 +13,8 @@ module Typelizer | |
| 13 13 | 
             
                        Alba
         | 
| 14 14 | 
             
                      elsif defined?(ActiveModel::Serializer) && serializer.ancestors.include?(ActiveModel::Serializer)
         | 
| 15 15 | 
             
                        AMS
         | 
| 16 | 
            +
                      elsif defined?(::Panko::Serializer) && serializer.ancestors.include?(::Panko::Serializer)
         | 
| 17 | 
            +
                        Panko
         | 
| 16 18 | 
             
                      else
         | 
| 17 19 | 
             
                        raise "Can't guess serializer plugin for #{serializer}. " \
         | 
| 18 20 | 
             
                                "Please specify it with `config.serializer_plugin`."
         | 
| @@ -11,7 +11,11 @@ module Typelizer | |
| 11 11 | 
             
                  end
         | 
| 12 12 |  | 
| 13 13 | 
             
                  def properties
         | 
| 14 | 
            -
                    serializer. | 
| 14 | 
            +
                    transform_keys = serializer.try(:_transform_keys)
         | 
| 15 | 
            +
                    attributes = serializer._attributes
         | 
| 16 | 
            +
                    attributes = attributes.transform_keys(&transform_keys) if transform_keys
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    attributes
         | 
| 15 19 | 
             
                      .flat_map do |key, options|
         | 
| 16 20 | 
             
                        if options[:association] == :flat
         | 
| 17 21 | 
             
                          Interface.new(serializer: options.fetch(:serializer)).properties
         | 
| @@ -0,0 +1,63 @@ | |
| 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
         | 
| @@ -0,0 +1,8 @@ | |
| 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? -%>
         | 
| @@ -0,0 +1 @@ | |
| 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(' | ') + ">" : ""%>
         | 
| @@ -1,31 +1,28 @@ | |
| 1 | 
            -
             | 
| 1 | 
            +
            <% if interface.imports.any? -%>
         | 
| 2 2 | 
             
            import type {<%= interface.imports.join(", ") %>} from '<%= interface.config.types_import_path %>'
         | 
| 3 | 
            -
             | 
| 3 | 
            +
            <% end -%>
         | 
| 4 4 |  | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 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) -%>
         | 
| 8 12 | 
             
            <%= indent(property) %>;
         | 
| 9 | 
            -
             | 
| 13 | 
            +
            <% end -%>
         | 
| 10 14 | 
             
            }
         | 
| 11 | 
            -
             | 
| 12 | 
            -
            type <%= interface.name %> = {
         | 
| 13 | 
            -
              <%= interface.root_key %>: <%= interface.name %>Data;
         | 
| 14 | 
            -
            <%- interface.meta_fields&.each do |property| -%>
         | 
| 15 | 
            -
            <%= indent(property) %>;
         | 
| 16 | 
            -
            <%- end -%>
         | 
| 17 | 
            -
            }
         | 
| 18 | 
            -
            <%- else -%>
         | 
| 15 | 
            +
            <% end %><% if interface.root_key %>
         | 
| 19 16 | 
             
            type <%= interface.name %> = {
         | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 17 | 
            +
            <%= indent(interface.root_key) %>: <%= interface.name %>Data;
         | 
| 18 | 
            +
            <% interface.meta_fields&.each do |property| -%>
         | 
| 22 19 | 
             
            <%= indent(property) %>;
         | 
| 23 | 
            -
             | 
| 20 | 
            +
            <% end -%>
         | 
| 24 21 | 
             
            }
         | 
| 25 | 
            -
             | 
| 22 | 
            +
            <% end -%>
         | 
| 26 23 |  | 
| 27 | 
            -
             | 
| 24 | 
            +
            <% if interface.config.verbatim_module_syntax -%>
         | 
| 28 25 | 
             
            export type { <%= interface.name %> };
         | 
| 29 | 
            -
             | 
| 26 | 
            +
            <% else -%>
         | 
| 30 27 | 
             
            export default <%= interface.name %>;
         | 
| 31 | 
            -
             | 
| 28 | 
            +
            <% end -%>
         | 
    
        data/lib/typelizer/version.rb
    CHANGED
    
    
    
        data/lib/typelizer/writer.rb
    CHANGED
    
    | @@ -32,7 +32,7 @@ module Typelizer | |
| 32 32 | 
             
                end
         | 
| 33 33 |  | 
| 34 34 | 
             
                def write_interface(interface)
         | 
| 35 | 
            -
                  write_file("#{interface.filename}.ts", interface. | 
| 35 | 
            +
                  write_file("#{interface.filename}.ts", interface.fingerprint) do
         | 
| 36 36 | 
             
                    render_template("interface.ts.erb", interface: interface)
         | 
| 37 37 | 
             
                  end
         | 
| 38 38 | 
             
                end
         | 
    
        data/lib/typelizer.rb
    CHANGED
    
    | @@ -14,6 +14,7 @@ require_relative "typelizer/serializer_plugins/auto" | |
| 14 14 | 
             
            require_relative "typelizer/serializer_plugins/oj_serializers"
         | 
| 15 15 | 
             
            require_relative "typelizer/serializer_plugins/alba"
         | 
| 16 16 | 
             
            require_relative "typelizer/serializer_plugins/ams"
         | 
| 17 | 
            +
            require_relative "typelizer/serializer_plugins/panko"
         | 
| 17 18 |  | 
| 18 19 | 
             
            require_relative "typelizer/model_plugins/active_record"
         | 
| 19 20 | 
             
            require_relative "typelizer/model_plugins/poro"
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,13 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: typelizer
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.4.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Svyatoslav Kryukov
         | 
| 8 | 
            -
            autorequire:
         | 
| 9 8 | 
             
            bindir: bin
         | 
| 10 9 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date:  | 
| 10 | 
            +
            date: 2025-05-02 00:00:00.000000000 Z
         | 
| 12 11 | 
             
            dependencies:
         | 
| 13 12 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 13 | 
             
              name: railties
         | 
| @@ -51,8 +50,11 @@ files: | |
| 51 50 | 
             
            - lib/typelizer/serializer_plugins/auto.rb
         | 
| 52 51 | 
             
            - lib/typelizer/serializer_plugins/base.rb
         | 
| 53 52 | 
             
            - lib/typelizer/serializer_plugins/oj_serializers.rb
         | 
| 53 | 
            +
            - lib/typelizer/serializer_plugins/panko.rb
         | 
| 54 | 
            +
            - lib/typelizer/templates/comment.ts.erb
         | 
| 54 55 | 
             
            - lib/typelizer/templates/fingerprint.ts.erb
         | 
| 55 56 | 
             
            - lib/typelizer/templates/index.ts.erb
         | 
| 57 | 
            +
            - lib/typelizer/templates/inheritance.ts.erb
         | 
| 56 58 | 
             
            - lib/typelizer/templates/inline_type.ts.erb
         | 
| 57 59 | 
             
            - lib/typelizer/templates/interface.ts.erb
         | 
| 58 60 | 
             
            - lib/typelizer/version.rb
         | 
| @@ -67,7 +69,6 @@ metadata: | |
| 67 69 | 
             
              homepage_uri: https://github.com/skryukov/typelizer
         | 
| 68 70 | 
             
              source_code_uri: https://github.com/skryukov/typelizer
         | 
| 69 71 | 
             
              rubygems_mfa_required: 'true'
         | 
| 70 | 
            -
            post_install_message:
         | 
| 71 72 | 
             
            rdoc_options: []
         | 
| 72 73 | 
             
            require_paths:
         | 
| 73 74 | 
             
            - lib
         | 
| @@ -82,8 +83,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 82 83 | 
             
                - !ruby/object:Gem::Version
         | 
| 83 84 | 
             
                  version: '0'
         | 
| 84 85 | 
             
            requirements: []
         | 
| 85 | 
            -
            rubygems_version: 3. | 
| 86 | 
            -
            signing_key:
         | 
| 86 | 
            +
            rubygems_version: 3.6.2
         | 
| 87 87 | 
             
            specification_version: 4
         | 
| 88 88 | 
             
            summary: A TypeScript type generator for Ruby serializers.
         | 
| 89 89 | 
             
            test_files: []
         |