foobara-typescript-remote-command-generator 1.1.4 → 1.1.6
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 +13 -0
- data/src/remote_generator/services/aggregate_entity_generator.rb +1 -1
- data/src/remote_generator/services/aggregate_model_generator.rb +1 -1
- data/src/remote_generator/services/atom_entity_generator.rb +1 -1
- data/src/remote_generator/services/atom_model_generator.rb +1 -1
- data/src/remote_generator/services/command_cast_result_generator.rb +313 -0
- data/src/remote_generator/services/command_generator.rb +36 -0
- data/src/remote_generator/services/command_result_generator.rb +20 -4
- data/src/remote_generator/services/dependency_group.rb +0 -10
- data/src/remote_generator/services/domain_generator.rb +2 -1
- data/src/remote_generator/services/entity_variants_generator.rb +1 -1
- data/src/remote_generator/services/error_generator.rb +1 -1
- data/src/remote_generator/services/model_variants_generator.rb +5 -5
- data/src/remote_generator/services/type_generator.rb +15 -3
- data/src/remote_generator/services/typescript_from_manifest_base_generator.rb +6 -11
- data/src/remote_generator/write_typescript_to_disk.rb +0 -19
- data/templates/Command/castJsonResult.ts.erb +9 -0
- data/templates/Command.ts.erb +9 -0
- data/templates/Entity/Loaded.ts.erb +6 -0
- data/templates/Entity/Unloaded.ts.erb +4 -5
- data/templates/Foobara/Auth/utils/accessTokens.ts.erb +7 -13
- data/templates/base/Entity.ts +16 -4
- data/templates/base/QueryCache.ts +4 -0
- data/templates/base/RemoteCommand.ts +7 -1
- data/templates/hooks/useQuery.ts +11 -7
- metadata +5 -5
- data/src/remote_generator/services/auth/setup_generator.rb +0 -25
- data/templates/setup.ts.erb +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4ded6d4d4e950f079230273d922ef380455597c4dd0fcf01005396156871291e
|
4
|
+
data.tar.gz: 9f2ff8044bd23a32c5db7768a4783e17c3be1742f43edf7d6c60664935e9ff1f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4447f6d43c30e80f514c8db191bbf8bb115ded65df4febaa030c877bffd88db7820db66d637185353e7bb7aab81d666dc38002d5413d99358f5a07e217fdcbfc
|
7
|
+
data.tar.gz: 4929a9a0ccb737f0df18979adcfc7d3d223c272c810f44aef208441b01077b77fcde6a114e0f822347530a39774c5bd06b0d2463ddb6503835a18d3853ea7449
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
## [1.1.6] - 2025-10-17
|
2
|
+
|
3
|
+
- Dirty all queries on login/logout, not just GetCurrentUser
|
4
|
+
- Remove no-longer needed /setup.ts file generation
|
5
|
+
|
6
|
+
## [1.1.5] - 2025-10-15
|
7
|
+
|
8
|
+
- Implement castJsonResult
|
9
|
+
- Fix various problems with Model and subclass constructors
|
10
|
+
- Memoize various #model_generators, #dependencies, #dependency_roots
|
11
|
+
- Cache generator creation
|
12
|
+
- Add ruby-prof and term trap spec support files
|
13
|
+
|
1
14
|
## [1.1.4] - 2025-10-02
|
2
15
|
|
3
16
|
- Don't give warning about importing domain/setup.ts if it already has been added to index.tsx
|
@@ -25,7 +25,7 @@ module Foobara
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def model_generators
|
28
|
-
types_depended_on.select(&:model?).map do |model|
|
28
|
+
@model_generators ||= types_depended_on.select(&:model?).map do |model|
|
29
29
|
if model.detached_entity?
|
30
30
|
Services::AggregateEntityGenerator.new(model)
|
31
31
|
else
|
@@ -23,7 +23,7 @@ module Foobara
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def model_generators
|
26
|
-
types_depended_on.select(&:model?).map do |model|
|
26
|
+
@model_generators ||= types_depended_on.select(&:model?).map do |model|
|
27
27
|
if model.detached_entity?
|
28
28
|
Services::UnloadedEntityGenerator.new(model)
|
29
29
|
else
|
@@ -25,7 +25,7 @@ module Foobara
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def model_generators
|
28
|
-
types_depended_on.select(&:model?).map do |model|
|
28
|
+
@model_generators ||= types_depended_on.select(&:model?).map do |model|
|
29
29
|
if model.detached_entity?
|
30
30
|
Services::UnloadedEntityGenerator.new(model)
|
31
31
|
else
|
@@ -0,0 +1,313 @@
|
|
1
|
+
require_relative "command_result_generator"
|
2
|
+
|
3
|
+
module Foobara
|
4
|
+
module RemoteGenerator
|
5
|
+
class Services
|
6
|
+
class CommandCastResultGenerator < CommandResultGenerator
|
7
|
+
class CastTree
|
8
|
+
attr_accessor :children, :declaration_to_cast, :past_first_model
|
9
|
+
|
10
|
+
def initialize(children: nil, declaration_to_cast: nil, past_first_model: false)
|
11
|
+
self.children = children
|
12
|
+
self.declaration_to_cast = declaration_to_cast
|
13
|
+
self.past_first_model = past_first_model
|
14
|
+
end
|
15
|
+
|
16
|
+
def empty?
|
17
|
+
(children.nil? || children.empty?) && declaration_to_cast.nil?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
alias command_manifest relevant_manifest
|
22
|
+
|
23
|
+
def result_type
|
24
|
+
command_manifest.result_type
|
25
|
+
end
|
26
|
+
|
27
|
+
def target_path
|
28
|
+
[*scoped_full_path, "castJsonResult.ts"]
|
29
|
+
end
|
30
|
+
|
31
|
+
def template_path
|
32
|
+
"Command/castJsonResult.ts.erb"
|
33
|
+
end
|
34
|
+
|
35
|
+
def applicable?
|
36
|
+
result_json_requires_cast?
|
37
|
+
end
|
38
|
+
|
39
|
+
def nested_model_generators
|
40
|
+
return @nested_model_generators if defined?(@nested_model_generators)
|
41
|
+
|
42
|
+
nested_model_generators = []
|
43
|
+
|
44
|
+
generators = model_generators
|
45
|
+
|
46
|
+
generators.each do |generator|
|
47
|
+
_models_reachable_from_declaration(
|
48
|
+
generator.relevant_manifest
|
49
|
+
)&.each do |(model, past_first)|
|
50
|
+
generator_class = if atom?
|
51
|
+
if model.detached_entity? && past_first
|
52
|
+
Services::UnloadedEntityGenerator
|
53
|
+
else
|
54
|
+
Services::AtomModelGenerator
|
55
|
+
end
|
56
|
+
elsif aggregate?
|
57
|
+
Services::AggregateModelGenerator
|
58
|
+
else
|
59
|
+
Services::ModelGenerator
|
60
|
+
end
|
61
|
+
|
62
|
+
new_generator = generator_class.new(model)
|
63
|
+
|
64
|
+
unless generators.any? do |g|
|
65
|
+
g.relevant_manifest == model && g.class == new_generator.class
|
66
|
+
end
|
67
|
+
nested_model_generators << new_generator
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
@nested_model_generators = nested_model_generators
|
73
|
+
end
|
74
|
+
|
75
|
+
def atom?
|
76
|
+
serializers&.any? { |s| s == "Foobara::CommandConnectors::Serializers::AtomicSerializer" }
|
77
|
+
end
|
78
|
+
|
79
|
+
def aggregate?
|
80
|
+
serializers&.any? { |s| s == "Foobara::CommandConnectors::Serializers::AggregateSerializer" }
|
81
|
+
end
|
82
|
+
|
83
|
+
def dependencies
|
84
|
+
@dependencies ||= model_generators + nested_model_generators
|
85
|
+
end
|
86
|
+
|
87
|
+
def cast_json_result_function
|
88
|
+
"#{cast_json_result_function_body}\nreturn json"
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
# TODO: need to make use of initial?
|
94
|
+
def cast_json_result_function_body(
|
95
|
+
cast_tree = _construct_cast_tree(result_type),
|
96
|
+
parent: "json",
|
97
|
+
property: nil,
|
98
|
+
value: parent
|
99
|
+
)
|
100
|
+
if cast_tree.nil? || cast_tree.empty?
|
101
|
+
return
|
102
|
+
end
|
103
|
+
|
104
|
+
result = []
|
105
|
+
|
106
|
+
case cast_tree
|
107
|
+
when CastTree
|
108
|
+
result << cast_json_result_function_body(cast_tree.children, parent: value)
|
109
|
+
result << _ts_cast_expression(cast_tree, parent:, property:, value:)
|
110
|
+
when ::Hash
|
111
|
+
cast_tree.each_pair do |path_part, child_cast_tree|
|
112
|
+
if path_part == :"#"
|
113
|
+
result << "#{parent}?.forEach((element: any, index: number, array: any[]) => {"
|
114
|
+
result << cast_json_result_function_body(child_cast_tree,
|
115
|
+
parent: "array",
|
116
|
+
property: "index",
|
117
|
+
value: "element")
|
118
|
+
result << "})"
|
119
|
+
elsif child_cast_tree.is_a?(::Hash)
|
120
|
+
# TODO: either test this codepath or delete it
|
121
|
+
# :nocov:
|
122
|
+
property = path_part =~ /\A\d+\z/ ? path_part.to_i : "\"#{path_part}\""
|
123
|
+
|
124
|
+
result << cast_json_result_function_body(child_cast_tree,
|
125
|
+
parent:,
|
126
|
+
property:,
|
127
|
+
value: "#{parent}.#{path_part}")
|
128
|
+
# :nocov:
|
129
|
+
elsif child_cast_tree.is_a?(CastTree)
|
130
|
+
result << cast_json_result_function_body(child_cast_tree.children,
|
131
|
+
parent: "#{parent}.#{path_part}")
|
132
|
+
|
133
|
+
property = path_part =~ /\A\d+\z/ ? path_part.to_i : "\"#{path_part}\""
|
134
|
+
result << _ts_cast_expression(child_cast_tree,
|
135
|
+
parent:,
|
136
|
+
property:,
|
137
|
+
value: "#{parent}.#{path_part}")
|
138
|
+
else
|
139
|
+
# :nocov:
|
140
|
+
raise "Not sure how to handle a #{cast_tree.class}: #{cast_tree}"
|
141
|
+
# :nocov:
|
142
|
+
end
|
143
|
+
end
|
144
|
+
else
|
145
|
+
# :nocov:
|
146
|
+
raise "Not sure how to handle a #{cast_tree.class}: #{cast_tree}"
|
147
|
+
# :nocov:
|
148
|
+
end
|
149
|
+
|
150
|
+
result.compact.join("\n")
|
151
|
+
end
|
152
|
+
|
153
|
+
def _ts_cast_expression(cast_tree, value:, parent: nil, property: nil)
|
154
|
+
unless cast_tree.is_a?(CastTree)
|
155
|
+
# :nocov:
|
156
|
+
raise "Expected a CastTree but got a #{cast_tree.class}: #{cast_tree}"
|
157
|
+
# :nocov:
|
158
|
+
end
|
159
|
+
|
160
|
+
type_declaration = cast_tree.declaration_to_cast
|
161
|
+
|
162
|
+
return unless type_declaration
|
163
|
+
|
164
|
+
lvalue = if parent
|
165
|
+
if property.nil?
|
166
|
+
parent
|
167
|
+
elsif property =~ /\A"(.*)"\z/
|
168
|
+
"#{parent}.#{$1}"
|
169
|
+
else
|
170
|
+
"#{parent}[#{property}]"
|
171
|
+
end
|
172
|
+
else
|
173
|
+
# TODO: either test this path or raise
|
174
|
+
# :nocov:
|
175
|
+
value
|
176
|
+
# :nocov:
|
177
|
+
end
|
178
|
+
|
179
|
+
type = if type_declaration.is_a?(Manifest::TypeDeclaration)
|
180
|
+
type_declaration.to_type
|
181
|
+
else
|
182
|
+
type_declaration
|
183
|
+
end
|
184
|
+
|
185
|
+
type_symbol = type.type_symbol
|
186
|
+
|
187
|
+
value ||= lvalue
|
188
|
+
|
189
|
+
if type_symbol == :date || type_symbol == :datetime
|
190
|
+
"#{lvalue} = new Date(#{value})"
|
191
|
+
elsif type.model?
|
192
|
+
ts_model_name = model_to_ts_model_name(type, association_depth:,
|
193
|
+
initial: !cast_tree.past_first_model)
|
194
|
+
|
195
|
+
"#{lvalue} = new #{ts_model_name}(#{value})"
|
196
|
+
else
|
197
|
+
# :nocov:
|
198
|
+
raise "Not sure how to cast type #{type} to a Typescript expression"
|
199
|
+
# :nocov:
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def _construct_cast_tree(type_declaration, past_first_model: false)
|
204
|
+
if type_declaration.is_a?(Manifest::Attributes)
|
205
|
+
return unless type_declaration.has_attribute_declarations?
|
206
|
+
return if type_declaration.attribute_declarations.empty?
|
207
|
+
|
208
|
+
path_tree = {}
|
209
|
+
|
210
|
+
type_declaration.attribute_declarations.each_pair do |attribute_name, attribute_declaration|
|
211
|
+
if type_requires_cast?(attribute_declaration)
|
212
|
+
path_tree[attribute_name] = _construct_cast_tree(attribute_declaration, past_first_model:)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
unless path_tree.empty?
|
217
|
+
CastTree.new(children: path_tree, past_first_model:)
|
218
|
+
end
|
219
|
+
elsif type_declaration.is_a?(Manifest::Array)
|
220
|
+
element_type = type_declaration.element_type
|
221
|
+
|
222
|
+
if element_type && type_requires_cast?(element_type)
|
223
|
+
CastTree.new(children: { "#": _construct_cast_tree(element_type, past_first_model:) })
|
224
|
+
end
|
225
|
+
elsif type_declaration.type.to_sym == :date || type_declaration.type.to_sym == :datetime
|
226
|
+
CastTree.new(declaration_to_cast: type_declaration, past_first_model:)
|
227
|
+
elsif type_declaration.model?
|
228
|
+
type_declaration = type_declaration.to_type
|
229
|
+
|
230
|
+
children = if type_declaration.detached_entity? && atom?
|
231
|
+
nil
|
232
|
+
else
|
233
|
+
_construct_cast_tree(type_declaration.attributes_type, past_first_model: true)
|
234
|
+
end
|
235
|
+
|
236
|
+
CastTree.new(children:, declaration_to_cast: type_declaration, past_first_model:)
|
237
|
+
# TODO: either test this code path or raise or delete it
|
238
|
+
# :nocov:
|
239
|
+
elsif type_declaration.custom?
|
240
|
+
if type_requires_cast?(type_declaration.base_type.to_type_declaration)
|
241
|
+
tree = _construct_cast_tree(type_declaration.base_type.to_type_declaration, past_first_model:)
|
242
|
+
|
243
|
+
if tree && !tree.empty?
|
244
|
+
CastTree.new(children: tree, past_first_model:)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
# :nocov:
|
249
|
+
end
|
250
|
+
|
251
|
+
# TODO: Feels like similar complicated logic is popping up in many places? How to find/converge such logic
|
252
|
+
def _models_reachable_from_declaration(type_declaration, past_first_model: false)
|
253
|
+
if type_declaration.is_a?(Manifest::Attributes)
|
254
|
+
return unless type_declaration.has_attribute_declarations?
|
255
|
+
return if type_declaration.attribute_declarations.empty?
|
256
|
+
|
257
|
+
models = nil
|
258
|
+
|
259
|
+
type_declaration.attribute_declarations.each_value do |attribute_declaration|
|
260
|
+
if type_requires_cast?(attribute_declaration)
|
261
|
+
models ||= Set.new
|
262
|
+
|
263
|
+
_models_reachable_from_declaration(
|
264
|
+
attribute_declaration,
|
265
|
+
past_first_model:
|
266
|
+
)&.each do |pair|
|
267
|
+
models << pair
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
models
|
273
|
+
elsif type_declaration.is_a?(Manifest::Array)
|
274
|
+
element_type = type_declaration.element_type
|
275
|
+
|
276
|
+
if element_type && type_requires_cast?(element_type)
|
277
|
+
_models_reachable_from_declaration(element_type, past_first_model:)
|
278
|
+
end
|
279
|
+
elsif type_declaration.model?
|
280
|
+
if type_declaration.is_a?(Manifest::TypeDeclaration)
|
281
|
+
type_declaration = type_declaration.to_type
|
282
|
+
end
|
283
|
+
|
284
|
+
models = Set[[type_declaration, past_first_model]]
|
285
|
+
|
286
|
+
if atom? && type_declaration.detached_entity?
|
287
|
+
return models
|
288
|
+
end
|
289
|
+
|
290
|
+
_models_reachable_from_declaration(
|
291
|
+
type_declaration.attributes_type,
|
292
|
+
past_first_model: true
|
293
|
+
)&.each do |pair|
|
294
|
+
models << pair
|
295
|
+
end
|
296
|
+
|
297
|
+
models
|
298
|
+
elsif type_declaration.custom?
|
299
|
+
# TODO: either test this code path or raise or delete it
|
300
|
+
# :nocov:
|
301
|
+
if type_requires_cast?(type_declaration.base_type.to_type_declaration)
|
302
|
+
_models_reachable_from_declaration(
|
303
|
+
type_declaration.base_type.to_type_declaration,
|
304
|
+
past_first_model:
|
305
|
+
)
|
306
|
+
end
|
307
|
+
# :nocov:
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
@@ -43,6 +43,42 @@ module Foobara
|
|
43
43
|
def base_class_name
|
44
44
|
base_class_path.split("/").last
|
45
45
|
end
|
46
|
+
|
47
|
+
def result_json_requires_cast?
|
48
|
+
# What types require a cast?
|
49
|
+
# :date and :datetime, :model, custom type declaration (check #custom?)
|
50
|
+
result_type && type_requires_cast?(result_type)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def type_requires_cast?(type_declaration)
|
56
|
+
if type_declaration.is_a?(Manifest::Attributes)
|
57
|
+
return false unless type_declaration.has_attribute_declarations?
|
58
|
+
return false if type_declaration.attribute_declarations.empty?
|
59
|
+
|
60
|
+
type_declaration.attribute_declarations.values.any? do |attribute_declaration|
|
61
|
+
type_requires_cast?(attribute_declaration)
|
62
|
+
end
|
63
|
+
elsif type_declaration.is_a?(Manifest::Array)
|
64
|
+
element_type = type_declaration.element_type
|
65
|
+
element_type && type_requires_cast?(element_type)
|
66
|
+
else
|
67
|
+
return true if type_declaration.model?
|
68
|
+
|
69
|
+
type_symbol = type_declaration.type_symbol
|
70
|
+
|
71
|
+
if type_symbol == :date || type_symbol == :datetime
|
72
|
+
return true
|
73
|
+
end
|
74
|
+
|
75
|
+
if type_declaration.custom?
|
76
|
+
type_declaration = type_declaration.to_type if type_declaration.is_a?(Manifest::TypeDeclaration)
|
77
|
+
base_type = type_declaration.base_type
|
78
|
+
type_requires_cast?(base_type)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
46
82
|
end
|
47
83
|
end
|
48
84
|
end
|
@@ -3,7 +3,7 @@ require_relative "typescript_from_manifest_base_generator"
|
|
3
3
|
module Foobara
|
4
4
|
module RemoteGenerator
|
5
5
|
class Services
|
6
|
-
class CommandResultGenerator <
|
6
|
+
class CommandResultGenerator < CommandGenerator
|
7
7
|
alias command_manifest relevant_manifest
|
8
8
|
|
9
9
|
def result_type
|
@@ -18,10 +18,20 @@ module Foobara
|
|
18
18
|
"Command/Result.ts.erb"
|
19
19
|
end
|
20
20
|
|
21
|
-
def model_generators(
|
22
|
-
|
21
|
+
def model_generators(*args)
|
22
|
+
type, initial = if args.empty?
|
23
|
+
return @model_generators if defined?(@model_generators)
|
23
24
|
|
24
|
-
|
25
|
+
use_cache = true
|
26
|
+
|
27
|
+
[result_type, true]
|
28
|
+
else
|
29
|
+
args
|
30
|
+
end
|
31
|
+
|
32
|
+
if type.nil?
|
33
|
+
[]
|
34
|
+
elsif type.detached_entity?
|
25
35
|
generator_class = if atom?
|
26
36
|
if initial
|
27
37
|
AtomEntityGenerator
|
@@ -57,9 +67,15 @@ module Foobara
|
|
57
67
|
end.flatten.uniq
|
58
68
|
elsif type.array?
|
59
69
|
model_generators(type.element_type, false)
|
70
|
+
# rubocop:disable Lint/DuplicateBranch
|
60
71
|
else
|
61
72
|
# TODO: handle tuples, associative arrays
|
62
73
|
[]
|
74
|
+
# rubocop:enable Lint/DuplicateBranch
|
75
|
+
end.tap do |result|
|
76
|
+
if use_cache
|
77
|
+
@model_generators = result
|
78
|
+
end
|
63
79
|
end
|
64
80
|
end
|
65
81
|
|
@@ -16,16 +16,6 @@ module Foobara
|
|
16
16
|
self.name = name
|
17
17
|
self.dependencies = dependencies.to_set
|
18
18
|
|
19
|
-
dependencies.each do |dep|
|
20
|
-
if dep.belongs_to_dependency_group
|
21
|
-
# :nocov:
|
22
|
-
raise "Dependency group #{dep} already belongs to dependency group #{dep.dependency_group}"
|
23
|
-
# :nocov:
|
24
|
-
end
|
25
|
-
|
26
|
-
dep.belongs_to_dependency_group = self
|
27
|
-
end
|
28
|
-
|
29
19
|
find_collisions
|
30
20
|
end
|
31
21
|
|
@@ -58,7 +58,8 @@ module Foobara
|
|
58
58
|
end
|
59
59
|
|
60
60
|
def dependencies
|
61
|
-
[*command_generators, *model_generators, *entity_generators, *type_generators,
|
61
|
+
@dependencies ||= [*command_generators, *model_generators, *entity_generators, *type_generators,
|
62
|
+
*organization]
|
62
63
|
end
|
63
64
|
|
64
65
|
def domain_name
|
@@ -54,7 +54,7 @@ module Foobara
|
|
54
54
|
end
|
55
55
|
|
56
56
|
def dependencies
|
57
|
-
types_depended_on.select { |type| type.detached_entity? || type.custom? || type.model? }
|
57
|
+
@dependencies ||= types_depended_on.select { |type| type.detached_entity? || type.custom? || type.model? }
|
58
58
|
end
|
59
59
|
|
60
60
|
def ts_type_full_path
|
@@ -23,11 +23,11 @@ module Foobara
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def dependencies
|
26
|
-
if has_associations?
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
26
|
+
@dependencies ||= if has_associations?
|
27
|
+
[model_generator, atom_model_generator, aggregate_model_generator]
|
28
|
+
else
|
29
|
+
[model_generator]
|
30
|
+
end
|
31
31
|
end
|
32
32
|
end
|
33
33
|
end
|
@@ -5,15 +5,27 @@ module Foobara
|
|
5
5
|
class Services
|
6
6
|
class TypeGenerator < TypescriptFromManifestBaseGenerator
|
7
7
|
class << self
|
8
|
+
def lru_cache
|
9
|
+
@lru_cache ||= LruCache.new(1000)
|
10
|
+
end
|
11
|
+
|
8
12
|
def new(relevant_manifest)
|
9
|
-
|
13
|
+
unless self == TypeGenerator
|
14
|
+
generator = lru_cache.cached([self, relevant_manifest]) do
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
return generator
|
19
|
+
end
|
10
20
|
|
11
21
|
if relevant_manifest.detached_entity?
|
12
22
|
EntityGenerator.new(relevant_manifest)
|
13
23
|
elsif relevant_manifest.model?
|
14
24
|
ModelGenerator.new(relevant_manifest)
|
15
25
|
else
|
16
|
-
|
26
|
+
lru_cache.cached([self, relevant_manifest]) do
|
27
|
+
super
|
28
|
+
end
|
17
29
|
end
|
18
30
|
end
|
19
31
|
end
|
@@ -99,7 +111,7 @@ module Foobara
|
|
99
111
|
end
|
100
112
|
|
101
113
|
def dependencies
|
102
|
-
custom_type_generators + model_generators
|
114
|
+
@dependencies ||= custom_type_generators + model_generators
|
103
115
|
end
|
104
116
|
|
105
117
|
def type_name_downcase
|
@@ -36,10 +36,7 @@ module Foobara
|
|
36
36
|
when "Foobara::Auth::Logout"
|
37
37
|
Services::Auth::LogoutGenerator
|
38
38
|
when /\bGetCurrentUser$/
|
39
|
-
|
40
|
-
Services::Auth::RequiresAuthGenerator,
|
41
|
-
Services::Auth::SetupGenerator
|
42
|
-
]
|
39
|
+
Services::Auth::RequiresAuthGenerator
|
43
40
|
else
|
44
41
|
if manifest.requires_authentication?
|
45
42
|
Services::Auth::RequiresAuthGenerator
|
@@ -52,6 +49,7 @@ module Foobara
|
|
52
49
|
*generator_classes,
|
53
50
|
Services::CommandInputsGenerator,
|
54
51
|
Services::CommandResultGenerator,
|
52
|
+
Services::CommandCastResultGenerator,
|
55
53
|
Services::CommandErrorsGenerator,
|
56
54
|
Services::CommandErrorsIndexGenerator,
|
57
55
|
Services::CommandManifestGenerator
|
@@ -127,6 +125,8 @@ module Foobara
|
|
127
125
|
end
|
128
126
|
|
129
127
|
def dependency_roots
|
128
|
+
return @dependency_roots if defined?(@dependency_roots)
|
129
|
+
|
130
130
|
unless dependency_group
|
131
131
|
# :nocov:
|
132
132
|
raise "This generator was created without a " \
|
@@ -134,7 +134,7 @@ module Foobara
|
|
134
134
|
# :nocov:
|
135
135
|
end
|
136
136
|
|
137
|
-
dependency_group.non_colliding_dependency_roots.sort_by(&:scoped_full_name)
|
137
|
+
@dependency_roots = dependency_group.non_colliding_dependency_roots.sort_by(&:scoped_full_name)
|
138
138
|
end
|
139
139
|
|
140
140
|
def ts_instance_path
|
@@ -380,12 +380,7 @@ module Foobara
|
|
380
380
|
|
381
381
|
def path_to_root
|
382
382
|
path = super
|
383
|
-
|
384
|
-
if path.empty?
|
385
|
-
"./"
|
386
|
-
else
|
387
|
-
path
|
388
|
-
end
|
383
|
+
path.empty? ? "./" : path
|
389
384
|
end
|
390
385
|
end
|
391
386
|
end
|
@@ -59,7 +59,6 @@ module Foobara
|
|
59
59
|
|
60
60
|
def run_post_generation_tasks
|
61
61
|
eslint_fix
|
62
|
-
warn_about_adding_setup_to_index
|
63
62
|
end
|
64
63
|
|
65
64
|
def eslint_fix
|
@@ -74,24 +73,6 @@ module Foobara
|
|
74
73
|
end
|
75
74
|
end
|
76
75
|
end
|
77
|
-
|
78
|
-
def warn_about_adding_setup_to_index
|
79
|
-
if paths_to_source_code.key?("setup.ts")
|
80
|
-
index_tsx_path = "#{output_directory}/../index.tsx"
|
81
|
-
|
82
|
-
if File.exist?(index_tsx_path)
|
83
|
-
unless File.read(index_tsx_path) =~ /import.*domains\/setup/
|
84
|
-
warn "WARNING: you should add the following to src/index.tsx:\n\n" \
|
85
|
-
"import './domains/setup'"
|
86
|
-
end
|
87
|
-
else
|
88
|
-
# :nocov:
|
89
|
-
warn "WARNING: Make sure you add the following somewhere:\n\n" \
|
90
|
-
"import './domains/setup'"
|
91
|
-
# :nocov:
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
95
76
|
end
|
96
77
|
end
|
97
78
|
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
import Result from "./Result"
|
2
|
+
|
3
|
+
<% dependency_roots.each do |dependency_root| %>
|
4
|
+
import <%= dependency_root.import_destructure %> from "<%= path_to_root %><%= dependency_root.import_path %>"
|
5
|
+
<% end %>
|
6
|
+
|
7
|
+
export default function castJsonResult (json: any): Result {
|
8
|
+
<%= cast_json_result_function %>
|
9
|
+
}
|
data/templates/Command.ts.erb
CHANGED
@@ -3,9 +3,18 @@ import <%= base_class_name %> from "<%= path_to_root %><%= base_class_path %>"
|
|
3
3
|
import Inputs from "./Inputs"
|
4
4
|
import Result from "./Result"
|
5
5
|
import { Error } from "./Errors"
|
6
|
+
<% if result_json_requires_cast? %>
|
7
|
+
import castJsonResult from "./castJsonResult"
|
8
|
+
<% end %>
|
6
9
|
|
7
10
|
export class <%= command_name %> extends <%= base_class_name %><Inputs, Result, Error> {
|
8
11
|
static readonly organizationName = "<%= organization_name %>"
|
9
12
|
static readonly domainName = "<%= domain_name %>"
|
10
13
|
static readonly commandName = "<%= command_name %>"
|
14
|
+
|
15
|
+
<% if result_json_requires_cast? %>
|
16
|
+
castJsonResult(json: any): Result {
|
17
|
+
return castJsonResult(json)
|
18
|
+
}
|
19
|
+
<% end %>
|
11
20
|
}
|
@@ -5,6 +5,12 @@ import {
|
|
5
5
|
|
6
6
|
export class Loaded<%= entity_short_name %><T extends <%= entity_short_name %>AttributesType = <%= entity_short_name %>AttributesType> extends <%= entity_short_name %><T> {
|
7
7
|
readonly isLoaded: true = true
|
8
|
+
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
10
|
+
constructor(attributes: T) {
|
11
|
+
super(attributes)
|
12
|
+
}
|
13
|
+
|
8
14
|
<% unless has_associations? %>
|
9
15
|
/* eslint-disable @typescript-eslint/class-literal-property-style */
|
10
16
|
get isAtom (): true { return true }
|
@@ -1,18 +1,17 @@
|
|
1
1
|
import { Never } from "<%= path_to_root %>base/Entity"
|
2
2
|
import {
|
3
3
|
<%= entity_short_name %>,
|
4
|
-
|
4
|
+
<%= entity_short_name %>PrimaryKeyType,
|
5
5
|
<%= entity_short_name %>AttributesType
|
6
6
|
} from "./Ambiguous"
|
7
7
|
|
8
8
|
export type Unloaded<%= entity_short_name %>AttributesType = Never<<%= entity_short_name %>AttributesType>
|
9
9
|
|
10
10
|
export class Unloaded<%= entity_short_name %> extends <%= entity_short_name %><Unloaded<%= entity_short_name %>AttributesType> {
|
11
|
-
|
12
|
-
constructor(
|
13
|
-
super(
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
12
|
+
constructor(primaryKey: <%= entity_short_name %>PrimaryKeyType) {
|
13
|
+
super(primaryKey)
|
14
14
|
}
|
15
|
-
*/
|
16
15
|
|
17
16
|
readonly isLoaded: false = false
|
18
17
|
}
|
@@ -1,31 +1,25 @@
|
|
1
1
|
import type Query from '../../../base/Query'
|
2
2
|
import type RemoteCommand from '../../../base/RemoteCommand'
|
3
|
+
import { forEachQuery } from '../../../base/QueryCache'
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
}
|
9
|
-
|
10
|
-
function setCurrentUserDirty () {
|
11
|
-
console.log('setCurrentUserDirty')
|
12
|
-
if (getCurrentUserQuery != null) {
|
13
|
-
getCurrentUserQuery.setDirty()
|
14
|
-
}
|
5
|
+
function dirtyAllQueries () {
|
6
|
+
forEachQuery((query: Query<RemoteCommand<any, any, any>>) => {
|
7
|
+
query.setDirty()
|
8
|
+
})
|
15
9
|
}
|
16
10
|
|
17
11
|
const accessTokens = new Map<string, string>()
|
18
12
|
|
19
13
|
const logout = (urlBase: string): void => {
|
20
14
|
accessTokens.delete(urlBase)
|
21
|
-
|
15
|
+
dirtyAllQueries()
|
22
16
|
}
|
23
17
|
let handleLogout: (baseUrl: string) => void = logout
|
24
18
|
|
25
19
|
const tokenForUrl = (baseUrl: string): string | undefined => accessTokens.get(baseUrl)
|
26
20
|
const handleLogin: (baseUrl: string, accessToken: string) => void = (baseUrl, accessToken) => {
|
27
21
|
accessTokens.set(baseUrl, accessToken)
|
28
|
-
|
22
|
+
dirtyAllQueries()
|
29
23
|
}
|
30
24
|
|
31
25
|
if (typeof BroadcastChannel !== 'undefined') {
|
data/templates/base/Entity.ts
CHANGED
@@ -17,10 +17,22 @@ export abstract class Entity<PrimaryKeyType extends EntityPrimaryKeyType, Attrib
|
|
17
17
|
abstract get hasAssociations(): boolean
|
18
18
|
abstract get associationPropertyPaths (): string[][]
|
19
19
|
|
20
|
-
constructor(
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
constructor(primaryKeyOrAttributes: PrimaryKeyType | AttributesType) {
|
21
|
+
let attributes: AttributesType | undefined
|
22
|
+
|
23
|
+
const klass = new.target as typeof Entity<PrimaryKeyType, AttributesType>
|
24
|
+
|
25
|
+
if (typeof primaryKeyOrAttributes === "object") {
|
26
|
+
attributes = primaryKeyOrAttributes
|
27
|
+
super(attributes)
|
28
|
+
this.isLoaded = true
|
29
|
+
this.primaryKey = this.attributes[klass.primaryKeyAttributeName as keyof AttributesType] as PrimaryKeyType
|
30
|
+
} else {
|
31
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
32
|
+
super({[klass.primaryKeyAttributeName]: primaryKeyOrAttributes} as AttributesType)
|
33
|
+
this.isLoaded = false
|
34
|
+
this.primaryKey = primaryKeyOrAttributes as PrimaryKeyType
|
35
|
+
}
|
24
36
|
}
|
25
37
|
|
26
38
|
/* Can we make this work or not?
|
@@ -33,6 +33,10 @@ export function getQuery<CommandT extends RemoteCommand<any, any, any>> (
|
|
33
33
|
return query
|
34
34
|
}
|
35
35
|
|
36
|
+
export function forEachQuery (callback: (query: Query<RemoteCommand<any, any, any>>) => void): void {
|
37
|
+
queryCache.forEach(callback)
|
38
|
+
}
|
39
|
+
|
36
40
|
function toKey<CommandT extends RemoteCommand<any, any, any>> (
|
37
41
|
CommandClass: RemoteCommandConstructor<CommandT>,
|
38
42
|
inputs: InputsOf<CommandT> | undefined
|
@@ -101,13 +101,19 @@ export default abstract class RemoteCommand<Inputs, Result, CommandError extends
|
|
101
101
|
return await fetch(this._buildUrl(), this._buildRequestParams())
|
102
102
|
}
|
103
103
|
|
104
|
+
castJsonResult (json: any): Result {
|
105
|
+
return json
|
106
|
+
}
|
107
|
+
|
104
108
|
async _handleResponse (response: Response): Promise<Outcome<Result, CommandError>> {
|
105
109
|
const text = await response.text()
|
106
110
|
const body = JSON.parse(text)
|
107
111
|
|
108
112
|
if (response.ok) {
|
113
|
+
const result = this.castJsonResult(body)
|
114
|
+
|
115
|
+
this.outcome = new SuccessfulOutcome<Result, CommandError>(result)
|
109
116
|
this.commandState = 'succeeded'
|
110
|
-
this.outcome = new SuccessfulOutcome<Result, CommandError>(body)
|
111
117
|
} else if (response.status === 422 || response.status === 401 || response.status === 403) {
|
112
118
|
this.commandState = 'errored'
|
113
119
|
this.outcome = new ErrorOutcome<Result, CommandError>(body)
|
data/templates/hooks/useQuery.ts
CHANGED
@@ -57,19 +57,23 @@ export default function useQuery<CommandT extends RemoteCommand<any, any, any>>
|
|
57
57
|
queryRef.current = query
|
58
58
|
}
|
59
59
|
|
60
|
+
const [queryState, setQueryState] = useState<QueryState<CommandT>>(
|
61
|
+
queryToQueryState<CommandT>(query)
|
62
|
+
)
|
63
|
+
|
60
64
|
useEffect(() => {
|
61
|
-
|
62
|
-
|
65
|
+
if (query == null) {
|
66
|
+
return undefined
|
67
|
+
}
|
68
|
+
|
69
|
+
const removeListener = query.onChange(() => {
|
70
|
+
if (query != null) { // Just here to satisfy type check
|
63
71
|
setQueryState(queryToQueryState<CommandT>(query))
|
64
72
|
}
|
65
73
|
})
|
66
74
|
|
67
|
-
return
|
75
|
+
return removeListener
|
68
76
|
}, [query])
|
69
77
|
|
70
|
-
const [queryState, setQueryState] = useState<QueryState<CommandT>>(
|
71
|
-
queryToQueryState<CommandT>(query)
|
72
|
-
)
|
73
|
-
|
74
78
|
return queryState
|
75
79
|
}
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foobara-typescript-remote-command-generator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Miles Georgi
|
@@ -15,7 +15,7 @@ dependencies:
|
|
15
15
|
requirements:
|
16
16
|
- - ">="
|
17
17
|
- !ruby/object:Gem::Version
|
18
|
-
version: 0.1.
|
18
|
+
version: 0.1.16
|
19
19
|
- - "<"
|
20
20
|
- !ruby/object:Gem::Version
|
21
21
|
version: 2.0.0
|
@@ -25,7 +25,7 @@ dependencies:
|
|
25
25
|
requirements:
|
26
26
|
- - ">="
|
27
27
|
- !ruby/object:Gem::Version
|
28
|
-
version: 0.1.
|
28
|
+
version: 0.1.16
|
29
29
|
- - "<"
|
30
30
|
- !ruby/object:Gem::Version
|
31
31
|
version: 2.0.0
|
@@ -70,7 +70,7 @@ files:
|
|
70
70
|
- src/remote_generator/services/auth/refresh_login_generator.rb
|
71
71
|
- src/remote_generator/services/auth/requires_auth_command_generator.rb
|
72
72
|
- src/remote_generator/services/auth/requires_auth_generator.rb
|
73
|
-
- src/remote_generator/services/
|
73
|
+
- src/remote_generator/services/command_cast_result_generator.rb
|
74
74
|
- src/remote_generator/services/command_errors_generator.rb
|
75
75
|
- src/remote_generator/services/command_errors_index_generator.rb
|
76
76
|
- src/remote_generator/services/command_generator.rb
|
@@ -103,6 +103,7 @@ files:
|
|
103
103
|
- templates/Command/Errors.ts.erb
|
104
104
|
- templates/Command/Inputs.ts.erb
|
105
105
|
- templates/Command/Result.ts.erb
|
106
|
+
- templates/Command/castJsonResult.ts.erb
|
106
107
|
- templates/Command/errors/index.ts.erb
|
107
108
|
- templates/Domain.ts.erb
|
108
109
|
- templates/Domain/config.ts.erb
|
@@ -134,7 +135,6 @@ files:
|
|
134
135
|
- templates/base/RemoteCommand.ts
|
135
136
|
- templates/base/RemoteCommandTypes.ts
|
136
137
|
- templates/hooks/useQuery.ts
|
137
|
-
- templates/setup.ts.erb
|
138
138
|
homepage: https://github.com/foobara/typescript-remote-command-generator
|
139
139
|
licenses:
|
140
140
|
- Apache-2.0
|
@@ -1,25 +0,0 @@
|
|
1
|
-
module Foobara
|
2
|
-
module RemoteGenerator
|
3
|
-
class Services
|
4
|
-
module Auth
|
5
|
-
class SetupGenerator < CommandGenerator
|
6
|
-
def template_path
|
7
|
-
"setup.ts.erb"
|
8
|
-
end
|
9
|
-
|
10
|
-
def target_path
|
11
|
-
["setup.ts"]
|
12
|
-
end
|
13
|
-
|
14
|
-
def command_generator
|
15
|
-
generator_for(command_manifest)
|
16
|
-
end
|
17
|
-
|
18
|
-
def dependencies
|
19
|
-
[command_generator]
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
data/templates/setup.ts.erb
DELETED
@@ -1,6 +0,0 @@
|
|
1
|
-
import { getQuery } from './base/QueryCache'
|
2
|
-
import { setGetCurrentUserQuery } from './Foobara/Auth/utils/accessTokens'
|
3
|
-
|
4
|
-
import <%= command_generator.import_destructure %> from "<%= path_to_root %><%= command_generator.import_path %>"
|
5
|
-
|
6
|
-
setGetCurrentUserQuery(getQuery(<%= dependency_group.non_colliding_type(command_generator) %>, undefined))
|