typelizer 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 45bdcfd99663e0a61f6d70b5825f4ee29db4bc70927676c162fab5e8557675d6
4
- data.tar.gz: ff3df8f4e1433bf7e8eb2f88318c117d58f86d14b974d6c1100fafe04988edc6
3
+ metadata.gz: d00f7bda8328c5148fe2bb6053d27066dd128fd834432c92d39417c3bbdc9405
4
+ data.tar.gz: 8b7fc9baa4b646cd91965b1316d9c9dcb22867a5bc8b65407c3fa36bbff9d85c
5
5
  SHA512:
6
- metadata.gz: ed256b928dd5621f2a573c74997bcb2dd072af8ed02e79919e3f0506056bfd579405d234257136d87a3840393cc6fa625f2b50c1e9362eabcb5b7682e0d0eb00
7
- data.tar.gz: ba1dab8fa94374dc25b02ac67b7e995f4a5d10c9492ceddf7bb866f22abaf609d148393af8a6c1eb899ccb553f0b2be542618811b58e1ae6b08f0d9d95d916ae
6
+ metadata.gz: 9174cd537b757ea89f7a886c1443e12a78f16b269bc16ccb2e49866128c47471df83e55c32cc60ef7148ea4e8cd15d5cc1552f907dad903931c043af97ab6bc2
7
+ data.tar.gz: f4d59939214975e140d1a9db3cdc8c5bd9a3278baf6f59d686af7434499917ae30581fcb2a2681c1e106372fcda819250a47f5ba61a4844819dac660f8dea93a
data/CHANGELOG.md CHANGED
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning].
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.0] - 2024-11-26
11
+
12
+ ## Added
13
+
14
+ - Add support for enum attributes declared using `ActiveRecord::Enum` or explicitly in serializers ([@envek])
15
+ - Add support for comments in generated TypeScript interfaces ([@envek])
16
+ - Add TypeScript verbatim module syntax support through `verbatim_module_syntax` config option ([@patvice])
17
+ - Add `typelizer:generate:refresh` command to clean output directory and regenerate all interfaces ([@patvice])
18
+ - Allow disabling Typelizer in Rails development with `DISABLE_TYPELIZER` environment variable to `true` ([@okuramasafumi])
19
+
20
+ ## Fixes
21
+
22
+ - Do not override `Typelizer.dirs` in the railtie initializer ([@patvice])
23
+ - Do not raise on empty nested serializers ([@skryukov])
24
+ - Attribute options merging in inherited serializers ([@envek])
25
+ - Allow recursive type definition ([@okuramasafumi])
26
+
27
+ ## [0.1.5] - 2024-10-07
28
+
29
+ ## Fixed
30
+
31
+ - Fix the duplicated import with multiple same association ([@okuramasafumi])
32
+
10
33
  ## [0.1.4] - 2024-10-04
11
34
 
12
35
  ## Added
@@ -52,10 +75,14 @@ and this project adheres to [Semantic Versioning].
52
75
  - Initial release ([@skryukov])
53
76
 
54
77
  [@davidrunger]: https://github.com/davidrunger
55
- [@okuramasafumi]: https://github.com/@okuramasafumi
78
+ [@envek]: https://github.com/envek
79
+ [@okuramasafumi]: https://github.com/okuramasafumi
80
+ [@patvice]: https://github.com/patvice
56
81
  [@skryukov]: https://github.com/skryukov
57
82
 
58
- [Unreleased]: https://github.com/skryukov/typelizer/compare/v0.1.4...HEAD
83
+ [Unreleased]: https://github.com/skryukov/typelizer/compare/v0.2.0...HEAD
84
+ [0.2.0]: https://github.com/skryukov/typelizer/compare/v0.1.5...v0.2.0
85
+ [0.1.5]: https://github.com/skryukov/typelizer/compare/v0.1.4...v0.1.5
59
86
  [0.1.4]: https://github.com/skryukov/typelizer/compare/v0.1.3...v0.1.4
60
87
  [0.1.3]: https://github.com/skryukov/typelizer/compare/v0.1.2...v0.1.3
61
88
  [0.1.2]: https://github.com/skryukov/typelizer/compare/v0.1.1...v0.1.2
