typelizer 0.5.5 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7b353423e2bc10fd89e3d944692c04b41b69f6b75567821060b5b9e84b5009a0
4
- data.tar.gz: 6abb793e276d1a6627284ac7e3e5a37888e9d48850f0f74c3475ee5034c06d11
3
+ metadata.gz: 57cc88a8063ebe9f00e0558f3dbd59aca579b5e191bdce38d3f8ca9b0451acee
4
+ data.tar.gz: 32cb1941e3ece707061fd7f2b1e6287710a0f9e2e8f8fc4d56b7f39fe444a7f6
5
5
  SHA512:
6
- metadata.gz: 92ebe7361f52e8644576b8eabffcf0602f24369e9f6a9bc82837550b123440438befef9fb0d730c8c96cab63e111858aa05088b65aa46ab2a1c6b2c58868fcff
7
- data.tar.gz: 9ee89e0e1761257e51a97ba4666c3cecc4c02581f1726117f67f5328be510b4fd05653f46effad3d33fb119dcee01c9734160be3b2893d3cf566a2d5079c05f5
6
+ metadata.gz: a9fb264406d6ea58bc1b7cc7e875bef11e08cf8f758786f0f63e4d110db5b0e39832615167068baea973a5cf5236f76cac537ce1cfd3f21abd3def162a33edd2
7
+ data.tar.gz: 74a8a3552d25fff6c5dee5837d42dc6f257d097944874ade9aee247d432ff7d85303ebb9a6e60ae000bb8634c2a47e4be2e52dc5f4b08d49bd06963473a19b43
data/CHANGELOG.md CHANGED
@@ -7,6 +7,42 @@ and this project adheres to [Semantic Versioning].
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.6.0] - 2026-01-14
11
+
12
+ ### Added
13
+
14
+ - New `imports_sort_order` configuration option for consistent import ordering in generated TypeScript interfaces. ([@jonmarkgo])
15
+
16
+ ```ruby
17
+ Typelizer.configure do |config|
18
+ # Sort imports alphabetically
19
+ config.imports_sort_order = :alphabetical
20
+ end
21
+ ```
22
+
23
+ Available options:
24
+ - `:none` (default) - preserve original order
25
+ - `:alphabetical` - sort imports A-Z (case-insensitive)
26
+ - `Proc` - custom sorting logic
27
+
28
+ ### Changed
29
+
30
+ - Rails enum attributes now generate named types (e.g., `PostCategory`) in a separate `Enums.ts` file instead of inline unions. ([@skryukov])
31
+
32
+ ### Fixed
33
+
34
+ - Fix `index.ts` not being regenerated when traits are added or removed. ([@skryukov])
35
+
36
+ ## [0.5.6] - 2026-01-12
37
+
38
+ ### Added
39
+
40
+ - Type inference for serialized fields. `serialize :skills, type: Array` generates `Array<unknown>`, `serialize :settings, type: Hash` generates `Record<string, unknown>`. ([@skryukov])
41
+
42
+ ### Fixed
43
+
44
+ - Alba: respect `key:` option for associations defined in traits. ([@skryukov])
45
+
10
46
  ## [0.5.5] - 2025-12-24
11
47
 
12
48
  ### Added
@@ -299,6 +335,7 @@ and this project adheres to [Semantic Versioning].
299
335
 
300
336
  [@davidrunger]: https://github.com/davidrunger
301
337
  [@Envek]: https://github.com/Envek
338
+ [@jonmarkgo]: https://github.com/jonmarkgo
302
339
  [@hkamberovic]: https://github.com/hkamberovic
303
340
  [@kristinemcbride]: https://github.com/kristinemcbride
304
341
  [@nkriege]: https://github.com/nkriege
@@ -310,7 +347,9 @@ and this project adheres to [Semantic Versioning].
310
347
  [@prog-supdex]: https://github.com/prog-supdex
311
348
  [@ventsislaf]: https://github.com/ventsislaf
312
349
 
