typelizer 0.13.0 → 0.14.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 +35 -1
- data/README.md +5 -5
- data/lib/typelizer/config.rb +1 -1
- data/lib/typelizer/dsl/hooks/blueprinter.rb +14 -0
- data/lib/typelizer/dsl/hooks.rb +3 -0
- data/lib/typelizer/interface.rb +2 -2
- data/lib/typelizer/model_plugins/active_record.rb +11 -2
- data/lib/typelizer/openapi.rb +2 -0
- data/lib/typelizer/property.rb +4 -2
- data/lib/typelizer/route_generator.rb +28 -26
- data/lib/typelizer/route_writer.rb +6 -2
- data/lib/typelizer/serializer_plugins/alba.rb +1 -1
- data/lib/typelizer/serializer_plugins/auto.rb +2 -0
- data/lib/typelizer/serializer_plugins/blueprinter.rb +71 -0
- data/lib/typelizer/serializer_plugins/{alba/trait_interface.rb → trait_interface.rb} +5 -4
- data/lib/typelizer/version.rb +1 -1
- data/lib/typelizer.rb +1 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8810d416e08a0541a844bf7e538fbd66999f1ab5e0b6209d5e7c071b06c58fdf
|
|
4
|
+
data.tar.gz: 725952a78be5d5f3e13d10e876badfc2be72e848af6bfd2a8063b0f34d0b28c8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a6e833349308d44de700a56865f948f8fa1a2d352acf23d34ab6d9fe22c548ceeef70d566fb78e700f985334f3eb40cabbe8542900b792b6966b98a7e3c07698
|
|
7
|
+
data.tar.gz: a6570c070f5b20876814b47045862e2aeeba6589bbd0c6220b43d958f9d46f93e08d54fa3aa1fdc81f74bc874d86acd3f5fe5bc9d4fb4ba7d828534a7ae31c0e
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,35 @@ and this project adheres to [Semantic Versioning].
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.14.0] - 2026-10-03
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- [Blueprinter](https://github.com/procore-oss/blueprinter) support, including views. ([@skryukov])
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Associations and nested attributes no longer take the type of a model column with the same name. ([@lukasedw])
|
|
19
|
+
- Generated route files no longer have duplicate keys when a resource is routed more than once, e.g. nested under several parents. ([@visini])
|
|
20
|
+
- OpenAPI: a string literal mixed with other types (`'auto' | number`) now becomes a string enum instead of a broken `$ref`. ([@skryukov])
|
|
21
|
+
- Conditional attributes (`if:`/`unless:`) stay optional under `null_strategy :optional` and `:nullable_and_optional`, even when the column is NOT NULL. ([@skryukov])
|
|
22
|
+
- Generation no longer crashes for a serializer whose class name contains neither `Serializer` nor `Resource`. ([@skryukov])
|
|
23
|
+
- Keys that aren't valid identifiers (e.g. from Alba's `transform_keys :dash`) are now quoted, and quotes in enum values are escaped. Run `rails typelizer:generate:refresh` once to fix existing files. ([@skryukov])
|
|
24
|
+
|
|
25
|
+
## [0.13.1] - 2026-05-18
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- `routes.include` and `routes.exclude` now accept a `Proc` (or any callable responding to `#call`) in addition to `Regexp`. Predicates receive the route-info hash (`:path`, `:name`, `:controller`, `:action`, `:verb`, `:required_parts`, `:optional_parts`) and must return truthy to match. Heterogeneous arrays are supported. Useful when the URL path alone can't disambiguate routes — e.g. multiple feature areas mounted under different subdomain constraints that share paths, or filtering by HTTP verb / helper name. ([@pastpatryk])
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
config.routes.include = ->(r) { r[:controller].to_s.start_with?("admin/") }
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- Unnamed alias routes sharing `controller#action` with a named route (e.g. ActiveStorage representations) no longer inherit the named route's name. Name lookup now matches by `[controller, path]` instead of `[controller, action]`, so distinct paths produce distinct entries. ([@skryukov])
|
|
38
|
+
|
|
10
39
|
## [0.13.0] - 2026-05-01
|
|
11
40
|
|
|
12
41
|
### Added
|
|
@@ -492,9 +521,11 @@ and this project adheres to [Semantic Versioning].
|
|
|
492
521
|
[@jonmarkgo]: https://github.com/jonmarkgo
|
|
493
522
|
[@julik]: https://github.com/julik
|
|
494
523
|
[@kristinemcbride]: https://github.com/kristinemcbride
|
|
524
|
+
[@lukasedw]: https://github.com/lukasedw
|
|
495
525
|
[@nkriege]: https://github.com/nkriege
|
|
496
526
|
[@NOX73]: https://github.com/NOX73
|
|
497
527
|
[@okuramasafumi]: https://github.com/okuramasafumi
|
|
528
|
+
[@pastpatryk]: https://github.com/pastpatryk
|
|
498
529
|
[@patvice]: https://github.com/patvice
|
|
499
530
|
[@pgiblock]: https://github.com/pgiblock
|
|
500
531
|
[@PedroAugustoRamalhoDuarte]: https://github.com/PedroAugustoRamalhoDuarte
|
|
@@ -502,8 +533,11 @@ and this project adheres to [Semantic Versioning].
|
|
|
502
533
|
[@rdavid1099]: https://github.com/rdavid1099
|
|
503
534
|
[@skryukov]: https://github.com/skryukov
|
|
504
535
|
[@ventsislaf]: https://github.com/ventsislaf
|
|
536
|
+
[@visini]: https://github.com/visini
|
|
505
537
|
|
|
506
|
-
[Unreleased]: https://github.com/skryukov/typelizer/compare/v0.
|
|
538
|
+
[Unreleased]: https://github.com/skryukov/typelizer/compare/v0.14.0...HEAD
|
|
539
|
+
[0.14.0]: https://github.com/skryukov/typelizer/compare/v0.13.1...v0.14.0
|
|
540
|
+
[0.13.1]: https://github.com/skryukov/typelizer/compare/v0.13.0...v0.13.1
|
|
507
541
|
[0.13.0]: https://github.com/skryukov/typelizer/compare/v0.12.0...v0.13.0
|
|
508
542
|
[0.12.0]: https://github.com/skryukov/typelizer/compare/v0.11.0...v0.12.0
|
|
509
543
|
[0.11.0]: https://github.com/skryukov/typelizer/compare/v0.10.0...v0.11.0
|
data/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Typelizer generates TypeScript types, route helpers, and OpenAPI schemas from yo
|
|
|
8
8
|
|
|
9
9
|
- Automatic TypeScript interface generation from serializers
|
|
10
10
|
- Type-safe route helpers from Rails routes
|
|
11
|
-
- Supports Alba, ActiveModel::Serializer, Oj::Serializer, Panko::Serializer
|
|
11
|
+
- Supports Alba, ActiveModel::Serializer, Oj::Serializer, Panko::Serializer, Blueprinter
|
|
12
12
|
- OpenAPI 3.0/3.1 schema generation
|
|
13
13
|
- Multiple output writers with layered configuration
|
|
14
14
|
- File watching with automatic regeneration in development
|
|
@@ -60,7 +60,7 @@ cd spec/app && RAILS_ENV=test bundle exec rails db:create db:migrate && cd ../..
|
|
|
60
60
|
bundle exec rspec
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
The test suite uses a dummy Rails app in `spec/app/` with models, migrations, and serializers for all
|
|
63
|
+
The test suite uses a dummy Rails app in `spec/app/` with models, migrations, and serializers for all five supported frameworks (Alba, AMS, OjSerializers, Panko, Blueprinter). Linting is done with StandardRB:
|
|
64
64
|
|
|
65
65
|
```bash
|
|
66
66
|
bundle exec standardrb
|
|
@@ -72,9 +72,9 @@ bundle exec standardrb
|
|
|
72
72
|
|
|
73
73
|
Typelizer is inspired by [types_from_serializers](https://github.com/ElMassimo/types_from_serializers), [js-routes](https://github.com/railsware/js-routes), and [Wayfinder](https://github.com/nicholasvansanten/wayfinder).
|
|
74
74
|
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
</a>
|
|
75
|
+
<br/>
|
|
76
|
+
|
|
77
|
+
<img src="https://cdn.evilmartians.com/badges/logo-no-label.svg" alt="Evil Martians logo" width="22" height="16" /> <b>Typelizer</b> is built by <b><a href="https://evilmartians.com/">Evil Martians</a></b>, an American design and engineering consultancy for <b>developer tools, AI, and cybersecurity startups</b>.
|
|
78
78
|
|
|
79
79
|
## License
|
|
80
80
|
|
data/lib/typelizer/config.rb
CHANGED
data/lib/typelizer/dsl/hooks.rb
CHANGED
|
@@ -18,6 +18,9 @@ module Typelizer
|
|
|
18
18
|
elsif defined?(::Panko::Serializer) && base.ancestors.include?(::Panko::Serializer)
|
|
19
19
|
require_relative "hooks/panko"
|
|
20
20
|
base.singleton_class.prepend(Panko)
|
|
21
|
+
elsif defined?(::Blueprinter::Base) && base.ancestors.include?(::Blueprinter::Base)
|
|
22
|
+
require_relative "hooks/blueprinter"
|
|
23
|
+
base.singleton_class.prepend(Blueprinter)
|
|
21
24
|
end
|
|
22
25
|
end
|
|
23
26
|
|
data/lib/typelizer/interface.rb
CHANGED
|
@@ -148,7 +148,7 @@ module Typelizer
|
|
|
148
148
|
# Collect enum type names from properties
|
|
149
149
|
enum_imports = all_properties.filter_map(&:enum_type_name)
|
|
150
150
|
|
|
151
|
-
result = (custom_type_imports + serializer_types + trait_imports + enum_imports + Array(parent_interface&.name)).uniq - [self_type_name, name]
|
|
151
|
+
result = (custom_type_imports + serializer_types + trait_imports + enum_imports + Array(parent_interface&.name)).uniq - [self_type_name, name, *trait_interfaces.map(&:name)]
|
|
152
152
|
ImportSorter.sort(result, config.imports_sort_order)
|
|
153
153
|
end
|
|
154
154
|
end
|
|
@@ -190,7 +190,7 @@ module Typelizer
|
|
|
190
190
|
end
|
|
191
191
|
|
|
192
192
|
def self_type_name
|
|
193
|
-
serializer.name
|
|
193
|
+
serializer.name[/(\w+::)?(\w+)(Serializer|Resource|Blueprint)/, 2]
|
|
194
194
|
end
|
|
195
195
|
|
|
196
196
|
def extract_typescript_types(type)
|
|
@@ -10,6 +10,7 @@ module Typelizer
|
|
|
10
10
|
|
|
11
11
|
def infer_types(prop)
|
|
12
12
|
infer_types_for_association(prop) ||
|
|
13
|
+
serializer_resolved_type?(prop) ||
|
|
13
14
|
infer_types_for_column(prop) ||
|
|
14
15
|
infer_types_for_association_ids(prop) ||
|
|
15
16
|
infer_types_for_delegate(prop) ||
|
|
@@ -26,6 +27,7 @@ module Typelizer
|
|
|
26
27
|
end
|
|
27
28
|
|
|
28
29
|
def enum_for(prop)
|
|
30
|
+
return if serializer_resolved_type?(prop)
|
|
29
31
|
return unless model_class&.defined_enums&.key?(prop.column_name.to_s)
|
|
30
32
|
|
|
31
33
|
prop.enum = model_class.defined_enums[prop.column_name.to_s].keys
|
|
@@ -35,6 +37,12 @@ module Typelizer
|
|
|
35
37
|
|
|
36
38
|
private
|
|
37
39
|
|
|
40
|
+
# An association or nested attribute is already typed by the serializer;
|
|
41
|
+
# a same-named column describes a different value.
|
|
42
|
+
def serializer_resolved_type?(prop)
|
|
43
|
+
prop.type.is_a?(Interface) || prop.type.is_a?(Shape)
|
|
44
|
+
end
|
|
45
|
+
|
|
38
46
|
def columns_hash
|
|
39
47
|
return nil unless model_class
|
|
40
48
|
return nil if model_class.abstract_class?
|
|
@@ -55,6 +63,7 @@ module Typelizer
|
|
|
55
63
|
association = model_class&.reflect_on_association(prop.column_name.to_sym)
|
|
56
64
|
return nil unless association
|
|
57
65
|
|
|
66
|
+
prop.multi = association.collection? if prop.multi.nil?
|
|
58
67
|
case association.macro
|
|
59
68
|
when :belongs_to
|
|
60
69
|
foreign_key = association.foreign_key
|
|
@@ -89,10 +98,10 @@ module Typelizer
|
|
|
89
98
|
when :nullable
|
|
90
99
|
prop.nullable = column.null
|
|
91
100
|
when :optional
|
|
92
|
-
prop.optional
|
|
101
|
+
prop.optional ||= column.null
|
|
93
102
|
when :nullable_and_optional
|
|
94
103
|
prop.nullable = column.null
|
|
95
|
-
prop.optional
|
|
104
|
+
prop.optional ||= column.null
|
|
96
105
|
else
|
|
97
106
|
raise "Unknown null strategy: #{config.null_strategy}"
|
|
98
107
|
end
|
data/lib/typelizer/openapi.rb
CHANGED
|
@@ -101,6 +101,8 @@ module Typelizer
|
|
|
101
101
|
def union_member_schema(type)
|
|
102
102
|
if type.respond_to?(:properties)
|
|
103
103
|
{"$ref" => "#/components/schemas/#{type.name}"}
|
|
104
|
+
elsif string_literal?(type)
|
|
105
|
+
{type: :string, enum: [unquote_string_literal(type)]}
|
|
104
106
|
else
|
|
105
107
|
sym = type.to_sym
|
|
106
108
|
if OPENAPI_TYPES.include?(sym)
|
data/lib/typelizer/property.rb
CHANGED
|
@@ -53,7 +53,7 @@ module Typelizer
|
|
|
53
53
|
# Add nullable at the end (null should always be last in sorted output)
|
|
54
54
|
type_str = "#{type_str} | null" if nullable
|
|
55
55
|
|
|
56
|
-
"#{name}#{"?" if optional}: #{type_str}"
|
|
56
|
+
"#{js_key(name.to_s, prefer_double_quotes)}#{"?" if optional}: #{type_str}"
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
def fingerprint
|
|
@@ -95,7 +95,9 @@ module Typelizer
|
|
|
95
95
|
end
|
|
96
96
|
|
|
97
97
|
def quote_string(str, prefer_double_quotes)
|
|
98
|
-
prefer_double_quotes ? "
|
|
98
|
+
quote = prefer_double_quotes ? '"' : "'"
|
|
99
|
+
escaped = str.to_s.gsub(/[\\#{quote}]/) { |char| "\\#{char}" }
|
|
100
|
+
"#{quote}#{escaped}#{quote}"
|
|
99
101
|
end
|
|
100
102
|
|
|
101
103
|
def js_key(str, prefer_double_quotes)
|
|
@@ -33,14 +33,14 @@ module Typelizer
|
|
|
33
33
|
Rails.application.reload_routes_unless_loaded
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
named_paths = build_named_paths(Rails.application.routes.named_routes)
|
|
37
37
|
|
|
38
38
|
routes = Rails.application.routes.routes.flat_map do |route|
|
|
39
39
|
app = route.app.app
|
|
40
40
|
if app.is_a?(Class) && app < Rails::Engine
|
|
41
41
|
collect_engine_routes(route, app) || []
|
|
42
42
|
else
|
|
43
|
-
build_route_info(route,
|
|
43
|
+
build_route_info(route, named_paths)
|
|
44
44
|
end
|
|
45
45
|
end.compact
|
|
46
46
|
|
|
@@ -49,52 +49,57 @@ module Typelizer
|
|
|
49
49
|
.map { |r| [r[:controller], r[:action], r[:path]] }.to_set
|
|
50
50
|
routes.reject! { |r| r[:verb] == "put" && patch_keys.include?([r[:controller], r[:action], r[:path]]) }
|
|
51
51
|
|
|
52
|
+
# Skip unnamed duplicates of a named route (Rails repeats member routes under each parent for `shallow: true`)
|
|
53
|
+
named_keys = routes.select { |r| r[:named] }
|
|
54
|
+
.map { |r| [r[:controller], r[:verb], r[:path]] }.to_set
|
|
55
|
+
routes.reject! { |r| !r[:named] && named_keys.include?([r[:controller], r[:verb], r[:path]]) }
|
|
56
|
+
routes.uniq!
|
|
57
|
+
|
|
52
58
|
if config.include
|
|
53
59
|
patterns = Array(config.include)
|
|
54
|
-
routes = routes.select { |r| patterns.any? { |p|
|
|
60
|
+
routes = routes.select { |r| patterns.any? { |p| match_route?(r, p) } }
|
|
55
61
|
end
|
|
56
62
|
if config.exclude
|
|
57
63
|
patterns = Array(config.exclude)
|
|
58
|
-
routes = routes.reject { |r| patterns.any? { |p|
|
|
64
|
+
routes = routes.reject { |r| patterns.any? { |p| match_route?(r, p) } }
|
|
59
65
|
end
|
|
60
66
|
|
|
61
67
|
routes
|
|
62
68
|
end
|
|
63
69
|
|
|
64
|
-
def
|
|
65
|
-
|
|
66
|
-
|
|
70
|
+
def match_route?(route_info, pattern)
|
|
71
|
+
if pattern.respond_to?(:call)
|
|
72
|
+
pattern.call(route_info)
|
|
73
|
+
else
|
|
74
|
+
route_info[:path].match?(pattern)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
67
77
|
|
|
68
|
-
|
|
78
|
+
def build_named_paths(named_routes, path_prefix: "")
|
|
79
|
+
named_routes.each_with_object({}) do |(name, route), paths|
|
|
69
80
|
controller = route.requirements[:controller]
|
|
70
81
|
action = route.requirements[:action]
|
|
71
82
|
next unless controller && action
|
|
72
83
|
|
|
73
|
-
|
|
74
|
-
prefixed_name = "#{name_prefix}#{name}"
|
|
75
|
-
name_by_action[[controller, action]] = prefixed_name
|
|
76
|
-
name_by_path[[controller, path]] = prefixed_name
|
|
84
|
+
paths[[controller, path_prefix + strip_format(route.path.spec.to_s)]] ||= name.to_s
|
|
77
85
|
end
|
|
78
|
-
|
|
79
|
-
[name_by_action, name_by_path]
|
|
80
86
|
end
|
|
81
87
|
|
|
82
88
|
def strip_format(path)
|
|
83
89
|
path.sub(FORMAT_SUFFIX, "")
|
|
84
90
|
end
|
|
85
91
|
|
|
86
|
-
def build_route_info(route,
|
|
92
|
+
def build_route_info(route, named_paths)
|
|
87
93
|
controller = route.requirements[:controller]
|
|
88
94
|
action = route.requirements[:action]
|
|
89
95
|
|
|
90
96
|
path = strip_format(route.path.spec.to_s)
|
|
91
97
|
|
|
92
98
|
if controller.present? && action.present?
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
name
|
|
99
|
+
# Match by [controller, path] so unnamed aliases at distinct paths (e.g. ActiveStorage representations) don't inherit a sibling's name
|
|
100
|
+
path_name = named_paths[[controller, path]]
|
|
101
|
+
name = route.name || (action if path_name)
|
|
96
102
|
elsif route.name.present?
|
|
97
|
-
has_own_name = true
|
|
98
103
|
name = route.name.to_s
|
|
99
104
|
controller = "_routes"
|
|
100
105
|
action = name
|
|
@@ -110,7 +115,8 @@ module Typelizer
|
|
|
110
115
|
|
|
111
116
|
{
|
|
112
117
|
name: name,
|
|
113
|
-
named:
|
|
118
|
+
named: !!route.name,
|
|
119
|
+
path_name: path_name,
|
|
114
120
|
controller: controller,
|
|
115
121
|
action: action,
|
|
116
122
|
verb: verb,
|
|
@@ -125,14 +131,10 @@ module Typelizer
|
|
|
125
131
|
engine_name = mount_route.name
|
|
126
132
|
return unless engine_name
|
|
127
133
|
|
|
128
|
-
|
|
129
|
-
engine.routes.named_routes,
|
|
130
|
-
path_prefix: mount_prefix,
|
|
131
|
-
name_prefix: "#{engine_name}_"
|
|
132
|
-
)
|
|
134
|
+
named_paths = build_named_paths(engine.routes.named_routes, path_prefix: mount_prefix)
|
|
133
135
|
|
|
134
136
|
engine.routes.routes.filter_map do |engine_route|
|
|
135
|
-
info = build_route_info(engine_route,
|
|
137
|
+
info = build_route_info(engine_route, named_paths)
|
|
136
138
|
next unless info
|
|
137
139
|
info[:path] = mount_prefix + info[:path]
|
|
138
140
|
info
|
|
@@ -145,8 +145,12 @@ module Typelizer
|
|
|
145
145
|
end
|
|
146
146
|
|
|
147
147
|
def route_key(route, controller_routes)
|
|
148
|
-
|
|
149
|
-
camelize_key(
|
|
148
|
+
same_action = controller_routes.select { |r| r[:action] == route[:action] }
|
|
149
|
+
return camelize_key(route[:action]) if same_action.one?
|
|
150
|
+
return camelize_key(route[:name]) if route[:named]
|
|
151
|
+
return camelize_key(route[:action]) if same_action.one? { |r| !r[:named] }
|
|
152
|
+
|
|
153
|
+
camelize_key("#{route[:action]}_#{route[:path_name]}")
|
|
150
154
|
end
|
|
151
155
|
|
|
152
156
|
def camelize_key(key)
|
|
@@ -15,6 +15,8 @@ module Typelizer
|
|
|
15
15
|
AMS
|
|
16
16
|
elsif defined?(::Panko::Serializer) && serializer.ancestors.include?(::Panko::Serializer)
|
|
17
17
|
Panko
|
|
18
|
+
elsif defined?(::Blueprinter::Base) && serializer.ancestors.include?(::Blueprinter::Base)
|
|
19
|
+
Blueprinter
|
|
18
20
|
else
|
|
19
21
|
raise "Can't guess serializer plugin for #{serializer}. " \
|
|
20
22
|
"Please specify it with `config.serializer_plugin`."
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
require_relative "base"
|
|
2
|
+
require_relative "trait_interface"
|
|
3
|
+
|
|
4
|
+
module Typelizer
|
|
5
|
+
module SerializerPlugins
|
|
6
|
+
class Blueprinter < Base
|
|
7
|
+
CONDITIONAL_OPTIONS = %i[if unless exclude_if_nil].freeze
|
|
8
|
+
|
|
9
|
+
def properties
|
|
10
|
+
view_properties(:default)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Each extra view becomes its own full type, since views can also exclude fields
|
|
14
|
+
def trait_interfaces
|
|
15
|
+
@trait_interfaces ||= (serializer.reflections.keys - %i[default identifier]).map do |view|
|
|
16
|
+
TraitInterface.new(serializer: serializer, trait_name: view, context: context, plugin: self, suffix: "View")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def trait_properties(view)
|
|
21
|
+
typelizes = serializer.respond_to?(:_typelizer_attributes) ? serializer._typelizer_attributes : {}
|
|
22
|
+
[view_properties(view), typelizes]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def view_properties(view)
|
|
28
|
+
reflection = serializer.reflections.fetch(view)
|
|
29
|
+
reflection.fields.values.map { |field| field_property(field) } +
|
|
30
|
+
reflection.associations.values.map { |association| association_property(association) }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def field_property(field)
|
|
34
|
+
Property.new(
|
|
35
|
+
name: field.display_name,
|
|
36
|
+
optional: optional?(field.options),
|
|
37
|
+
nullable: false,
|
|
38
|
+
multi: false,
|
|
39
|
+
column_name: field.name
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# `multi` stays nil: Blueprinter renders a list or a single object depending
|
|
44
|
+
# on the value, so the model's association decides
|
|
45
|
+
def association_property(association)
|
|
46
|
+
Property.new(
|
|
47
|
+
name: association.display_name,
|
|
48
|
+
type: association_type(association),
|
|
49
|
+
optional: optional?(association.options),
|
|
50
|
+
nullable: false,
|
|
51
|
+
multi: nil,
|
|
52
|
+
column_name: association.name
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def association_type(association)
|
|
57
|
+
blueprint = association.blueprint
|
|
58
|
+
return unless blueprint.is_a?(Class)
|
|
59
|
+
|
|
60
|
+
interface = context.interface_for(blueprint)
|
|
61
|
+
return interface if association.view == :default
|
|
62
|
+
|
|
63
|
+
interface.trait_interfaces.find { |view| view.trait_name == association.view }&.name
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def optional?(options)
|
|
67
|
+
CONDITIONAL_OPTIONS.any? { |key| options.key?(key) }
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "
|
|
3
|
+
require_relative "../type_inference"
|
|
4
4
|
|
|
5
5
|
module Typelizer
|
|
6
6
|
module SerializerPlugins
|
|
7
|
-
class
|
|
7
|
+
class TraitInterface
|
|
8
8
|
include TypeInference
|
|
9
9
|
|
|
10
10
|
attr_reader :serializer, :trait_name, :context, :plugin
|
|
11
11
|
|
|
12
|
-
def initialize(serializer:, trait_name:, context:, plugin:)
|
|
12
|
+
def initialize(serializer:, trait_name:, context:, plugin:, suffix: "Trait")
|
|
13
13
|
@serializer = serializer
|
|
14
14
|
@trait_name = trait_name
|
|
15
15
|
@context = context
|
|
16
16
|
@plugin = plugin
|
|
17
|
+
@suffix = suffix
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
def config
|
|
@@ -22,7 +23,7 @@ module Typelizer
|
|
|
22
23
|
|
|
23
24
|
def name
|
|
24
25
|
base_name = config.serializer_name_mapper.call(serializer).tr_s(":", "")
|
|
25
|
-
"#{base_name}#{trait_name.to_s.camelize}
|
|
26
|
+
"#{base_name}#{trait_name.to_s.camelize}#{@suffix}"
|
|
26
27
|
end
|
|
27
28
|
|
|
28
29
|
def properties
|
data/lib/typelizer/version.rb
CHANGED
data/lib/typelizer.rb
CHANGED
|
@@ -29,6 +29,7 @@ require_relative "typelizer/serializer_plugins/oj_serializers"
|
|
|
29
29
|
require_relative "typelizer/serializer_plugins/alba"
|
|
30
30
|
require_relative "typelizer/serializer_plugins/ams"
|
|
31
31
|
require_relative "typelizer/serializer_plugins/panko"
|
|
32
|
+
require_relative "typelizer/serializer_plugins/blueprinter"
|
|
32
33
|
|
|
33
34
|
require_relative "typelizer/model_plugins/active_record"
|
|
34
35
|
require_relative "typelizer/model_plugins/poro"
|
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.
|
|
4
|
+
version: 0.14.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Svyatoslav Kryukov
|
|
@@ -56,6 +56,7 @@ files:
|
|
|
56
56
|
- lib/typelizer/dsl/hooks.rb
|
|
57
57
|
- lib/typelizer/dsl/hooks/alba.rb
|
|
58
58
|
- lib/typelizer/dsl/hooks/ams.rb
|
|
59
|
+
- lib/typelizer/dsl/hooks/blueprinter.rb
|
|
59
60
|
- lib/typelizer/dsl/hooks/oj_serializers.rb
|
|
60
61
|
- lib/typelizer/dsl/hooks/panko.rb
|
|
61
62
|
- lib/typelizer/generator.rb
|
|
@@ -77,12 +78,13 @@ files:
|
|
|
77
78
|
- lib/typelizer/serializer_config_layer.rb
|
|
78
79
|
- lib/typelizer/serializer_plugins/alba.rb
|
|
79
80
|
- lib/typelizer/serializer_plugins/alba/block_attribute_collector.rb
|
|
80
|
-
- lib/typelizer/serializer_plugins/alba/trait_interface.rb
|
|
81
81
|
- lib/typelizer/serializer_plugins/ams.rb
|
|
82
82
|
- lib/typelizer/serializer_plugins/auto.rb
|
|
83
83
|
- lib/typelizer/serializer_plugins/base.rb
|
|
84
|
+
- lib/typelizer/serializer_plugins/blueprinter.rb
|
|
84
85
|
- lib/typelizer/serializer_plugins/oj_serializers.rb
|
|
85
86
|
- lib/typelizer/serializer_plugins/panko.rb
|
|
87
|
+
- lib/typelizer/serializer_plugins/trait_interface.rb
|
|
86
88
|
- lib/typelizer/shape.rb
|
|
87
89
|
- lib/typelizer/templates/comment.ts.erb
|
|
88
90
|
- lib/typelizer/templates/enums.ts.erb
|