typelizer 0.8.0 → 0.9.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 +30 -2
- data/README.md +51 -0
- data/lib/tasks/generate.rake +2 -0
- data/lib/typelizer/delegate_tracker.rb +1 -1
- data/lib/typelizer/dsl.rb +5 -5
- data/lib/typelizer/generator.rb +1 -1
- data/lib/typelizer/interface.rb +43 -37
- data/lib/typelizer/openapi.rb +162 -72
- data/lib/typelizer/property.rb +22 -4
- data/lib/typelizer/serializer_plugins/alba/{trait_attribute_collector.rb → block_attribute_collector.rb} +16 -5
- data/lib/typelizer/serializer_plugins/alba/trait_interface.rb +8 -22
- data/lib/typelizer/serializer_plugins/alba.rb +35 -8
- data/lib/typelizer/type_inference.rb +47 -0
- data/lib/typelizer/type_parser.rb +14 -0
- data/lib/typelizer/version.rb +1 -1
- data/lib/typelizer.rb +12 -2
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d8a6cb6187a0670a589346d969c61a17ae0b9fd60a4e6926c74c2e72c9594130
|
|
4
|
+
data.tar.gz: 4ef1a1e6e728df5ab1d5198a0254cfa68e5e477da57fb9c3c6b7ae2f7efa945d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 56296749f1d5478e98457afac0d2e8599e360d5b7864d596772c825e9970471de8b1d0103ada0120b9adeddab2805e65976c496ce0963785dfd39ddc3dffae91
|
|
7
|
+
data.tar.gz: 25f8667ef0f54560e1c84e8d906b50abc55e01ea761dacff012657bc7d94f7127263166b81c4e317350ee89219f92a070aa042004f42cfad3a6482fe31a0c86f
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning].
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.9.0] - 2026-02-26
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Alba: nested attributes (`nested` / `nested_attribute`) now generate inline nested TypeScript types with full type inference support, including within traits. ([@pgiblock])
|
|
15
|
+
|
|
16
|
+
- OpenAPI: support for traits in schema generation. ([@skryukov])
|
|
17
|
+
|
|
18
|
+
- Union types in `typelize` for polymorphic associations. Supports serializer class references, pipe-delimited strings, and plain TypeScript type names. ([@skryukov])
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
typelize commentable: [UserResource, CommentResource]
|
|
22
|
+
typelize approver: "AuthorResource | null"
|
|
23
|
+
typelize content: "TextBlock | ImageBlock"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- OpenAPI: TypeScript-only types (`any`, `unknown`, `never`) and generic types (`Record<string, unknown>`, `Partial<T>`, etc.) no longer produce invalid `$ref` entries. They are mapped to `{type: :object}` instead. ([@skryukov])
|
|
29
|
+
- OpenAPI: fix nullable arrays producing incorrect schemas. ([@skryukov])
|
|
30
|
+
- Fix Typelizer not loading gracefully when required gems are missing at boot time. ([@skryukov])
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- **Internal:** Union types are now stored as arrays of symbols instead of pipe-delimited strings. This fixes import resolution for serializer classes inside unions and eliminates redundant string splitting/joining across the DSL, Interface, and OpenAPI layers. ([@skryukov])
|
|
35
|
+
|
|
10
36
|
## [0.8.0] - 2026-02-19
|
|
11
37
|
|
|
12
38
|
### Added
|
|
@@ -381,12 +407,14 @@ and this project adheres to [Semantic Versioning].
|
|
|
381
407
|
[@NOX73]: https://github.com/NOX73
|
|
382
408
|
[@okuramasafumi]: https://github.com/okuramasafumi
|
|
383
409
|
[@patvice]: https://github.com/patvice
|
|
410
|
+
[@pgiblock]: https://github.com/pgiblock
|
|
411
|
+
[@prog-supdex]: https://github.com/prog-supdex
|
|
384
412
|
[@PedroAugustoRamalhoDuarte]: https://github.com/PedroAugustoRamalhoDuarte
|
|
385
413
|
[@skryukov]: https://github.com/skryukov
|
|
386
|
-
[@prog-supdex]: https://github.com/prog-supdex
|
|
387
414
|
[@ventsislaf]: https://github.com/ventsislaf
|
|
388
415
|
|
|
389
|
-
[Unreleased]: https://github.com/skryukov/typelizer/compare/v0.
|
|
416
|
+
[Unreleased]: https://github.com/skryukov/typelizer/compare/v0.9.0...HEAD
|
|
417
|
+
[0.9.0]: https://github.com/skryukov/typelizer/compare/v0.8.0...v0.9.0
|
|
390
418
|
[0.8.0]: https://github.com/skryukov/typelizer/compare/v0.7.0...v0.8.0
|
|
391
419
|
[0.7.0]: https://github.com/skryukov/typelizer/compare/v0.6.0...v0.7.0
|
|
392
420
|
[0.6.0]: https://github.com/skryukov/typelizer/compare/v0.5.6...v0.6.0
|
data/README.md
CHANGED
|
@@ -149,6 +149,57 @@ class PostResource < ApplicationResource
|
|
|
149
149
|
end
|
|
150
150
|
```
|
|
151
151
|
|
|
152
|
+
Union types are supported for polymorphic associations. You can use serializer class references, which resolve to their generated type names:
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
class PostResource < ApplicationResource
|
|
156
|
+
attributes :id, :title
|
|
157
|
+
|
|
158
|
+
# Union of two serializers — resolves to generated type names
|
|
159
|
+
typelize commentable: [UserResource, CommentResource]
|
|
160
|
+
attribute :commentable
|
|
161
|
+
|
|
162
|
+
# Nullable union — extracts null and marks as nullable
|
|
163
|
+
typelize approver: "AuthorResource | null"
|
|
164
|
+
attribute :approver
|
|
165
|
+
|
|
166
|
+
# Pipe-delimited string with serializer names
|
|
167
|
+
typelize target: "UserResource | CommentResource"
|
|
168
|
+
attribute :target
|
|
169
|
+
|
|
170
|
+
# String and class constant can be mixed
|
|
171
|
+
typelize item: ["Namespace::UserResource", CommentResource]
|
|
172
|
+
attribute :item
|
|
173
|
+
end
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
You can also use plain TypeScript type names for custom types that aren't backed by serializers:
|
|
177
|
+
|
|
178
|
+
```ruby
|
|
179
|
+
class PostResource < ApplicationResource
|
|
180
|
+
attributes :id, :title
|
|
181
|
+
|
|
182
|
+
# Plain type names — passed through as-is to TypeScript
|
|
183
|
+
typelize content: "TextBlock | ImageBlock"
|
|
184
|
+
attribute :content
|
|
185
|
+
|
|
186
|
+
# Works with arrays too
|
|
187
|
+
typelize sections: ["TextBlock", "ImageBlock"]
|
|
188
|
+
attribute :sections
|
|
189
|
+
end
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
This generates:
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
type Post = {
|
|
196
|
+
id: number;
|
|
197
|
+
title: string;
|
|
198
|
+
content: TextBlock | ImageBlock;
|
|
199
|
+
sections: TextBlock | ImageBlock;
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
152
203
|
For more complex type definitions, use the full API:
|
|
153
204
|
|
|
154
205
|
```ruby
|
data/lib/tasks/generate.rake
CHANGED
|
@@ -24,6 +24,8 @@ namespace :typelizer do
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
interfaces = Typelizer.interfaces
|
|
27
|
+
raise ArgumentError, "No serializers found. Please ensure all your serializers include Typelizer::DSL." if interfaces.empty?
|
|
28
|
+
|
|
27
29
|
puts "Finished in #{time} seconds"
|
|
28
30
|
puts "Found #{interfaces.size} serializers:"
|
|
29
31
|
puts interfaces.map { |i| "\t#{i.name}" }.join("\n")
|
|
@@ -15,7 +15,7 @@ module Typelizer
|
|
|
15
15
|
module Hook
|
|
16
16
|
def delegate(*methods, to:, allow_nil: nil, prefix: nil, **)
|
|
17
17
|
super.tap do
|
|
18
|
-
next unless is_a?(Class) && defined?(ActiveRecord::Base) && self < ActiveRecord::Base
|
|
18
|
+
next unless is_a?(Class) && defined?(ActiveRecord::Base) && !ActiveRecord.autoload?(:Base) && self < ActiveRecord::Base
|
|
19
19
|
|
|
20
20
|
method_prefix = if prefix == true
|
|
21
21
|
"#{to}_"
|
data/lib/typelizer/dsl.rb
CHANGED
|
@@ -104,16 +104,16 @@ module Typelizer
|
|
|
104
104
|
options = attrs.last.is_a?(Hash) ? attrs.pop : {}
|
|
105
105
|
|
|
106
106
|
if attrs.any?
|
|
107
|
-
# Parse type shortcuts and merge options
|
|
108
107
|
parsed_types = attrs.map { |t| TypeParser.parse(t) }
|
|
109
|
-
|
|
110
|
-
options[:type] = type_names.join(" | ")
|
|
111
|
-
|
|
112
|
-
# Merge modifier flags from all parsed types
|
|
108
|
+
all_types = parsed_types.flat_map { |p| Array(p[:type]) }
|
|
113
109
|
parsed_types.each do |parsed|
|
|
114
110
|
options[:optional] = true if parsed[:optional]
|
|
115
111
|
options[:multi] = true if parsed[:multi]
|
|
112
|
+
options[:nullable] = true if parsed[:nullable]
|
|
116
113
|
end
|
|
114
|
+
options[:nullable] = true if all_types.delete(:null)
|
|
115
|
+
# Unwrap single-element arrays: typelize field: ["string"] behaves like typelize field: "string"
|
|
116
|
+
options[:type] = (all_types.size == 1) ? all_types.first : all_types
|
|
117
117
|
end
|
|
118
118
|
|
|
119
119
|
instance_variable_get(instance_variable)[name.to_sym] ||= {}
|
data/lib/typelizer/generator.rb
CHANGED
|
@@ -11,7 +11,7 @@ module Typelizer
|
|
|
11
11
|
|
|
12
12
|
Typelizer.configuration.writers.each do |writer_name, writer_config|
|
|
13
13
|
interfaces = Typelizer.interfaces(writer_name: writer_name)
|
|
14
|
-
|
|
14
|
+
next if interfaces.empty?
|
|
15
15
|
|
|
16
16
|
Writer.new(writer_config).call(interfaces, force: force)
|
|
17
17
|
end
|
data/lib/typelizer/interface.rb
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
require_relative "type_inference"
|
|
2
|
+
|
|
1
3
|
module Typelizer
|
|
2
4
|
class Interface
|
|
5
|
+
include TypeInference
|
|
6
|
+
|
|
3
7
|
attr_reader :serializer, :context
|
|
4
8
|
|
|
5
9
|
def initialize(serializer:, context:)
|
|
@@ -60,7 +64,7 @@ module Typelizer
|
|
|
60
64
|
|
|
61
65
|
def enum_types
|
|
62
66
|
@enum_types ||= begin
|
|
63
|
-
all_properties = properties + trait_interfaces.flat_map(&:properties)
|
|
67
|
+
all_properties = collect_all_properties(properties + trait_interfaces.flat_map(&:properties))
|
|
64
68
|
all_properties
|
|
65
69
|
.select(&:enum_definition)
|
|
66
70
|
.uniq(&:enum_type_name)
|
|
@@ -104,12 +108,12 @@ module Typelizer
|
|
|
104
108
|
|
|
105
109
|
def imports
|
|
106
110
|
@imports ||= begin
|
|
107
|
-
# Include both main properties and trait properties for import collection
|
|
108
|
-
|
|
111
|
+
# Include both main properties and trait properties for import collection,
|
|
112
|
+
# recursively including nested sub-properties
|
|
113
|
+
all_properties = collect_all_properties(properties_to_print + trait_interfaces.flat_map(&:properties))
|
|
109
114
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
.partition { |type| type.is_a?(Interface) }
|
|
115
|
+
flat_types = all_properties.filter_map(&:type).flat_map { |t| Array(t) }.uniq
|
|
116
|
+
association_serializers, attribute_types = flat_types.partition { |type| type.is_a?(Interface) }
|
|
113
117
|
|
|
114
118
|
serializer_types = association_serializers
|
|
115
119
|
.filter_map { |interface| interface.name if interface.name != name && !interface.inline? }
|
|
@@ -158,6 +162,16 @@ module Typelizer
|
|
|
158
162
|
|
|
159
163
|
private
|
|
160
164
|
|
|
165
|
+
def collect_all_properties(props)
|
|
166
|
+
props.flat_map do |prop|
|
|
167
|
+
if prop.nested_properties&.any?
|
|
168
|
+
[prop] + collect_all_properties(prop.nested_properties)
|
|
169
|
+
else
|
|
170
|
+
[prop]
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
161
175
|
def self_type_name
|
|
162
176
|
serializer.name.match(/(\w+::)?(\w+)(Serializer|Resource)/)[2]
|
|
163
177
|
end
|
|
@@ -182,6 +196,7 @@ module Typelizer
|
|
|
182
196
|
.then { |p| has_dsl ? p : apply_model_inference(p) }
|
|
183
197
|
.then { |p| apply_multi_flag(p, multi_attrs) }
|
|
184
198
|
.then { |p| apply_metadata(p) }
|
|
199
|
+
.then { |p| infer_nested_property_types(p) }
|
|
185
200
|
end
|
|
186
201
|
end
|
|
187
202
|
|
|
@@ -199,47 +214,38 @@ module Typelizer
|
|
|
199
214
|
|
|
200
215
|
def resolve_class_type(attrs)
|
|
201
216
|
type = attrs[:type]
|
|
202
|
-
return attrs unless type.is_a?(String) || type.is_a?(Symbol)
|
|
203
|
-
|
|
204
|
-
klass = Object.const_get(type.to_s)
|
|
205
|
-
return attrs unless klass.respond_to?(:typelizer_config)
|
|
206
217
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
218
|
+
case type
|
|
219
|
+
when Array
|
|
220
|
+
resolve_union_class_types(attrs)
|
|
221
|
+
when String, Symbol
|
|
222
|
+
resolve_single_class_type(attrs)
|
|
223
|
+
else
|
|
224
|
+
attrs
|
|
225
|
+
end
|
|
210
226
|
end
|
|
211
227
|
|
|
212
|
-
def
|
|
213
|
-
|
|
228
|
+
def resolve_single_class_type(attrs)
|
|
229
|
+
attrs.merge(type: resolve_type_part(attrs[:type]))
|
|
214
230
|
end
|
|
215
231
|
|
|
216
|
-
def
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
232
|
+
def resolve_union_class_types(attrs)
|
|
233
|
+
resolved = attrs[:type].map { |part| resolve_type_part(part) }
|
|
234
|
+
# Unwrap single-element arrays (e.g., after null extraction from ["Serializer", null])
|
|
235
|
+
attrs.merge(type: (resolved.size == 1) ? resolved.first : resolved)
|
|
220
236
|
end
|
|
221
237
|
|
|
222
|
-
def
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
238
|
+
def resolve_type_part(part)
|
|
239
|
+
klass = Object.const_get(part.to_s)
|
|
240
|
+
klass.respond_to?(:typelizer_config) ? context.interface_for(klass) : part
|
|
241
|
+
rescue NameError
|
|
242
|
+
part
|
|
227
243
|
end
|
|
228
244
|
|
|
229
|
-
def
|
|
230
|
-
return
|
|
231
|
-
|
|
232
|
-
# Execute the `serializer_model_mapper` lambda in the context of the `config` object
|
|
233
|
-
# This giving a possibility to access other lambdas, for example, `serializer_name_mapper`
|
|
234
|
-
config.instance_exec(serializer, &config.serializer_model_mapper)
|
|
235
|
-
rescue NameError => e
|
|
236
|
-
Typelizer.logger.debug("model_mapper failed for serializer #{serializer.name}: #{e.class}: #{e.message}")
|
|
237
|
-
|
|
238
|
-
nil
|
|
239
|
-
end
|
|
245
|
+
def apply_multi_flag(prop, multi_attrs)
|
|
246
|
+
return prop unless multi_attrs.include?(prop.column_name.to_sym)
|
|
240
247
|
|
|
241
|
-
|
|
242
|
-
@model_plugin ||= config.model_plugin.new(model_class: model_class, config: config)
|
|
248
|
+
prop.with(multi: true)
|
|
243
249
|
end
|
|
244
250
|
end
|
|
245
251
|
end
|
data/lib/typelizer/openapi.rb
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Typelizer
|
|
4
|
-
|
|
4
|
+
module OpenAPI
|
|
5
5
|
SUPPORTED_VERSIONS = ["3.0", "3.1"].freeze
|
|
6
|
+
|
|
6
7
|
OPENAPI_TYPES = %i[integer number string boolean object array null].freeze
|
|
8
|
+
TS_OBJECT_TYPES = %w[any unknown never Record Partial Pick Omit].freeze
|
|
7
9
|
|
|
8
10
|
COLUMN_TYPE_MAP = {
|
|
9
11
|
integer: {type: :integer},
|
|
@@ -25,100 +27,188 @@ module Typelizer
|
|
|
25
27
|
cidr: {type: :string}
|
|
26
28
|
}.freeze
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
class << self
|
|
31
|
+
def schema_for(interface, openapi_version: "3.0")
|
|
32
|
+
validate_version!(openapi_version)
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
required_props = interface.properties.reject(&:optional).map(&:name)
|
|
35
|
+
schema = {
|
|
36
|
+
type: :object,
|
|
37
|
+
properties: interface.properties.to_h { |prop| [prop.name, property_schema(prop, openapi_version: openapi_version)] }
|
|
38
|
+
}
|
|
39
|
+
schema[:required] = required_props if required_props.any?
|
|
40
|
+
schema
|
|
41
|
+
end
|
|
39
42
|
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
def property_schema(property, openapi_version: "3.0")
|
|
44
|
+
if property.type.is_a?(Array)
|
|
45
|
+
return union_schema(property, openapi_version: openapi_version)
|
|
46
|
+
end
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
|
|
48
|
+
definition = base_type(property, openapi_version: openapi_version)
|
|
49
|
+
ref = definition.delete("$ref")
|
|
50
|
+
|
|
51
|
+
definition = if ref
|
|
52
|
+
ref_schema(ref, property, openapi_version: openapi_version)
|
|
53
|
+
else
|
|
54
|
+
inline_schema(definition, property, openapi_version: openapi_version)
|
|
55
|
+
end
|
|
45
56
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
else
|
|
49
|
-
inline_schema(definition, property, openapi_version: openapi_version)
|
|
57
|
+
definition = wrap_traits(definition, property, openapi_version: openapi_version)
|
|
58
|
+
wrap_multi(definition, property, openapi_version: openapi_version)
|
|
50
59
|
end
|
|
51
60
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def ref_schema(ref, property, openapi_version:)
|
|
64
|
+
ref_obj = {"$ref" => ref}
|
|
65
|
+
item_nullable = !property.multi && property.nullable
|
|
66
|
+
|
|
67
|
+
if v31?(openapi_version)
|
|
68
|
+
definition = item_nullable ? {oneOf: [ref_obj, {type: :null}]} : ref_obj
|
|
69
|
+
else
|
|
70
|
+
needs_wrapper = item_nullable || (!property.multi && (property.comment.is_a?(String) || property.deprecated))
|
|
71
|
+
definition = needs_wrapper ? {allOf: [ref_obj]} : ref_obj
|
|
72
|
+
definition[:nullable] = true if item_nullable
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
apply_metadata(definition, property) unless property.multi
|
|
76
|
+
definition
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def inline_schema(definition, property, openapi_version:)
|
|
80
|
+
unless property.multi
|
|
81
|
+
apply_nullable(definition, property, openapi_version: openapi_version)
|
|
82
|
+
apply_metadata(definition, property)
|
|
83
|
+
end
|
|
84
|
+
if property.enum.is_a?(Array)
|
|
85
|
+
items_nullable = !property.multi && property.nullable
|
|
86
|
+
definition[:enum] = (items_nullable && !property.enum.include?(nil)) ? property.enum + [nil] : property.enum
|
|
87
|
+
end
|
|
88
|
+
definition
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def union_schema(property, openapi_version:)
|
|
92
|
+
schemas = property.type.map { |part| union_member_schema(part) }
|
|
93
|
+
|
|
94
|
+
definition = {anyOf: schemas}
|
|
95
|
+
|
|
96
|
+
unless property.multi
|
|
97
|
+
apply_nullable(definition, property, openapi_version: openapi_version)
|
|
98
|
+
apply_metadata(definition, property)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
wrap_multi(definition, property, openapi_version: openapi_version)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def union_member_schema(type)
|
|
105
|
+
if type.respond_to?(:properties)
|
|
106
|
+
{"$ref" => "#/components/schemas/#{type.name}"}
|
|
107
|
+
else
|
|
108
|
+
sym = type.to_sym
|
|
109
|
+
if OPENAPI_TYPES.include?(sym)
|
|
110
|
+
{type: sym}
|
|
111
|
+
elsif ts_only_type?(type.to_s)
|
|
112
|
+
{type: :object}
|
|
57
113
|
else
|
|
58
|
-
|
|
114
|
+
{"$ref" => "#/components/schemas/#{type}"}
|
|
59
115
|
end
|
|
60
116
|
end
|
|
61
117
|
end
|
|
62
118
|
|
|
63
|
-
definition
|
|
64
|
-
|
|
119
|
+
def wrap_traits(definition, property, openapi_version:)
|
|
120
|
+
return definition unless property.respond_to?(:with_traits) && property.with_traits&.any? && property.type.respond_to?(:name)
|
|
65
121
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
122
|
+
trait_refs = property.with_traits.map do |t|
|
|
123
|
+
{"$ref" => "#/components/schemas/#{property.type.name}#{t.to_s.camelize}Trait"}
|
|
124
|
+
end
|
|
69
125
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
126
|
+
base_ref = definition.delete("$ref")
|
|
127
|
+
if base_ref
|
|
128
|
+
definition = {allOf: [{"$ref" => base_ref}] + trait_refs}
|
|
129
|
+
elsif definition[:oneOf]
|
|
130
|
+
non_null = definition[:oneOf].reject { |s| s[:type] == :null }
|
|
131
|
+
null_schemas = definition[:oneOf].select { |s| s[:type] == :null }
|
|
132
|
+
all_of = non_null + trait_refs
|
|
133
|
+
definition = null_schemas.any? ? {oneOf: [{allOf: all_of}, *null_schemas]} : {allOf: all_of}
|
|
134
|
+
elsif definition[:allOf]
|
|
135
|
+
definition[:allOf].concat(trait_refs)
|
|
136
|
+
else
|
|
137
|
+
raise ArgumentError, "Unexpected schema shape for traits on property #{property.name}: #{definition.inspect}"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
definition[:nullable] = true if !v31?(openapi_version) && property.nullable
|
|
141
|
+
definition
|
|
76
142
|
end
|
|
77
143
|
|
|
78
|
-
definition
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
144
|
+
def apply_metadata(definition, property)
|
|
145
|
+
definition[:description] = property.comment if property.comment.is_a?(String)
|
|
146
|
+
definition[:deprecated] = true if property.deprecated
|
|
147
|
+
end
|
|
82
148
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
149
|
+
def apply_nullable(definition, property, openapi_version:)
|
|
150
|
+
return unless property.nullable
|
|
151
|
+
|
|
152
|
+
if definition[:anyOf]
|
|
153
|
+
v31?(openapi_version) ? definition[:anyOf] << {type: :null} : definition[:nullable] = true
|
|
154
|
+
elsif definition[:type]
|
|
155
|
+
v31?(openapi_version) ? definition[:type] = [definition[:type], :null] : definition[:nullable] = true
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def wrap_multi(definition, property, openapi_version:)
|
|
160
|
+
return definition unless property.multi
|
|
161
|
+
|
|
162
|
+
definition = {type: :array, items: definition}
|
|
163
|
+
apply_metadata(definition, property)
|
|
86
164
|
if property.nullable
|
|
87
|
-
|
|
88
|
-
|
|
165
|
+
v31?(openapi_version) ? definition[:type] = [:array, :null] : definition[:nullable] = true
|
|
166
|
+
end
|
|
167
|
+
definition
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def base_type(property, openapi_version:)
|
|
171
|
+
if property.type.respond_to?(:properties)
|
|
172
|
+
if property.type.respond_to?(:inline?) && property.type.inline?
|
|
173
|
+
schema_for(property.type, openapi_version: openapi_version)
|
|
89
174
|
else
|
|
90
|
-
|
|
175
|
+
{"$ref" => "#/components/schemas/#{property.type.name}"}
|
|
91
176
|
end
|
|
177
|
+
elsif property.type.nil? && property.respond_to?(:nested_properties) && property.nested_properties&.any?
|
|
178
|
+
nested_schema(property, openapi_version: openapi_version)
|
|
179
|
+
elsif property.column_type && COLUMN_TYPE_MAP.key?(property.column_type)
|
|
180
|
+
result = COLUMN_TYPE_MAP[property.column_type].dup
|
|
181
|
+
result[:type] = :string if property.enum
|
|
182
|
+
result
|
|
183
|
+
elsif (property.type.is_a?(String) || property.type.is_a?(Symbol)) && !OPENAPI_TYPES.include?(property.type.to_sym) && !ts_only_type?(property.type.to_s)
|
|
184
|
+
{"$ref" => "#/components/schemas/#{property.type}"}
|
|
185
|
+
else
|
|
186
|
+
type = property.type.to_s.to_sym
|
|
187
|
+
OPENAPI_TYPES.include?(type) ? {type: type} : {type: :object}
|
|
92
188
|
end
|
|
93
189
|
end
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
190
|
+
|
|
191
|
+
def nested_schema(property, openapi_version:)
|
|
192
|
+
required = property.nested_properties.reject(&:optional).map(&:name)
|
|
193
|
+
schema = {
|
|
194
|
+
type: :object,
|
|
195
|
+
properties: property.nested_properties.to_h { |p| [p.name, property_schema(p, openapi_version: openapi_version)] }
|
|
196
|
+
}
|
|
197
|
+
schema[:required] = required if required.any?
|
|
198
|
+
schema
|
|
98
199
|
end
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
result
|
|
111
|
-
elsif property.type.is_a?(String) && !OPENAPI_TYPES.include?(property.type.to_sym)
|
|
112
|
-
{"$ref" => "#/components/schemas/#{property.type}"}
|
|
113
|
-
else
|
|
114
|
-
type = property.type.to_s.to_sym
|
|
115
|
-
if OPENAPI_TYPES.include?(type)
|
|
116
|
-
{type: type}
|
|
117
|
-
else
|
|
118
|
-
{type: :object}
|
|
119
|
-
end
|
|
200
|
+
|
|
201
|
+
def v31?(openapi_version)
|
|
202
|
+
openapi_version.to_s == "3.1"
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def ts_only_type?(type_str)
|
|
206
|
+
type_str.start_with?("{") || type_str.include?("<") || TS_OBJECT_TYPES.include?(type_str)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def validate_version!(openapi_version)
|
|
210
|
+
raise ArgumentError, "Unsupported openapi_version: #{openapi_version}. Must be one of: #{SUPPORTED_VERSIONS.join(", ")}" unless SUPPORTED_VERSIONS.include?(openapi_version.to_s)
|
|
120
211
|
end
|
|
121
212
|
end
|
|
122
|
-
private_class_method :base_type
|
|
123
213
|
end
|
|
124
214
|
end
|
data/lib/typelizer/property.rb
CHANGED
|
@@ -2,7 +2,7 @@ module Typelizer
|
|
|
2
2
|
Property = Struct.new(
|
|
3
3
|
:name, :type, :optional, :nullable,
|
|
4
4
|
:multi, :column_name, :column_type, :comment, :enum, :enum_type_name, :deprecated,
|
|
5
|
-
:with_traits,
|
|
5
|
+
:with_traits, :nested_properties, :nested_typelizes,
|
|
6
6
|
keyword_init: true
|
|
7
7
|
) do
|
|
8
8
|
def with(**attrs)
|
|
@@ -52,8 +52,13 @@ module Typelizer
|
|
|
52
52
|
def fingerprint
|
|
53
53
|
# Use array format for consistent output across Ruby versions
|
|
54
54
|
# (Hash#inspect format changed in Ruby 3.4)
|
|
55
|
-
# Exclude fields that do not affect generated TypeScript output
|
|
56
|
-
to_h
|
|
55
|
+
# Exclude fields that do not affect generated TypeScript output.
|
|
56
|
+
# Exclude nested_properties/nested_typelizes from to_h to avoid changing
|
|
57
|
+
# fingerprints for properties that don't use them.
|
|
58
|
+
# nested_typelizes is excluded entirely as it only affects inference, not output.
|
|
59
|
+
to_h.except(:column_type, :nested_properties, :nested_typelizes)
|
|
60
|
+
.merge(type: UnionTypeSorter.sort(type_name(sort_order: :alphabetical), :alphabetical))
|
|
61
|
+
.then { |h| nested_properties&.any? ? h.merge(nested_properties: nested_properties.map(&:fingerprint)) : h }
|
|
57
62
|
.to_a.inspect
|
|
58
63
|
end
|
|
59
64
|
|
|
@@ -90,7 +95,20 @@ module Typelizer
|
|
|
90
95
|
return enum_values.join(" | ")
|
|
91
96
|
end
|
|
92
97
|
|
|
93
|
-
type.
|
|
98
|
+
if type.nil? && nested_properties&.any?
|
|
99
|
+
inner = nested_properties.map { |p|
|
|
100
|
+
rendered = p.render(sort_order: sort_order, prefer_double_quotes: prefer_double_quotes) + ";"
|
|
101
|
+
rendered.gsub(/^/, " ")
|
|
102
|
+
}.join("\n")
|
|
103
|
+
return "{\n#{inner}\n}"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
case type
|
|
107
|
+
when Array
|
|
108
|
+
type.map { |t| t.respond_to?(:name) ? t.name : t.to_s }.join(" | ")
|
|
109
|
+
else
|
|
110
|
+
type.respond_to?(:name) ? type.name : type&.to_s || "unknown"
|
|
111
|
+
end
|
|
94
112
|
end
|
|
95
113
|
end
|
|
96
114
|
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Typelizer
|
|
4
4
|
module SerializerPlugins
|
|
5
|
-
class Alba::
|
|
5
|
+
class Alba::BlockAttributeCollector
|
|
6
6
|
attr_reader :collected_attributes, :collected_typelizes
|
|
7
7
|
|
|
8
8
|
def initialize
|
|
@@ -53,15 +53,26 @@ module Typelizer
|
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
-
# Simple struct to hold association info from
|
|
57
|
-
|
|
56
|
+
# Simple struct to hold association info from blocks
|
|
57
|
+
BlockAssociation = Struct.new(:name, :resource, :with_traits, :multi, :key, keyword_init: true)
|
|
58
|
+
|
|
59
|
+
# Struct to hold nested attribute info captured within a block
|
|
60
|
+
BlockNestedAttribute = Struct.new(:name, :block, keyword_init: true)
|
|
61
|
+
|
|
62
|
+
def nested_attribute(name, **options, &block)
|
|
63
|
+
raise ArgumentError, "Block is required for nested_attribute" unless block
|
|
64
|
+
|
|
65
|
+
@collected_attributes[name] = BlockNestedAttribute.new(name: name, block: block)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
alias_method :nested, :nested_attribute
|
|
58
69
|
|
|
59
70
|
# Support association methods that might be used in traits
|
|
60
71
|
def one(name, **options, &block)
|
|
61
72
|
resource = options[:resource] || options[:serializer]
|
|
62
73
|
with_traits = options[:with_traits]
|
|
63
74
|
key = options[:key] || name
|
|
64
|
-
@collected_attributes[key] =
|
|
75
|
+
@collected_attributes[key] = BlockAssociation.new(
|
|
65
76
|
name: name,
|
|
66
77
|
resource: resource,
|
|
67
78
|
with_traits: with_traits,
|
|
@@ -77,7 +88,7 @@ module Typelizer
|
|
|
77
88
|
resource = options[:resource] || options[:serializer]
|
|
78
89
|
with_traits = options[:with_traits]
|
|
79
90
|
key = options[:key] || name
|
|
80
|
-
@collected_attributes[key] =
|
|
91
|
+
@collected_attributes[key] = BlockAssociation.new(
|
|
81
92
|
name: name,
|
|
82
93
|
resource: resource,
|
|
83
94
|
with_traits: with_traits,
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../../type_inference"
|
|
4
|
+
|
|
3
5
|
module Typelizer
|
|
4
6
|
module SerializerPlugins
|
|
5
7
|
class Alba::TraitInterface
|
|
8
|
+
include TypeInference
|
|
9
|
+
|
|
6
10
|
attr_reader :serializer, :trait_name, :context, :plugin
|
|
7
11
|
|
|
8
12
|
def initialize(serializer:, trait_name:, context:, plugin:)
|
|
@@ -33,31 +37,13 @@ module Typelizer
|
|
|
33
37
|
|
|
34
38
|
def infer_types(props, typelizes)
|
|
35
39
|
props.map do |prop|
|
|
36
|
-
# First check for typelize DSL in the trait
|
|
37
40
|
dsl_type = typelizes[prop.column_name.to_sym] || typelizes[prop.name.to_sym]
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# Fall back to model plugin for type inference
|
|
46
|
-
model_plugin.infer_types(prop)
|
|
41
|
+
prop
|
|
42
|
+
.then { |p| dsl_type&.any? ? p.with(**dsl_type) : apply_model_inference(p) }
|
|
43
|
+
.then { |p| apply_metadata(p) }
|
|
44
|
+
.then { |p| infer_nested_property_types(p) }
|
|
47
45
|
end
|
|
48
46
|
end
|
|
49
|
-
|
|
50
|
-
def model_class
|
|
51
|
-
return serializer._typelizer_model_name if serializer.respond_to?(:_typelizer_model_name)
|
|
52
|
-
|
|
53
|
-
config.instance_exec(serializer, &config.serializer_model_mapper)
|
|
54
|
-
rescue NameError
|
|
55
|
-
nil
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def model_plugin
|
|
59
|
-
@model_plugin ||= config.model_plugin.new(model_class: model_class, config: config)
|
|
60
|
-
end
|
|
61
47
|
end
|
|
62
48
|
end
|
|
63
49
|
end
|
|
@@ -48,19 +48,19 @@ module Typelizer
|
|
|
48
48
|
return [], {} unless trait_block
|
|
49
49
|
|
|
50
50
|
# Create a collector to capture attributes defined in the trait block
|
|
51
|
-
collector =
|
|
51
|
+
collector = BlockAttributeCollector.new
|
|
52
52
|
collector.instance_exec(&trait_block)
|
|
53
53
|
|
|
54
54
|
props = collector.collected_attributes.map do |name, attr|
|
|
55
|
-
|
|
55
|
+
build_collected_property(name.is_a?(Symbol) ? name.name : name, attr)
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
[props, collector.collected_typelizes]
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
-
def
|
|
61
|
+
def build_collected_property(name, attr)
|
|
62
62
|
case attr
|
|
63
|
-
when
|
|
63
|
+
when BlockAttributeCollector::BlockAssociation
|
|
64
64
|
with_traits = Array(attr.with_traits) if attr.with_traits
|
|
65
65
|
resource = attr.resource || infer_resource_from_name(name)
|
|
66
66
|
|
|
@@ -73,6 +73,19 @@ module Typelizer
|
|
|
73
73
|
column_name: name,
|
|
74
74
|
with_traits: with_traits
|
|
75
75
|
)
|
|
76
|
+
when BlockAttributeCollector::BlockNestedAttribute
|
|
77
|
+
prop_name = has_transform_key?(serializer) ? fetch_key(serializer, name) : name
|
|
78
|
+
nested_props, nested_typelizes = collect_nested_block(attr.block)
|
|
79
|
+
Property.new(
|
|
80
|
+
name: prop_name,
|
|
81
|
+
type: nil,
|
|
82
|
+
optional: false,
|
|
83
|
+
nullable: false,
|
|
84
|
+
multi: false,
|
|
85
|
+
column_name: name,
|
|
86
|
+
nested_properties: nested_props,
|
|
87
|
+
nested_typelizes: nested_typelizes
|
|
88
|
+
)
|
|
76
89
|
else
|
|
77
90
|
build_property(name, attr)
|
|
78
91
|
end
|
|
@@ -95,7 +108,7 @@ module Typelizer
|
|
|
95
108
|
end
|
|
96
109
|
|
|
97
110
|
def trait_interfaces
|
|
98
|
-
traits.map do |trait_name, _|
|
|
111
|
+
@trait_interfaces ||= traits.map do |trait_name, _|
|
|
99
112
|
TraitInterface.new(
|
|
100
113
|
serializer: serializer,
|
|
101
114
|
trait_name: trait_name,
|
|
@@ -164,6 +177,8 @@ module Typelizer
|
|
|
164
177
|
**options
|
|
165
178
|
)
|
|
166
179
|
when ::Alba::NestedAttribute
|
|
180
|
+
block = attr.instance_variable_get(:@block)
|
|
181
|
+
nested_props, nested_typelizes = collect_nested_block(block)
|
|
167
182
|
Property.new(
|
|
168
183
|
name: name,
|
|
169
184
|
type: nil,
|
|
@@ -171,6 +186,8 @@ module Typelizer
|
|
|
171
186
|
nullable: false,
|
|
172
187
|
multi: false,
|
|
173
188
|
column_name: column_name,
|
|
189
|
+
nested_properties: nested_props,
|
|
190
|
+
nested_typelizes: nested_typelizes,
|
|
174
191
|
**options
|
|
175
192
|
)
|
|
176
193
|
when ::Alba::ConditionalAttribute
|
|
@@ -192,14 +209,24 @@ module Typelizer
|
|
|
192
209
|
::Alba.transform_key(key, transform_type: serializer._transform_type)
|
|
193
210
|
end
|
|
194
211
|
|
|
195
|
-
private
|
|
196
|
-
|
|
197
212
|
def ts_mapper
|
|
198
213
|
config.plugin_configs.dig(:alba, :ts_mapper) || ALBA_TS_MAPPER
|
|
199
214
|
end
|
|
215
|
+
|
|
216
|
+
def collect_nested_block(block)
|
|
217
|
+
collector = BlockAttributeCollector.new
|
|
218
|
+
collector.instance_exec(&block)
|
|
219
|
+
|
|
220
|
+
props = collector.collected_attributes.map do |attr_name, attr|
|
|
221
|
+
attr_name_str = attr_name.is_a?(Symbol) ? attr_name.name : attr_name
|
|
222
|
+
build_collected_property(attr_name_str, attr)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
[props, collector.collected_typelizes]
|
|
226
|
+
end
|
|
200
227
|
end
|
|
201
228
|
end
|
|
202
229
|
end
|
|
203
230
|
|
|
204
|
-
require_relative "alba/
|
|
231
|
+
require_relative "alba/block_attribute_collector"
|
|
205
232
|
require_relative "alba/trait_interface"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Typelizer
|
|
4
|
+
module TypeInference
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def apply_model_inference(prop)
|
|
8
|
+
model_plugin.infer_types(prop)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def apply_metadata(prop)
|
|
12
|
+
prop.tap do |p|
|
|
13
|
+
p.comment ||= model_plugin.comment_for(p) if config.comments && p.comment != false
|
|
14
|
+
p.enum ||= model_plugin.enum_for(p) if p.enum != false
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def infer_nested_property_types(prop)
|
|
19
|
+
return prop unless prop.nested_properties&.any?
|
|
20
|
+
|
|
21
|
+
typelizes = prop.nested_typelizes || {}
|
|
22
|
+
inferred = prop.nested_properties.map do |sub_prop|
|
|
23
|
+
dsl_type = typelizes[sub_prop.column_name.to_sym] || typelizes[sub_prop.name.to_sym]
|
|
24
|
+
sub_prop
|
|
25
|
+
.then { |p| dsl_type&.any? ? p.with(**dsl_type) : apply_model_inference(p) }
|
|
26
|
+
.then { |p| apply_metadata(p) }
|
|
27
|
+
.then { |p| infer_nested_property_types(p) }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
prop.with(nested_properties: inferred)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def model_class
|
|
34
|
+
return serializer._typelizer_model_name if serializer.respond_to?(:_typelizer_model_name)
|
|
35
|
+
|
|
36
|
+
config.instance_exec(serializer, &config.serializer_model_mapper)
|
|
37
|
+
rescue NameError => e
|
|
38
|
+
Typelizer.logger.debug("model_mapper failed for serializer #{serializer.name}: #{e.class}: #{e.message}")
|
|
39
|
+
|
|
40
|
+
nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def model_plugin
|
|
44
|
+
@model_plugin ||= config.model_plugin.new(model_class: model_class, config: config)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -14,6 +14,8 @@ module Typelizer
|
|
|
14
14
|
return options if type_def.nil?
|
|
15
15
|
|
|
16
16
|
type_str = type_def.to_s
|
|
17
|
+
return parse_union(type_str, **options) if type_str.include?("|")
|
|
18
|
+
|
|
17
19
|
match = TYPE_PATTERN.match(type_str)
|
|
18
20
|
|
|
19
21
|
return {type: type_def}.merge(options) unless match
|
|
@@ -34,6 +36,18 @@ module Typelizer
|
|
|
34
36
|
type_str = type_def.to_s
|
|
35
37
|
type_str.end_with?("?", "[]")
|
|
36
38
|
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def parse_union(type_str, **options)
|
|
43
|
+
parts = type_str.split(/\s*\|\s*/)
|
|
44
|
+
options[:nullable] = true if parts.delete("null")
|
|
45
|
+
if parts.size == 1
|
|
46
|
+
parse(parts.first, **options)
|
|
47
|
+
else
|
|
48
|
+
{type: parts.map(&:to_sym)}.merge(options)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
37
51
|
end
|
|
38
52
|
end
|
|
39
53
|
end
|
data/lib/typelizer/version.rb
CHANGED
data/lib/typelizer.rb
CHANGED
|
@@ -72,7 +72,14 @@ module Typelizer
|
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
def openapi_schemas(writer_name: nil, openapi_version: "3.0")
|
|
75
|
-
|
|
75
|
+
result = {}
|
|
76
|
+
interfaces(writer_name: writer_name).each do |i|
|
|
77
|
+
result[i.name] = OpenAPI.schema_for(i, openapi_version: openapi_version)
|
|
78
|
+
i.trait_interfaces.each do |trait|
|
|
79
|
+
result[trait.name] = OpenAPI.schema_for(trait, openapi_version: openapi_version)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
result
|
|
76
83
|
end
|
|
77
84
|
|
|
78
85
|
private
|
|
@@ -85,7 +92,10 @@ module Typelizer
|
|
|
85
92
|
resolved = base_classes.filter_map do |base_class|
|
|
86
93
|
Object.const_get(base_class) if Object.const_defined?(base_class)
|
|
87
94
|
end
|
|
88
|
-
|
|
95
|
+
if base_classes.any? && resolved.none?
|
|
96
|
+
logger.warn("Typelizer: No serializers found. Ensure your serializers include Typelizer::DSL.")
|
|
97
|
+
return []
|
|
98
|
+
end
|
|
89
99
|
|
|
90
100
|
(resolved + resolved.flat_map(&:descendants)).uniq
|
|
91
101
|
.reject { |serializer| reject_class.call(serializer: serializer) }
|
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.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Svyatoslav Kryukov
|
|
@@ -72,7 +72,7 @@ files:
|
|
|
72
72
|
- lib/typelizer/renderer.rb
|
|
73
73
|
- lib/typelizer/serializer_config_layer.rb
|
|
74
74
|
- lib/typelizer/serializer_plugins/alba.rb
|
|
75
|
-
- lib/typelizer/serializer_plugins/alba/
|
|
75
|
+
- lib/typelizer/serializer_plugins/alba/block_attribute_collector.rb
|
|
76
76
|
- lib/typelizer/serializer_plugins/alba/trait_interface.rb
|
|
77
77
|
- lib/typelizer/serializer_plugins/ams.rb
|
|
78
78
|
- lib/typelizer/serializer_plugins/auto.rb
|
|
@@ -86,6 +86,7 @@ files:
|
|
|
86
86
|
- lib/typelizer/templates/inheritance.ts.erb
|
|
87
87
|
- lib/typelizer/templates/inline_type.ts.erb
|
|
88
88
|
- lib/typelizer/templates/interface.ts.erb
|
|
89
|
+
- lib/typelizer/type_inference.rb
|
|
89
90
|
- lib/typelizer/type_parser.rb
|
|
90
91
|
- lib/typelizer/union_type_sorter.rb
|
|
91
92
|
- lib/typelizer/version.rb
|