313
- [Unreleased]: https://github.com/skryukov/typelizer/compare/v0.5.5...HEAD
350
+ [Unreleased]: https://github.com/skryukov/typelizer/compare/v0.6.0...HEAD
351
+ [0.6.0]: https://github.com/skryukov/typelizer/compare/v0.5.6...v0.6.0
352
+ [0.5.6]: https://github.com/skryukov/typelizer/compare/v0.5.5...v0.5.6
314
353
  [0.5.5]: https://github.com/skryukov/typelizer/compare/v0.5.4...v0.5.5
315
354
  [0.5.4]: https://github.com/skryukov/typelizer/compare/v0.5.3...v0.5.4
316
355
  [0.5.3]: https://github.com/skryukov/typelizer/compare/v0.5.2...v0.5.3
data/README.md CHANGED
@@ -493,6 +493,12 @@ Typelizer.configure do |config|
493
493
  # Proc - custom sorting function receiving array of Property objects
494
494
  config.properties_sort_order = :none
495
495
 
496
+ # Strategy for ordering imports in generated TypeScript interfaces
497
+ # :none - preserve original order (default)
498
+ # :alphabetical - sort imports A-Z (case-insensitive)
499
+ # Proc - custom sorting function receiving array of import strings
500
+ config.imports_sort_order = :none
501
+
496
502
  # Plugin for model type inference (default: ModelPlugins::Auto)
497
503
  config.model_plugin = Typelizer::ModelPlugins::Auto
498
504
 
@@ -24,6 +24,7 @@ module Typelizer
24
24
  :serializer_model_mapper,
25
25
  :properties_transformer,
26
26
  :properties_sort_order,
27
+ :imports_sort_order,
27
28
  :model_plugin,
28
29
  :serializer_plugin,
29
30
  :plugin_configs,
@@ -79,6 +80,7 @@ module Typelizer
79
80
  types_global: DEFAULT_TYPES_GLOBAL,
80
81
  properties_transformer: nil,
81
82
  properties_sort_order: :none,
83
+ imports_sort_order: :none,
82
84
  verbatim_module_syntax: false
83
85
  }
84
86
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Typelizer
4
+ module ImportSorter
5
+ def self.sort(imports, sort_order)
6
+ case sort_order
7
+ when :none, nil
8
+ imports
9
+ when :alphabetical
10
+ imports.sort_by { |i| i.to_s.downcase }
11
+ when Proc
12
+ result = sort_order.call(imports)
13
+ result.is_a?(Array) ? result : imports
14
+ else
15
+ imports
16
+ end
17
+ rescue => e
18
+ Typelizer.logger.warn("ImportSorter error: #{e.message}, preserving original order")
19
+ imports
20
+ end
21
+ end
22
+ end
@@ -58,6 +58,15 @@ module Typelizer
58
58
  @trait_interfaces ||= serializer_plugin.trait_interfaces
59
59
  end
60
60
 
61
+ def enum_types
62
+ @enum_types ||= begin
63
+ all_properties = properties + trait_interfaces.flat_map(&:properties)
64
+ all_properties
65
+ .select(&:enum_definition)
66
+ .uniq(&:enum_type_name)
67
+ end
68
+ end
69
+
61
70
  def properties
62
71
  @properties ||= begin
63
72
  props = serializer_plugin.properties
@@ -119,7 +128,11 @@ module Typelizer
119
128
  prop.with_traits.map { |t| "#{prop.type.name}#{t.to_s.camelize}Trait" }
120
129
  end
121
130
 
122
- (custom_type_imports + serializer_types + trait_imports + Array(parent_interface&.name)).uniq - [self_type_name, name]
131
+ # Collect enum type names from properties
132
+ enum_imports = all_properties.filter_map(&:enum_type_name)
133
+
134
+ result = (custom_type_imports + serializer_types + trait_imports + enum_imports + Array(parent_interface&.name)).uniq - [self_type_name, name]
135
+ ImportSorter.sort(result, config.imports_sort_order)
123
136
  end
124
137
  end
125
138
 