data/README.md CHANGED
@@ -14,6 +14,7 @@ Typelizer is a Ruby gem that automatically generates TypeScript interfaces from
14
14
  - [TypeScript Integration](#typescript-integration)
15
15
  - [Manual Generation](#manual-generation)
16
16
  - [Automatic Generation in Development](#automatic-generation-in-development)
17
+ - [Disabling Typelizer](#disabling-typelizer)
17
18
  - [Configuration](#configuration)
18
19
  - [Global Configuration](#global-configuration)
19
20
  - [Config Options](#config-options)
@@ -79,9 +80,34 @@ class PostResource < ApplicationResource
79
80
  attribute :author_name do |post|
80
81
  post.author.name
81
82
  end
83
+
84
+ typelize :string, nullable: true, comment: "Author's avatar URL"
85
+ attribute :avatar do
86
+ "https://example.com/avatar.png" if active?
87
+ end
82
88
  end
83
89
  ```
84
90
 
91
+ `typelize` can be used with a Hash to specify multiple types at once.
92
+
93
+ ```ruby
94
+ class PostResource < ApplicationResource
95
+ attributes :id, :title, :body, :published_at
96
+
97
+ attribute :author_name do |post|
98
+ post.author.name
99
+ end
100
+
101
+ typelize author_name: :string, published_at: :string
102
+ end
103
+ ```
104
+
105
+ You can also specify more complex type definitions using a lower-level API:
106
+
107
+ ```ruby
108
+ typelize attribute_name: ["string", "Date", optional: true, nullable: true, multi: true, enum: %w[foo bar], comment: "Attribute description"]
109
+ ```
110
+
85
111
  ### TypeScript Integration
86
112
 
87
113
  Typelizer generates TypeScript interfaces in the specified output directory:
@@ -91,6 +117,7 @@ Typelizer generates TypeScript interfaces in the specified output directory:
91
117
  export interface Post {
92
118
  id: number;
93
119
  title: string;
120
+ category?: "news" | "article" | "blog" | null;
94
121
  body: string;
95
122
  published_at: string | null;
96
123
  author_name: string;
@@ -156,11 +183,15 @@ See the [Configuration](#configuration) section for more options.
156
183
 
157
184
  ### Manual Generation
158
185
 
159
- To manually generate TypeScript interfaces:
186
+ To manually generate TypeScript interfaces use one of the following commands:
160
187
 
161
- ```
162
- $ rails typelizer:generate
163
- ```
188
+ ```bash
189
+ # Generate new interfaces
190
+ rails typelizer:generate
191
+
192
+ # Clean output directory and regenerate all interfaces
193
+ rails typelizer:generate:refresh
194
+ ````
164
195
 
165
196
  ### Automatic Generation in Development
166
197
 
@@ -170,6 +201,10 @@ When [Listen](https://github.com/guard/listen) is installed, Typelizer automatic
170
201
  Typelizer.listen = false
171
202
  ```
172
203
 
204
+ ### Disabling Typelizer
205
+
206
+ 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.
207
+
173
208
  ## Configuration
174
209
 
175
210
  ### Global Configuration
@@ -227,6 +262,14 @@ Typelizer.configure do |config|
227
262
  # List of type names that should be considered global in TypeScript
228
263
  # (i.e. not prefixed with the import path)
229
264
  config.types_global << %w[Array Date Record File FileList]
265
+
266
+ # Support TypeScript's Verbatim module syntax option (default: false)
267
+ # Will change imports and exports of types from default to support this syntax option
268
+ config.verbatim_module_syntax = false
269
+
270
+ # Support comments in generated TypeScript interfaces (default: false)
271
+ # Will add comments to the generated interfaces
272
+ config.comments = false
230
273
  end
231
274
  ```
232
275
 
@@ -1,14 +1,27 @@
1
1
  namespace :typelizer do
2
2
  desc "Generate TypeScript interfaces from serializers"
3
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)
4
17
  require "benchmark"
5
18
 
6
- ENV["TYPELIZER"] = "true"
19
+ ENV["DISABLE_TYPELIZER"] = "false"
7
20
 
8
21
  puts "Generating TypeScript interfaces..."
9
22
  serializers = []
10
23
  time = Benchmark.realtime do
11
- serializers = Typelizer::Generator.call
24
+ serializers = block.call
12
25
  end
13
26
 
14
27
  puts "Finished in #{time} seconds"
@@ -24,6 +24,8 @@ module Typelizer
24
24
  :output_dir,
25
25
  :types_import_path,
26
26
  :types_global,
27
+ :verbatim_module_syntax,
28
+ :comments,
27
29
  keyword_init: true
28
30
  ) do
29
31
  class << self
@@ -45,13 +47,15 @@ module Typelizer
45
47
 
46
48
  type_mapping: TYPE_MAPPING,
47
49
  null_strategy: :nullable,
50
+ comments: false,
48
51
 
49
52
  output_dir: js_root.join("types/serializers"),
50
53
 
51
54
  types_import_path: "@/types",
52
55
  types_global: %w[Array Date Record File FileList],
53
56
 
54
- properties_transformer: nil
57
+ properties_transformer: nil,
58
+ verbatim_module_syntax: false
55
59
  )
56
60
  end
57
61
 
@@ -70,7 +74,6 @@ module Typelizer
70
74
 
71
75
  def method_missing(method, *args, &block)
72
76
  return Typelizer.send(method, *args, &block) if Typelizer.respond_to?(method)
73
-
74
77
  instance.send(method, *args, &block)
75
78
  end
76
79
  end
data/lib/typelizer/dsl.rb CHANGED
@@ -64,9 +64,11 @@ module Typelizer
64
64
 
65
65
  unless respond_to?(attribute_name)
66
66
  define_singleton_method(attribute_name) do
67
- result = instance_variable_get(instance_variable)
67
+ result = instance_variable_get(instance_variable) || {}
68
68
  if superclass.respond_to?(attribute_name)
69
- result.merge(superclass.send(attribute_name))
69
+ result.merge(superclass.send(attribute_name)) do |key, currentdef, supervaldef|
70
+ supervaldef.merge(currentdef)
71
+ end
70
72
  else
71
73
  result
72
74
  end
@@ -2,8 +2,8 @@
2
2
 
3
3
  module Typelizer
4
4
  class Generator
5
- def self.call
6
- new.call
5
+ def self.call(**args)
6
+ new.call(**args)
7
7
  end
8
8
 
9
9
  def initialize(config = Typelizer::Config)
@@ -16,14 +16,18 @@ module Typelizer
16
16
  def call(force: false)
17
17
  return unless Typelizer.enabled?
18
18
 
19
- read_serializers
20
-
21
- interfaces = target_serializers.map(&:typelizer_interface).reject(&:empty?)
22
19
  writer.call(interfaces, force: force)
23
20
 
24
21
  interfaces
25
22
  end
26
23
 
24
+ def interfaces
25
+ @interfaces ||= begin
26
+ read_serializers
27
+ target_serializers.map(&:typelizer_interface).reject(&:empty?)
28
+ end
29
+ end
30
+
27
31
  private
28
32
 
29
33
  def target_serializers
@@ -66,7 +66,7 @@ module Typelizer
66
66
  .uniq
67
67
  .reject { |type| global_type?(type) }
68
68
 
69
- custom_type_imports + serializer_types
69
+ (custom_type_imports + serializer_types).uniq - Array(self_type_name)
70
70
  end
71
71
 
72
72
  def inspect
@@ -75,6 +75,10 @@ module Typelizer
75
75
 
76
76
  private
77
77
 
78
+ def self_type_name
79
+ serializer.name.match(/(\w+::)?(\w+)(Serializer|Resource)/)[2]
80
+ end
81
+
78
82
  def extract_typescript_types(type)
79
83
  type.split(/[<>\[\],\s|]+/)
80
84
  end
@@ -87,7 +91,12 @@ module Typelizer
87
91
  props.map do |prop|
88
92
  if serializer.respond_to?(hash_name)
89
93
  dsl_type = serializer.public_send(hash_name)[prop.name.to_sym]
90
- next Property.new(prop.to_h.merge(dsl_type)) if dsl_type&.any?
94
+ if dsl_type&.any?
95
+ next Property.new(prop.to_h.merge(dsl_type)).tap do |property|
96
+ property.comment ||= model_plugin.comment_for(property) if config.comments && property.comment != false
97
+ property.enum ||= model_plugin.enum_for(property) if property.enum != false
98
+ end
99
+ end
91
100
  end
92
101
 
93
102
  model_plugin.infer_types(prop)
@@ -2,14 +2,14 @@ module Typelizer
2
2
  module ModelPlugins
3
3
  class ActiveRecord
4
4
  def initialize(model_class:, config:)
5
- @columns_hash = model_class&.columns_hash || {}
5
+ @model_class = model_class
6
6
  @config = config
7
7
  end
8
8
 
9
- attr_reader :columns_hash, :config
9
+ attr_reader :model_class, :config
10
10
 
11
11
  def infer_types(prop)
12
- column = columns_hash[prop.column_name.to_s]
12
+ column = model_class&.columns_hash&.dig(prop.column_name.to_s)
13
13
  return prop unless column
14
14
 
15
15
  prop.multi = !!column.try(:array)
@@ -26,9 +26,25 @@ module Typelizer
26
26
  end
27
27
 
28
28
  prop.type = @config.type_mapping[column.type]
29
+ prop.comment = comment_for(prop)
30
+ prop.enum = enum_for(prop)
31
+ prop.type = :string if prop.enum # Ignore underlying column type for enums
29
32
 
30
33
  prop
31
34
  end
35
+
36
+ def comment_for(prop)
37
+ column = model_class&.columns_hash&.dig(prop.column_name.to_s)
38
+ return nil unless column
39
+
40
+ prop.comment = column.comment
41
+ end
42
+
43
+ def enum_for(prop)
44
+ return unless model_class&.defined_enums&.key?(prop.column_name.to_s)
45
+
46
+ prop.enum = model_class.defined_enums[prop.column_name.to_s].keys
47
+ end
32
48
  end
33
49
  end
34
50
  end
@@ -8,6 +8,14 @@ module Typelizer
8
8
  def infer_types(prop)
9
9
  prop
10
10
  end
11
+
12
+ def comment_for(prop)
13
+ nil
14
+ end
15
+
16
+ def enum_for(prop)
17
+ nil
18
+ end
11
19
  end
12
20
  end
13
21
  end
@@ -1,7 +1,7 @@
1
1
  module Typelizer
2
2
  Property = Struct.new(
3
3
  :name, :type, :optional, :nullable,
4
- :multi, :column_name,
4
+ :multi, :column_name, :comment, :enum,
5
5
  keyword_init: true
6
6
  ) do
7
7
  def inspect
@@ -20,6 +20,8 @@ module Typelizer
20
20
  private
21
21
 
22
22
  def type_name
23
+ return enum.map { |v| v.to_s.inspect }.join(" | ") if enum
24
+
23
25
  type.respond_to?(:name) ? type.name : type || "unknown"
24
26
  end
25
27
  end
@@ -6,10 +6,12 @@ module Typelizer
6
6
 
7
7
  initializer "typelizer.configure" do
8
8
  Typelizer.configure do |c|
9
- c.dirs = [
10
- Rails.root.join("app", "resources"),
11
- Rails.root.join("app", "serializers")
12
- ]
9
+ if c.dirs.empty?
10
+ c.dirs = [
11
+ Rails.root.join("app", "resources"),
12
+ Rails.root.join("app", "serializers")
13
+ ]
14
+ end
13
15
  end
14
16
  end
15
17
 
@@ -1,3 +1,7 @@
1
1
  <%- interfaces.each do |interface| -%>
2
+ <%- if interface.config.verbatim_module_syntax -%>
3
+ export type { <%= interface.name %> } from './<%= interface.filename %>'
4
+ <%- else -%>
2
5
  export type { default as <%= interface.name %> } from './<%= interface.filename %>'
3
6
  <%- end -%>
7
+ <%- end -%>
@@ -18,9 +18,14 @@ type <%= interface.name %> = {
18
18
  <%- else -%>
19
19
  type <%= interface.name %> = {
20
20
  <%- interface.properties.each do |property| -%>
21
+ <%= indent("/** #{property.comment.split("\n").map(&:strip).join("\n * ")} */\n") if interface.config.comments && property.comment -%>
21
22
  <%= indent(property) %>;
22
23
  <%- end -%>
23
24
  }
24
25
  <%- end -%>
25
26
 
27
+ <%-if interface.config.verbatim_module_syntax -%>
28
+ export type { <%= interface.name %> };
29
+ <%- else -%>
26
30
  export default <%= interface.name %>;
31
+ <%- end -%>
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Typelizer
4
- VERSION = "0.1.4"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/typelizer.rb CHANGED
@@ -26,7 +26,9 @@ require "logger"
26
26
  module Typelizer
27
27
  class << self
28
28
  def enabled?
29
- ENV["RAILS_ENV"] == "development" || ENV["RACK_ENV"] == "development" || ENV["TYPELIZER"] == "true"
29
+ return false if ENV["DISABLE_TYPELIZER"] == "true" || ENV["DISABLE_TYPELIZER"] == "1"
30
+
31
+ ENV["RAILS_ENV"] == "development" || ENV["RACK_ENV"] == "development" || ENV["DISABLE_TYPELIZER"] == "false"
30
32
  end
31
33
 
32
34
  attr_accessor :dirs
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: typelizer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Svyatoslav Kryukov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-04 00:00:00.000000000 Z
11
+ date: 2024-11-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -82,7 +82,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
82
82
  - !ruby/object:Gem::Version
83
83
  version: '0'
84
84
  requirements: []
85
- rubygems_version: 3.5.17
85
+ rubygems_version: 3.5.23
86
86
  signing_key:
87
87
  specification_version: 4
88
88
  summary: A TypeScript type generator for Ruby serializers.