types_from_serializers 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +15 -0
- data/LICENSE.txt +22 -0
- data/README.md +33 -0
- data/lib/types_from_serializers/dsl.rb +91 -0
- data/lib/types_from_serializers/generator.rb +343 -0
- data/lib/types_from_serializers/railtie.rb +45 -0
- data/lib/types_from_serializers/version.rb +6 -0
- data/lib/types_from_serializers.rb +5 -0
- metadata +228 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ab46cb83091bc01087646a3e9c1eee590d4148ea16f86fcca2c83ad91b24d2a5
|
4
|
+
data.tar.gz: 95e62afbb4b49ab897434fe33926322964439af03726c570bec6296eb4c51ca1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8280b0616181a1b13b4192bc6cd9ceb6b43498d31bd83b871d3da0bba924c63b52d67598df83df8d8ccac535999f81a99e42b530cbc0ab6afeddbfa9366928e2
|
7
|
+
data.tar.gz: ee8d6f445603ef26932c631a2064f337186c6f27451771b2f31577df37c587f318b1abe22e9277e7db48f2d65d47ca7c62f1177a67cbc35df615e5277931bce1
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# 0.1.0 (2022-07-12)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* handle non-ActiveRecord models and extract types from unions ([ea9b2a7](https://github.com/ElMassimo/types_from_serializers/commit/ea9b2a71cb85503ff691e5ef115ab73f89b005af))
|
7
|
+
|
8
|
+
|
9
|
+
### Features
|
10
|
+
|
11
|
+
* make the DSL is available without manually requiring ([04a4f3a](https://github.com/ElMassimo/types_from_serializers/commit/04a4f3ad3c96658639cbe75de07f19cc7f79f4c6))
|
12
|
+
* support specifying base serializers and additional dirs to scan ([164cfe1](https://github.com/ElMassimo/types_from_serializers/commit/164cfe17bb0527c59cf95441381aef7bf797a568))
|
13
|
+
|
14
|
+
|
15
|
+
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2021 Máximo Mussini
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
<h1 align="center">
|
2
|
+
Types From Serializers
|
3
|
+
<p align="center">
|
4
|
+
<a href="https://travis-ci.org/ElMassimo/types_from_serializers"><img alt="Build Status" src="https://travis-ci.org/ElMassimo/types_from_serializers.svg"/></a>
|
5
|
+
<a href="http://inch-ci.org/github/ElMassimo/types_from_serializers"><img alt="Inline docs" src="http://inch-ci.org/github/ElMassimo/types_from_serializers.svg"/></a>
|
6
|
+
<a href="https://codeclimate.com/github/ElMassimo/types_from_serializers"><img alt="Maintainability" src="https://codeclimate.com/github/ElMassimo/types_from_serializers/badges/gpa.svg"/></a>
|
7
|
+
<a href="https://codeclimate.com/github/ElMassimo/types_from_serializers"><img alt="Test Coverage" src="https://codeclimate.com/github/ElMassimo/types_from_serializers/badges/coverage.svg"/></a>
|
8
|
+
<a href="https://rubygems.org/gems/types_from_serializers"><img alt="Gem Version" src="https://img.shields.io/gem/v/types_from_serializers.svg?colorB=e9573f"/></a>
|
9
|
+
<a href="https://github.com/ElMassimo/types_from_serializers/blob/main/LICENSE.txt"><img alt="License" src="https://img.shields.io/badge/license-MIT-428F7E.svg"/></a>
|
10
|
+
</p>
|
11
|
+
</h1>
|
12
|
+
|
13
|
+
[aliases]: https://vite-ruby.netlify.app/guide/development.html#import-aliases-%F0%9F%91%89
|
14
|
+
[config options]: https://github.com/ElMassimo/types_from_serializers/blob/main/lib/types_from_serializers/generator.rb#L82-L85
|
15
|
+
[readme]: https://github.com/ElMassimo/types_from_serializers
|
16
|
+
|
17
|
+
For more information, check the main [README].
|
18
|
+
|
19
|
+
### Installation 💿
|
20
|
+
|
21
|
+
Add this line to your application's Gemfile:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
gem 'types_from_serializers'
|
25
|
+
```
|
26
|
+
|
27
|
+
And then execute:
|
28
|
+
|
29
|
+
$ bundle
|
30
|
+
|
31
|
+
Or install it yourself as:
|
32
|
+
|
33
|
+
$ gem install types_from_serializers
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
|
5
|
+
# Internal: A DSL to specify types for serializer attributes.
|
6
|
+
module TypesFromSerializer
|
7
|
+
module DSL
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
# Override: Capture the name of the model related to the serializer.
|
12
|
+
#
|
13
|
+
# name - An alias for the internal object in the serializer.
|
14
|
+
# model - The name of an ActiveRecord model to infer types from the schema.
|
15
|
+
# types_from - The name of a TypeScript interface to infer types from.
|
16
|
+
def object_as(name, model: nil, types_from: nil)
|
17
|
+
# NOTE: Avoid taking memory for type information that won't be used.
|
18
|
+
if Rails.env.development?
|
19
|
+
model ||= name.is_a?(Symbol) ? name : try(:_serializer_model_name)
|
20
|
+
@_serializer_model_name = model || name
|
21
|
+
@_serializer_types_from = types_from if types_from
|
22
|
+
end
|
23
|
+
|
24
|
+
super(name)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Public: Like `attributes`, but providing type information for each field.
|
28
|
+
def typed_attributes(attrs)
|
29
|
+
attributes(*attrs.keys)
|
30
|
+
|
31
|
+
# NOTE: Avoid taking memory for type information that won't be used.
|
32
|
+
if Rails.env.development?
|
33
|
+
_typed_attributes.update(attrs.map { |key, type|
|
34
|
+
[key.to_s, type.is_a?(Hash) ? type : {type: type}]
|
35
|
+
}.to_h)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Public: Allows to specify the type for a serializer method that will
|
40
|
+
# be defined immediately after calling this method.
|
41
|
+
def type(type = :unknown, optional: false)
|
42
|
+
@_current_attribute_type = {type: type, optional: optional}
|
43
|
+
end
|
44
|
+
|
45
|
+
# Internal: Intercept a method definition, tying a type that was
|
46
|
+
# previously specified to the name of the attribute.
|
47
|
+
def method_added(name)
|
48
|
+
super(name)
|
49
|
+
if @_current_attribute_type
|
50
|
+
serializer_attributes name
|
51
|
+
|
52
|
+
# NOTE: Avoid taking memory for type information that won't be used.
|
53
|
+
if Rails.env.development?
|
54
|
+
_typed_attributes[name.to_s] = @_current_attribute_type
|
55
|
+
end
|
56
|
+
|
57
|
+
@_current_attribute_type = nil
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# NOTE: Avoid taking memory for type information that won't be used.
|
62
|
+
if Rails.env.development?
|
63
|
+
# Internal: Contains type information for serializer attributes.
|
64
|
+
def _typed_attributes
|
65
|
+
unless defined?(@_typed_attributes)
|
66
|
+
@_typed_attributes = superclass.try(:_typed_attributes)&.dup || {}
|
67
|
+
end
|
68
|
+
@_typed_attributes
|
69
|
+
end
|
70
|
+
|
71
|
+
# Internal: The name of the model that will be serialized by this
|
72
|
+
# serializer, used to infer field types from the SQL columns.
|
73
|
+
def _serializer_model_name
|
74
|
+
unless defined?(@_serializer_model_name)
|
75
|
+
@_serializer_model_name = superclass.try(:_serializer_model_name)
|
76
|
+
end
|
77
|
+
@_serializer_model_name
|
78
|
+
end
|
79
|
+
|
80
|
+
# Internal: The TypeScript interface that will be used by default to
|
81
|
+
# infer the serializer field types when not explicitly provided.
|
82
|
+
def _serializer_types_from
|
83
|
+
unless defined?(@_serializer_types_from)
|
84
|
+
@_serializer_types_from = superclass.try(:_serializer_types_from)
|
85
|
+
end
|
86
|
+
@_serializer_types_from
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,343 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "digest"
|
4
|
+
require "fileutils"
|
5
|
+
require "pathname"
|
6
|
+
|
7
|
+
# Public: Automatically generates TypeScript interfaces for Ruby serializers.
|
8
|
+
module TypesFromSerializers
|
9
|
+
# Internal: The configuration for TypeScript generation.
|
10
|
+
Config = Struct.new(
|
11
|
+
:base_serializers,
|
12
|
+
:serializers_dirs,
|
13
|
+
:output_dir,
|
14
|
+
:name_from_serializer,
|
15
|
+
:native_types,
|
16
|
+
:sql_to_typescript_type_mapping,
|
17
|
+
keyword_init: true,
|
18
|
+
)
|
19
|
+
|
20
|
+
# Internal: The type metadata for a serializer.
|
21
|
+
SerializerMetadata = Struct.new(
|
22
|
+
:attributes,
|
23
|
+
:associations,
|
24
|
+
:model_name,
|
25
|
+
:types_from,
|
26
|
+
keyword_init: true,
|
27
|
+
)
|
28
|
+
|
29
|
+
# Internal: The type metadata for a serializer field.
|
30
|
+
FieldMetadata = Struct.new(:name, :type, :optional, :many, keyword_init: true) do
|
31
|
+
def typescript_name
|
32
|
+
name.to_s.camelize(:lower)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Internal: Extensions that simplify the implementation of the generator.
|
37
|
+
module SerializerRefinements
|
38
|
+
refine String do
|
39
|
+
# Internal: Converts a name such as :user to the User constant.
|
40
|
+
def to_model
|
41
|
+
classify.safe_constantize
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
refine Symbol do
|
46
|
+
def safe_constantize
|
47
|
+
to_s.classify.safe_constantize
|
48
|
+
end
|
49
|
+
|
50
|
+
delegate :to_model, to: :to_s
|
51
|
+
end
|
52
|
+
# rubocop:enable Rails/Delegate
|
53
|
+
|
54
|
+
refine Class do
|
55
|
+
# Internal: Name of the TypeScript interface.
|
56
|
+
def typescript_interface_name
|
57
|
+
TypesFromSerializers.config.name_from_serializer.call(name).tr_s(":", "")
|
58
|
+
end
|
59
|
+
|
60
|
+
# Internal: The base name of the TypeScript file to be written.
|
61
|
+
def typescript_interface_basename
|
62
|
+
TypesFromSerializers.config.name_from_serializer.call(name).gsub("::", "/")
|
63
|
+
end
|
64
|
+
|
65
|
+
# Internal: A first pass of gathering types for the serializer fields.
|
66
|
+
def typescript_metadata
|
67
|
+
SerializerMetadata.new(
|
68
|
+
model_name: _serializer_model_name,
|
69
|
+
types_from: _serializer_types_from,
|
70
|
+
attributes: _attributes.map { |key, options|
|
71
|
+
typed_attrs = _typed_attributes.fetch(key, {})
|
72
|
+
FieldMetadata.new(
|
73
|
+
**typed_attrs,
|
74
|
+
name: key,
|
75
|
+
optional: typed_attrs[:optional] || options.key?(:if),
|
76
|
+
)
|
77
|
+
},
|
78
|
+
associations: _associations.map { |key, options|
|
79
|
+
FieldMetadata.new(
|
80
|
+
name: options.fetch(:root, key),
|
81
|
+
type: options.fetch(:serializer),
|
82
|
+
optional: options.key?(:if),
|
83
|
+
many: options.fetch(:write_method) == :write_many,
|
84
|
+
)
|
85
|
+
},
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Internal: Infers field types by checking the SQL columns for the model
|
90
|
+
# serialized, or from a TypeScript interface if provided.
|
91
|
+
def typescript_infer_types(metadata)
|
92
|
+
model = metadata.model_name&.to_model
|
93
|
+
interface = metadata.types_from
|
94
|
+
|
95
|
+
metadata.attributes.reject(&:type).each do |meta|
|
96
|
+
if model&.respond_to?(:columns_hash) && (column = model.columns_hash[meta.name.to_s])
|
97
|
+
meta[:type] = TypesFromSerializers.config.sql_to_typescript_type_mapping[column.type]
|
98
|
+
meta[:optional] ||= column.null
|
99
|
+
elsif interface
|
100
|
+
meta[:type] = "#{interface}['#{meta.typescript_name}']"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def typescript_imports(metadata)
|
106
|
+
assoc_imports = metadata.associations.map { |meta|
|
107
|
+
[meta.type.typescript_interface_name, "~/types/serializers/#{meta.type.typescript_interface_basename}"]
|
108
|
+
}
|
109
|
+
|
110
|
+
attr_imports = metadata.attributes
|
111
|
+
.flat_map { |meta| extract_typescript_types(meta.type.to_s) }
|
112
|
+
.uniq
|
113
|
+
.reject { |type| typescript_native_type?(type) }
|
114
|
+
.map { |type|
|
115
|
+
[type, "~/types/#{type}"]
|
116
|
+
}
|
117
|
+
|
118
|
+
(assoc_imports + attr_imports).uniq.map { |interface, filename|
|
119
|
+
"import type #{interface} from '#{filename}'\n"
|
120
|
+
}.uniq
|
121
|
+
end
|
122
|
+
|
123
|
+
# Internal: Extracts any types inside generics or array types.
|
124
|
+
def extract_typescript_types(type)
|
125
|
+
type.split(/[<>\[\],\s|]+/)
|
126
|
+
end
|
127
|
+
|
128
|
+
# NOTE: Treat uppercase names as custom types.
|
129
|
+
# Lowercase names would be native types, such as :string and :boolean.
|
130
|
+
def typescript_native_type?(type)
|
131
|
+
type[0] == type[0].downcase || TypesFromSerializers.config.native_types.include?(type)
|
132
|
+
end
|
133
|
+
|
134
|
+
def typescript_fields(metadata)
|
135
|
+
(metadata.attributes + metadata.associations).map { |meta|
|
136
|
+
type = meta.type.is_a?(Class) ? meta.type.typescript_interface_name : meta.type || :unknown
|
137
|
+
type = meta.many ? "#{type}[]" : type
|
138
|
+
" #{meta.typescript_name}#{"?" if meta.optional}: #{type}"
|
139
|
+
}
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Internal: Structure to keep track of changed files.
|
145
|
+
class Changes
|
146
|
+
def initialize(dirs)
|
147
|
+
@added = Set.new
|
148
|
+
@removed = Set.new
|
149
|
+
@modified = Set.new
|
150
|
+
track_changes(dirs)
|
151
|
+
end
|
152
|
+
|
153
|
+
def updated?
|
154
|
+
@modified.any? || @added.any? || @removed.any?
|
155
|
+
end
|
156
|
+
|
157
|
+
def any_removed?
|
158
|
+
@removed.any?
|
159
|
+
end
|
160
|
+
|
161
|
+
def modified_files
|
162
|
+
@modified
|
163
|
+
end
|
164
|
+
|
165
|
+
def only_modified?
|
166
|
+
@added.empty? && @removed.empty?
|
167
|
+
end
|
168
|
+
|
169
|
+
def clear
|
170
|
+
@added.clear
|
171
|
+
@removed.clear
|
172
|
+
@modified.clear
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
|
177
|
+
def track_changes(dirs)
|
178
|
+
Listen.to(*dirs, only: %r{.rb$}) do |modified, added, removed|
|
179
|
+
modified.each { |file| @modified.add(file) }
|
180
|
+
added.each { |file| @added.add(file) }
|
181
|
+
removed.each { |file| @removed.add(file) }
|
182
|
+
end.start
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
class << self
|
187
|
+
using SerializerRefinements
|
188
|
+
|
189
|
+
attr_reader :force_generation
|
190
|
+
|
191
|
+
# Public: Configuration of the code generator.
|
192
|
+
def config
|
193
|
+
(@config ||= default_config(root)).tap do |config|
|
194
|
+
yield(config) if block_given?
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# Public: Generates code for all serializers in the app.
|
199
|
+
def generate(force: ENV["SERIALIZER_TYPES_FORCE"])
|
200
|
+
@force_generation = force
|
201
|
+
generate_index_file
|
202
|
+
|
203
|
+
loaded_serializers.each do |serializer|
|
204
|
+
generate_interface_for(serializer)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def generate_changed
|
209
|
+
if changes.updated?
|
210
|
+
config.output_dir.rmtree if changes.any_removed?
|
211
|
+
load_serializers(changes.modified_files)
|
212
|
+
generate
|
213
|
+
changes.clear
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# Internal: Defines a TypeScript interface for the serializer.
|
218
|
+
def generate_interface_for(serializer)
|
219
|
+
metadata = serializer.typescript_metadata
|
220
|
+
filename = serializer.typescript_interface_basename
|
221
|
+
|
222
|
+
write_if_changed(filename: filename, cache_key: metadata.inspect) {
|
223
|
+
serializer.typescript_infer_types(metadata)
|
224
|
+
<<~TS
|
225
|
+
//
|
226
|
+
// DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
|
227
|
+
#{serializer.typescript_imports(metadata).join}
|
228
|
+
export default interface #{serializer.typescript_interface_name} {
|
229
|
+
#{serializer.typescript_fields(metadata).join("\n")}
|
230
|
+
}
|
231
|
+
TS
|
232
|
+
}
|
233
|
+
end
|
234
|
+
|
235
|
+
# Internal: Allows to import all serializer types from a single file.
|
236
|
+
def generate_index_file
|
237
|
+
write_if_changed(filename: "index", cache_key: all_serializer_files.join) {
|
238
|
+
load_serializers(all_serializer_files)
|
239
|
+
<<~TS
|
240
|
+
//
|
241
|
+
// DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
|
242
|
+
#{loaded_serializers.map { |s| "export type { default as #{s.typescript_interface_name} } from './#{s.typescript_interface_basename}'" }.join("\n")}
|
243
|
+
TS
|
244
|
+
}
|
245
|
+
end
|
246
|
+
|
247
|
+
# Internal: Checks if it should avoid generating an interface.
|
248
|
+
def skip_serializer?(name)
|
249
|
+
name.include?("BaseSerializer") || name.in?(config.base_serializers)
|
250
|
+
end
|
251
|
+
|
252
|
+
# Internal: Returns an object compatible with FileUpdateChecker.
|
253
|
+
def track_changes
|
254
|
+
changes
|
255
|
+
end
|
256
|
+
|
257
|
+
private
|
258
|
+
|
259
|
+
def root
|
260
|
+
defined?(Rails) ? Rails.root : Pathname.new(Dir.pwd)
|
261
|
+
end
|
262
|
+
|
263
|
+
def changes
|
264
|
+
@changes ||= Changes.new(config.serializers_dirs)
|
265
|
+
end
|
266
|
+
|
267
|
+
def all_serializer_files
|
268
|
+
config.serializers_dirs.flat_map { |dir| Dir["#{dir}/**/*.rb"] }
|
269
|
+
end
|
270
|
+
|
271
|
+
def load_serializers(files)
|
272
|
+
files.each { |file| require file }
|
273
|
+
end
|
274
|
+
|
275
|
+
def loaded_serializers
|
276
|
+
config.base_serializers.map(&:constantize)
|
277
|
+
.flat_map(&:descendants)
|
278
|
+
.uniq
|
279
|
+
.sort_by(&:name)
|
280
|
+
.reject { |s| skip_serializer?(s.name) }
|
281
|
+
rescue NameError
|
282
|
+
raise ArgumentError, "Please ensure all your serializers extend BaseSerializer, or configure `config.base_serializers`."
|
283
|
+
end
|
284
|
+
|
285
|
+
def default_config(root)
|
286
|
+
Config.new(
|
287
|
+
# The base serializers that all other serializers extend.
|
288
|
+
base_serializers: ["BaseSerializer"],
|
289
|
+
|
290
|
+
# The dirs where the serializer files are located.
|
291
|
+
serializers_dirs: [root.join("app/serializers").to_s],
|
292
|
+
|
293
|
+
# The dir where interface files are placed.
|
294
|
+
output_dir: root.join(defined?(ViteRuby) ? ViteRuby.config.source_code_dir : "app/frontend").join("types/serializers"),
|
295
|
+
|
296
|
+
# Remove the serializer suffix from the class name.
|
297
|
+
name_from_serializer: ->(name) { name.delete_suffix("Serializer") },
|
298
|
+
|
299
|
+
# Types that don't need to be imported in TypeScript.
|
300
|
+
native_types: [
|
301
|
+
"Array",
|
302
|
+
"Record",
|
303
|
+
"Date",
|
304
|
+
].to_set,
|
305
|
+
|
306
|
+
# Maps SQL column types to TypeScript native and custom types.
|
307
|
+
sql_to_typescript_type_mapping: {
|
308
|
+
boolean: :boolean,
|
309
|
+
date: "string | Date",
|
310
|
+
datetime: "string | Date",
|
311
|
+
decimal: :number,
|
312
|
+
integer: :number,
|
313
|
+
string: :string,
|
314
|
+
text: :string,
|
315
|
+
}.tap do |types|
|
316
|
+
types.default = :unknown
|
317
|
+
end,
|
318
|
+
)
|
319
|
+
end
|
320
|
+
|
321
|
+
# Internal: Writes if the file does not exist or the cache key has changed.
|
322
|
+
# The cache strategy consists of a comment on the first line of the file.
|
323
|
+
#
|
324
|
+
# Yields to receive the rendered file content when it needs to.
|
325
|
+
def write_if_changed(filename:, cache_key:)
|
326
|
+
filename = config.output_dir.join("#{filename}.ts")
|
327
|
+
FileUtils.mkdir_p(filename.dirname)
|
328
|
+
cache_key_comment = "// TypesFromSerializers CacheKey #{Digest::MD5.hexdigest(cache_key)}\n"
|
329
|
+
File.open(filename, "a+") { |file|
|
330
|
+
if stale?(file, cache_key_comment)
|
331
|
+
file.truncate(0)
|
332
|
+
file.write(cache_key_comment)
|
333
|
+
file.write(yield)
|
334
|
+
end
|
335
|
+
}
|
336
|
+
end
|
337
|
+
|
338
|
+
# Internal: Returns true if the cache key has changed since the last codegen.
|
339
|
+
def stale?(file, cache_key_comment)
|
340
|
+
@force_generation || file.gets != cache_key_comment
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/railtie"
|
4
|
+
|
5
|
+
class TypesFromSerializers::Railtie < Rails::Railtie
|
6
|
+
railtie_name :types_from_serializers
|
7
|
+
|
8
|
+
# Automatically generates code whenever a serializer is loaded.
|
9
|
+
if defined?(Rails.env) && Rails.env.development?
|
10
|
+
require_relative "generator"
|
11
|
+
|
12
|
+
initializer "types_from_serializers.reloader" do |app|
|
13
|
+
if Gem.loaded_specs["listen"]
|
14
|
+
require "listen"
|
15
|
+
|
16
|
+
app.config.after_initialize do
|
17
|
+
app.reloaders << TypesFromSerializers.track_changes
|
18
|
+
end
|
19
|
+
|
20
|
+
app.config.to_prepare do
|
21
|
+
TypesFromSerializers.generate_changed
|
22
|
+
end
|
23
|
+
else
|
24
|
+
app.config.to_prepare do
|
25
|
+
TypesFromSerializers.generate
|
26
|
+
end
|
27
|
+
|
28
|
+
Rails.logger.warn "Add 'listen' to your Gemfile to automatically generate code on serializer changes."
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Suitable when triggering code generation manually.
|
34
|
+
rake_tasks do |app|
|
35
|
+
namespace :types_from_serializers do
|
36
|
+
desc "Generates TypeScript interfaces for each serializer in the app."
|
37
|
+
task generate: :environment do
|
38
|
+
require_relative "generator"
|
39
|
+
serializers = TypesFromSerializers.generate(force: true)
|
40
|
+
puts "Generated TypeScript interfaces for #{serializers.size} serializers:"
|
41
|
+
puts serializers.map { |s| "\t#{s.name}" }.join("\n")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
metadata
ADDED
@@ -0,0 +1,228 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: types_from_serializers
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Máximo Mussini
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-07-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: railties
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.1'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '8'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '5.1'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '8'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: oj_serializers
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '1.0'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '1.0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: listen
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '3.2'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '3.2'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: bundler
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '2'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '2'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: pry-byebug
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '3.9'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '3.9'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: rake
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '13'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '13'
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: rspec-given
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '3.8'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - "~>"
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '3.8'
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
name: simplecov
|
119
|
+
requirement: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - "<"
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0.18'
|
124
|
+
type: :development
|
125
|
+
prerelease: false
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - "<"
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0.18'
|
131
|
+
- !ruby/object:Gem::Dependency
|
132
|
+
name: standard
|
133
|
+
requirement: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - "~>"
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '1.0'
|
138
|
+
type: :development
|
139
|
+
prerelease: false
|
140
|
+
version_requirements: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - "~>"
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '1.0'
|
145
|
+
- !ruby/object:Gem::Dependency
|
146
|
+
name: rubocop-rails
|
147
|
+
requirement: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
type: :development
|
153
|
+
prerelease: false
|
154
|
+
version_requirements: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - ">="
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '0'
|
159
|
+
- !ruby/object:Gem::Dependency
|
160
|
+
name: rubocop-rspec
|
161
|
+
requirement: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - ">="
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
type: :development
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - ">="
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: '0'
|
173
|
+
- !ruby/object:Gem::Dependency
|
174
|
+
name: rubocop-performance
|
175
|
+
requirement: !ruby/object:Gem::Requirement
|
176
|
+
requirements:
|
177
|
+
- - ">="
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: '0'
|
180
|
+
type: :development
|
181
|
+
prerelease: false
|
182
|
+
version_requirements: !ruby/object:Gem::Requirement
|
183
|
+
requirements:
|
184
|
+
- - ">="
|
185
|
+
- !ruby/object:Gem::Version
|
186
|
+
version: '0'
|
187
|
+
description: types_from_serializers helps you by automatically generating TypeScript
|
188
|
+
interfaces for your JSON serializers, allowing you typecheck your frontend code
|
189
|
+
to ship fast and with confidence.
|
190
|
+
email:
|
191
|
+
- maximomussini@gmail.com
|
192
|
+
executables: []
|
193
|
+
extensions: []
|
194
|
+
extra_rdoc_files:
|
195
|
+
- README.md
|
196
|
+
files:
|
197
|
+
- CHANGELOG.md
|
198
|
+
- LICENSE.txt
|
199
|
+
- README.md
|
200
|
+
- lib/types_from_serializers.rb
|
201
|
+
- lib/types_from_serializers/dsl.rb
|
202
|
+
- lib/types_from_serializers/generator.rb
|
203
|
+
- lib/types_from_serializers/railtie.rb
|
204
|
+
- lib/types_from_serializers/version.rb
|
205
|
+
homepage: https://github.com/ElMassimo/types_from_serializers
|
206
|
+
licenses:
|
207
|
+
- MIT
|
208
|
+
metadata: {}
|
209
|
+
post_install_message:
|
210
|
+
rdoc_options: []
|
211
|
+
require_paths:
|
212
|
+
- lib
|
213
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
214
|
+
requirements:
|
215
|
+
- - ">="
|
216
|
+
- !ruby/object:Gem::Version
|
217
|
+
version: '0'
|
218
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - ">="
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: '0'
|
223
|
+
requirements: []
|
224
|
+
rubygems_version: 3.3.7
|
225
|
+
signing_key:
|
226
|
+
specification_version: 4
|
227
|
+
summary: Generate TypeScript interfaces from your JSON serializers.
|
228
|
+
test_files: []
|