@@ -28,6 +28,8 @@ module Typelizer
28
28
  return unless model_class&.defined_enums&.key?(prop.column_name.to_s)
29
29
 
30
30
  prop.enum = model_class.defined_enums[prop.column_name.to_s].keys
31
+ prop.enum_type_name = "#{model_class.name.demodulize}#{prop.column_name.to_s.camelize}"
32
+ prop.enum
31
33
  end
32
34
 
33
35
  private
@@ -104,6 +106,10 @@ module Typelizer
104
106
  attribute_type_obj = model_class.attribute_types.fetch(prop.column_name.to_s, nil)
105
107
  return nil unless attribute_type_obj
106
108
 
109
+ if attribute_type_obj.instance_of?(::ActiveRecord::Type::Serialized)
110
+ return infer_types_for_serialized(prop, attribute_type_obj)
111
+ end
112
+
107
113
  if attribute_type_obj.respond_to?(:subtype)
108
114
  prop.type = @config.type_mapping[attribute_type_obj.subtype.type]
109
115
  prop.multi = true
@@ -113,6 +119,23 @@ module Typelizer
113
119
 
114
120
  prop
115
121
  end
122
+
123
+ def infer_types_for_serialized(prop, type_obj)
124
+ object_class = type_obj.coder.try(:object_class) ||
125
+ type_obj.try(:object_class)
126
+
127
+ case object_class&.to_s
128
+ when "Array"
129
+ prop.type = :unknown
130
+ prop.multi = true
131
+ when "Hash"
132
+ prop.type = "Record<string, unknown>"
133
+ else
134
+ prop.type = :unknown
135
+ end
136
+
137
+ prop
138
+ end
116
139
  end
117
140
  end
118
141
  end
@@ -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, :deprecated,
4
+ :multi, :column_name, :comment, :enum, :enum_type_name, :deprecated,
5
5
  :with_traits,
6
6
  keyword_init: true
7
7
  ) do
@@ -39,10 +39,16 @@ module Typelizer
39
39
  end << ">"
40
40
  end
41
41
 
42
+ def enum_definition
43
+ return unless enum && enum_type_name
44
+
45
+ "type #{enum_type_name} = #{enum.map { |v| v.to_s.inspect }.join(" | ")}"
46
+ end
47
+
42
48
  private
43
49
 
44
50
  def type_name
45
- return enum.map { |v| v.to_s.inspect }.join(" | ") if enum
51
+ return enum_type_name if enum_type_name
46
52
 
47
53
  type.respond_to?(:name) ? type.name : type || "unknown"
48
54
  end
@@ -54,17 +54,19 @@ module Typelizer
54
54
  end
55
55
 
56
56
  # Simple struct to hold association info from traits
57
- TraitAssociation = Struct.new(:name, :resource, :with_traits, :multi, keyword_init: true)
57
+ TraitAssociation = Struct.new(:name, :resource, :with_traits, :multi, :key, keyword_init: true)
58
58
 
59
59
  # Support association methods that might be used in traits
60
60
  def one(name, **options, &block)
61
61
  resource = options[:resource] || options[:serializer]
62
62
  with_traits = options[:with_traits]
