types_from_serializers 0.1.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 +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: []
|