63
- @collected_attributes[name] = TraitAssociation.new(
63
+ key = options[:key] || name
64
+ @collected_attributes[key] = TraitAssociation.new(
64
65
  name: name,
65
66
  resource: resource,
66
67
  with_traits: with_traits,
67
- multi: false
68
+ multi: false,
69
+ key: key
68
70
  )
69
71
  end
70
72
 
@@ -74,11 +76,13 @@ module Typelizer
74
76
  def many(name, **options, &block)
75
77
  resource = options[:resource] || options[:serializer]
76
78
  with_traits = options[:with_traits]
77
- @collected_attributes[name] = TraitAssociation.new(
79
+ key = options[:key] || name
80
+ @collected_attributes[key] = TraitAssociation.new(
78
81
  name: name,
79
82
  resource: resource,
80
83
  with_traits: with_traits,
81
- multi: true
84
+ multi: true,
85
+ key: key
82
86
  )
83
87
  end
84
88
 
@@ -0,0 +1,3 @@
1
+ <%- enums.each do |property| -%>
2
+ export <%= property.enum_definition %>;
3
+ <%- end -%>
@@ -1,3 +1,6 @@
1
+ <%- if enums.any? -%>
2
+ export type { <%= enums.map(&:enum_type_name).join(", ") %> } from './Enums'
3
+ <%- end -%>
1
4
  <%- interfaces.each do |interface| -%>
2
5
  <%- if interface.config.verbatim_module_syntax -%>
3
6
  export type { <%= interface.name %><%= ", " + interface.trait_interfaces.map(&:name).join(", ") if interface.trait_interfaces.any? %> } from <%= interface.quote('./' + interface.filename) %>
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Typelizer
4
- VERSION = "0.5.5"
4
+ VERSION = "0.6.0"
5
5
  end
@@ -22,7 +22,10 @@ module Typelizer
22
22
  begin
23
23
  written_files.concat(valid_interfaces.map { |interface| write_interface(interface) })
24
24
 
25
- written_files << write_index(valid_interfaces)
25
+ enums = collect_enums(valid_interfaces)
26
+ written_files << write_enums(enums) if enums.any?
27
+
28
+ written_files << write_index(valid_interfaces, enums: enums)
26
29
 
27
30
  cleanup_stale_files(written_files) unless force
28
31
 
@@ -49,9 +52,23 @@ module Typelizer
49
52
  File.delete(*stale_files) unless stale_files.empty?
50
53
  end
51
54
 
52
- def write_index(interfaces)
53
- write_file("index.ts", interfaces.map(&:filename).join) do
54
- render_template("index.ts.erb", interfaces: interfaces)
55
+ def collect_enums(interfaces)
56
+ interfaces
57
+ .flat_map(&:enum_types)
58
+ .uniq(&:enum_type_name)
59
+ .sort_by(&:enum_type_name)
60
+ end
61
+
62
+ def write_enums(enums)
63
+ write_file("Enums.ts", enums.map(&:fingerprint).join) do
64
+ render_template("enums.ts.erb", enums: enums)
65
+ end
66
+ end
67
+
68
+ def write_index(interfaces, enums: [])
69
+ fingerprint = interfaces.map(&:fingerprint).join + enums.map(&:fingerprint).join
70
+ write_file("index.ts", fingerprint) do
71
+ render_template("index.ts.erb", interfaces: interfaces, enums: enums)
55
72
  end
56
73
  end
57
74
 
data/lib/typelizer.rb CHANGED
@@ -12,6 +12,7 @@ require_relative "typelizer/serializer_config_layer"
12
12
  require_relative "typelizer/contexts/writer_context"
13
13
  require_relative "typelizer/contexts/scan_context"
14
14
  require_relative "typelizer/property_sorter"
15
+ require_relative "typelizer/import_sorter"
15
16
  require_relative "typelizer/interface"
16
17
  require_relative "typelizer/renderer"
17
18
  require_relative "typelizer/writer"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: typelizer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.5
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Svyatoslav Kryukov
@@ -54,6 +54,7 @@ files:
54
54
  - lib/typelizer/contexts/writer_context.rb
55
55
  - lib/typelizer/dsl.rb
56
56
  - lib/typelizer/generator.rb
57
+ - lib/typelizer/import_sorter.rb
57
58
  - lib/typelizer/interface.rb
58
59
  - lib/typelizer/listen.rb
59
60
  - lib/typelizer/model_plugins/active_record.rb
@@ -73,6 +74,7 @@ files:
73
74
  - lib/typelizer/serializer_plugins/oj_serializers.rb
74
75
  - lib/typelizer/serializer_plugins/panko.rb
75
76
  - lib/typelizer/templates/comment.ts.erb
77
+ - lib/typelizer/templates/enums.ts.erb
76
78
  - lib/typelizer/templates/fingerprint.ts.erb
77
79
  - lib/typelizer/templates/index.ts.erb
78
80
  - lib/typelizer/templates/inheritance.ts.erb