rails-graphql 0.2.1 → 1.0.0.beta

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.
Files changed (297) hide show
  1. checksums.yaml +4 -4
  2. data/ext/console.rb +18 -0
  3. data/ext/extconf.h +3 -0
  4. data/ext/extconf.rb +1 -54
  5. data/ext/gql_parser.c +646 -0
  6. data/ext/shared.c +482 -0
  7. data/ext/shared.h +177 -0
  8. data/lib/gql_parser.so +0 -0
  9. data/lib/rails/graphql/adapters/mysql_adapter.rb +59 -0
  10. data/lib/rails/graphql/adapters/pg_adapter.rb +25 -22
  11. data/lib/rails/graphql/adapters/sqlite_adapter.rb +17 -14
  12. data/lib/rails/graphql/alternative/field_set.rb +36 -0
  13. data/lib/rails/graphql/alternative/mutation.rb +17 -0
  14. data/lib/rails/graphql/alternative/query.rb +93 -0
  15. data/lib/rails/graphql/alternative/subscription.rb +17 -0
  16. data/lib/rails/graphql/alternative.rb +20 -0
  17. data/lib/rails/graphql/argument.rb +21 -24
  18. data/lib/rails/graphql/callback.rb +24 -9
  19. data/lib/rails/graphql/collectors/hash_collector.rb +14 -6
  20. data/lib/rails/graphql/collectors/idented_collector.rb +10 -7
  21. data/lib/rails/graphql/collectors/json_collector.rb +22 -17
  22. data/lib/rails/graphql/collectors.rb +4 -4
  23. data/lib/rails/graphql/config.rb +130 -15
  24. data/lib/rails/graphql/directive/cached_directive.rb +33 -0
  25. data/lib/rails/graphql/directive/deprecated_directive.rb +10 -10
  26. data/lib/rails/graphql/directive/include_directive.rb +5 -4
  27. data/lib/rails/graphql/directive/skip_directive.rb +5 -4
  28. data/lib/rails/graphql/directive.rb +118 -63
  29. data/lib/rails/graphql/errors.rb +33 -4
  30. data/lib/rails/graphql/event.rb +16 -5
  31. data/lib/rails/graphql/field/authorized_field.rb +19 -3
  32. data/lib/rails/graphql/field/input_field.rb +11 -10
  33. data/lib/rails/graphql/field/mutation_field.rb +42 -7
  34. data/lib/rails/graphql/field/output_field.rb +102 -13
  35. data/lib/rails/graphql/field/proxied_field.rb +31 -22
  36. data/lib/rails/graphql/field/resolved_field.rb +26 -24
  37. data/lib/rails/graphql/field/scoped_config.rb +10 -4
  38. data/lib/rails/graphql/field/subscription_field.rb +140 -0
  39. data/lib/rails/graphql/field/typed_field.rb +43 -22
  40. data/lib/rails/graphql/field.rb +70 -56
  41. data/lib/rails/graphql/global_id.rb +85 -0
  42. data/lib/rails/graphql/helpers/attribute_delegator.rb +5 -5
  43. data/lib/rails/graphql/helpers/inherited_collection/array.rb +50 -0
  44. data/lib/rails/graphql/helpers/inherited_collection/base.rb +43 -0
  45. data/lib/rails/graphql/helpers/inherited_collection/hash.rb +87 -0
  46. data/lib/rails/graphql/helpers/inherited_collection.rb +25 -76
  47. data/lib/rails/graphql/helpers/instantiable.rb +15 -0
  48. data/lib/rails/graphql/helpers/leaf_from_ar.rb +7 -7
  49. data/lib/rails/graphql/helpers/registerable.rb +44 -62
  50. data/lib/rails/graphql/helpers/unregisterable.rb +16 -0
  51. data/lib/rails/graphql/helpers/with_arguments.rb +31 -27
  52. data/lib/rails/graphql/helpers/with_assignment.rb +6 -6
  53. data/lib/rails/graphql/helpers/with_callbacks.rb +25 -8
  54. data/lib/rails/graphql/helpers/with_description.rb +71 -0
  55. data/lib/rails/graphql/helpers/with_directives.rb +54 -30
  56. data/lib/rails/graphql/helpers/with_events.rb +21 -23
  57. data/lib/rails/graphql/helpers/with_fields.rb +76 -22
  58. data/lib/rails/graphql/helpers/with_global_id.rb +22 -0
  59. data/lib/rails/graphql/helpers/with_name.rb +43 -0
  60. data/lib/rails/graphql/helpers/with_namespace.rb +7 -4
  61. data/lib/rails/graphql/helpers/with_owner.rb +8 -7
  62. data/lib/rails/graphql/helpers/with_schema_fields.rb +137 -55
  63. data/lib/rails/graphql/helpers/with_validator.rb +9 -9
  64. data/lib/rails/graphql/helpers.rb +10 -3
  65. data/lib/rails/graphql/introspection.rb +43 -36
  66. data/lib/rails/graphql/railtie.rb +88 -33
  67. data/lib/rails/graphql/railties/base_generator.rb +3 -9
  68. data/lib/rails/graphql/railties/channel.rb +157 -0
  69. data/lib/rails/graphql/railties/controller.rb +62 -17
  70. data/lib/rails/graphql/railties/controller_runtime.rb +5 -5
  71. data/lib/rails/graphql/railties/log_subscriber.rb +81 -14
  72. data/lib/rails/graphql/request/arguments.rb +24 -49
  73. data/lib/rails/graphql/request/backtrace.rb +191 -0
  74. data/lib/rails/graphql/request/component/field.rb +86 -65
  75. data/lib/rails/graphql/request/component/fragment.rb +72 -24
  76. data/lib/rails/graphql/request/component/operation/subscription.rb +164 -4
  77. data/lib/rails/graphql/request/component/operation.rb +63 -31
  78. data/lib/rails/graphql/request/component/spread.rb +68 -25
  79. data/lib/rails/graphql/request/component/typename.rb +27 -12
  80. data/lib/rails/graphql/request/component.rb +75 -36
  81. data/lib/rails/graphql/request/context.rb +18 -8
  82. data/lib/rails/graphql/request/errors.rb +16 -6
  83. data/lib/rails/graphql/request/event.rb +19 -8
  84. data/lib/rails/graphql/request/helpers/directives.rb +68 -27
  85. data/lib/rails/graphql/request/helpers/selection_set.rb +51 -25
  86. data/lib/rails/graphql/request/helpers/value_writers.rb +18 -16
  87. data/lib/rails/graphql/request/prepared_data.rb +98 -0
  88. data/lib/rails/graphql/request/steps/authorizable.rb +24 -14
  89. data/lib/rails/graphql/request/steps/organizable.rb +110 -48
  90. data/lib/rails/graphql/request/steps/{prepareable.rb → preparable.rb} +20 -7
  91. data/lib/rails/graphql/request/steps/{resolveable.rb → resolvable.rb} +15 -6
  92. data/lib/rails/graphql/request/strategy/cached_strategy.rb +64 -0
  93. data/lib/rails/graphql/request/strategy/dynamic_instance.rb +6 -6
  94. data/lib/rails/graphql/request/strategy/multi_query_strategy.rb +6 -13
  95. data/lib/rails/graphql/request/strategy/sequenced_strategy.rb +9 -9
  96. data/lib/rails/graphql/request/strategy.rb +131 -75
  97. data/lib/rails/graphql/request/subscription.rb +80 -0
  98. data/lib/rails/graphql/request.rb +305 -86
  99. data/lib/rails/graphql/schema.rb +240 -48
  100. data/lib/rails/graphql/shortcuts.rb +22 -3
  101. data/lib/rails/graphql/source/active_record/builders.rb +49 -35
  102. data/lib/rails/graphql/source/active_record_source.rb +70 -54
  103. data/lib/rails/graphql/source/base.rb +111 -0
  104. data/lib/rails/graphql/source/builder.rb +128 -0
  105. data/lib/rails/graphql/source/scoped_arguments.rb +31 -19
  106. data/lib/rails/graphql/source.rb +89 -213
  107. data/lib/rails/graphql/subscription/provider/action_cable.rb +112 -0
  108. data/lib/rails/graphql/subscription/provider/base.rb +191 -0
  109. data/lib/rails/graphql/subscription/provider.rb +18 -0
  110. data/lib/rails/graphql/subscription/store/base.rb +145 -0
  111. data/lib/rails/graphql/subscription/store/memory.rb +127 -0
  112. data/lib/rails/graphql/subscription/store.rb +19 -0
  113. data/lib/rails/graphql/subscription.rb +17 -0
  114. data/lib/rails/graphql/to_gql.rb +29 -32
  115. data/lib/rails/graphql/type/enum/directive_location_enum.rb +11 -11
  116. data/lib/rails/graphql/type/enum/type_kind_enum.rb +3 -3
  117. data/lib/rails/graphql/type/enum.rb +34 -48
  118. data/lib/rails/graphql/type/input.rb +74 -23
  119. data/lib/rails/graphql/type/interface.rb +16 -26
  120. data/lib/rails/graphql/type/object/directive_object.rb +4 -4
  121. data/lib/rails/graphql/type/object/enum_value_object.rb +3 -3
  122. data/lib/rails/graphql/type/object/field_object.rb +24 -6
  123. data/lib/rails/graphql/type/object/input_value_object.rb +3 -3
  124. data/lib/rails/graphql/type/object/schema_object.rb +5 -8
  125. data/lib/rails/graphql/type/object/type_object.rb +29 -19
  126. data/lib/rails/graphql/type/object.rb +26 -23
  127. data/lib/rails/graphql/type/scalar/any_scalar.rb +30 -0
  128. data/lib/rails/graphql/type/scalar/bigint_scalar.rb +5 -5
  129. data/lib/rails/graphql/type/scalar/binary_scalar.rb +3 -3
  130. data/lib/rails/graphql/type/scalar/boolean_scalar.rb +8 -8
  131. data/lib/rails/graphql/type/scalar/date_scalar.rb +3 -3
  132. data/lib/rails/graphql/type/scalar/date_time_scalar.rb +3 -3
  133. data/lib/rails/graphql/type/scalar/decimal_scalar.rb +3 -3
  134. data/lib/rails/graphql/type/scalar/float_scalar.rb +5 -5
  135. data/lib/rails/graphql/type/scalar/id_scalar.rb +6 -5
  136. data/lib/rails/graphql/type/scalar/int_scalar.rb +6 -5
  137. data/lib/rails/graphql/type/scalar/json_scalar.rb +39 -0
  138. data/lib/rails/graphql/type/scalar/string_scalar.rb +18 -4
  139. data/lib/rails/graphql/type/scalar/time_scalar.rb +5 -5
  140. data/lib/rails/graphql/type/scalar.rb +25 -22
  141. data/lib/rails/graphql/type/union.rb +14 -16
  142. data/lib/rails/graphql/type.rb +34 -25
  143. data/lib/rails/graphql/type_map.rb +256 -164
  144. data/lib/rails/graphql/uri.rb +166 -0
  145. data/lib/rails/graphql/version.rb +15 -3
  146. data/lib/rails/graphql.rake +3 -0
  147. data/lib/rails/graphql.rb +85 -52
  148. data/lib/rails-graphql.rb +1 -1
  149. data/test/assets/en.yml +29 -0
  150. data/test/assets/introspection-mem.txt +1 -1
  151. data/test/assets/mem.gql +18 -45
  152. data/test/assets/mysql.gql +392 -0
  153. data/test/assets/sqlite.gql +21 -12
  154. data/test/assets/translate.gql +335 -0
  155. data/test/config.rb +18 -8
  156. data/test/graphql/schema_test.rb +12 -19
  157. data/test/graphql/source_test.rb +8 -75
  158. data/test/graphql/type/enum_test.rb +207 -203
  159. data/test/graphql/type/input_test.rb +14 -9
  160. data/test/graphql/type/interface_test.rb +4 -4
  161. data/test/graphql/type/scalar/any_scalar_test.rb +38 -0
  162. data/test/graphql/type/scalar/boolean_scalar_test.rb +6 -3
  163. data/test/graphql/type/scalar/json_scalar_test.rb +23 -0
  164. data/test/graphql/type_map_test.rb +51 -66
  165. data/test/graphql/type_test.rb +0 -19
  166. data/test/graphql_test.rb +1 -1
  167. data/test/integration/{authorization/authorization_test.rb → authorization_test.rb} +40 -14
  168. data/test/integration/config.rb +36 -3
  169. data/test/integration/customization_test.rb +39 -0
  170. data/test/integration/global_id_test.rb +99 -0
  171. data/test/integration/memory/star_wars_introspection_test.rb +24 -16
  172. data/test/integration/memory/star_wars_query_test.rb +54 -3
  173. data/test/integration/memory/star_wars_validation_test.rb +1 -1
  174. data/test/integration/mysql/star_wars_introspection_test.rb +25 -0
  175. data/test/integration/persisted_query_test.rb +87 -0
  176. data/test/integration/resolver_precedence_test.rb +154 -0
  177. data/test/integration/schemas/memory.rb +22 -7
  178. data/test/integration/schemas/mysql.rb +62 -0
  179. data/test/integration/schemas/sqlite.rb +21 -12
  180. data/test/integration/sqlite/star_wars_global_id_test.rb +83 -0
  181. data/test/integration/sqlite/star_wars_introspection_test.rb +10 -0
  182. data/test/integration/sqlite/star_wars_query_test.rb +14 -1
  183. data/test/integration/translate_test.rb +61 -0
  184. data/test/test_ext.rb +16 -13
  185. metadata +108 -157
  186. data/ext/depend +0 -3
  187. data/ext/graphqlparser/Ast.cpp +0 -346
  188. data/ext/graphqlparser/Ast.h +0 -1214
  189. data/ext/graphqlparser/AstNode.h +0 -36
  190. data/ext/graphqlparser/AstVisitor.h +0 -137
  191. data/ext/graphqlparser/GraphQLParser.cpp +0 -76
  192. data/ext/graphqlparser/GraphQLParser.h +0 -55
  193. data/ext/graphqlparser/JsonVisitor.cpp +0 -161
  194. data/ext/graphqlparser/JsonVisitor.cpp.inc +0 -456
  195. data/ext/graphqlparser/JsonVisitor.h +0 -121
  196. data/ext/graphqlparser/JsonVisitor.h.inc +0 -110
  197. data/ext/graphqlparser/VERSION +0 -1
  198. data/ext/graphqlparser/c/GraphQLAst.cpp +0 -324
  199. data/ext/graphqlparser/c/GraphQLAst.h +0 -180
  200. data/ext/graphqlparser/c/GraphQLAstForEachConcreteType.h +0 -44
  201. data/ext/graphqlparser/c/GraphQLAstNode.cpp +0 -25
  202. data/ext/graphqlparser/c/GraphQLAstNode.h +0 -33
  203. data/ext/graphqlparser/c/GraphQLAstToJSON.cpp +0 -21
  204. data/ext/graphqlparser/c/GraphQLAstToJSON.h +0 -24
  205. data/ext/graphqlparser/c/GraphQLAstVisitor.cpp +0 -55
  206. data/ext/graphqlparser/c/GraphQLAstVisitor.h +0 -53
  207. data/ext/graphqlparser/c/GraphQLParser.cpp +0 -35
  208. data/ext/graphqlparser/c/GraphQLParser.h +0 -54
  209. data/ext/graphqlparser/dump_json_ast.cpp +0 -48
  210. data/ext/graphqlparser/lexer.lpp +0 -324
  211. data/ext/graphqlparser/parser.ypp +0 -693
  212. data/ext/graphqlparser/parsergen/lexer.cpp +0 -2633
  213. data/ext/graphqlparser/parsergen/lexer.h +0 -528
  214. data/ext/graphqlparser/parsergen/location.hh +0 -189
  215. data/ext/graphqlparser/parsergen/parser.tab.cpp +0 -3300
  216. data/ext/graphqlparser/parsergen/parser.tab.hpp +0 -646
  217. data/ext/graphqlparser/parsergen/position.hh +0 -179
  218. data/ext/graphqlparser/parsergen/stack.hh +0 -156
  219. data/ext/graphqlparser/syntaxdefs.h +0 -19
  220. data/ext/libgraphqlparser/AstNode.h +0 -36
  221. data/ext/libgraphqlparser/CMakeLists.txt +0 -148
  222. data/ext/libgraphqlparser/CONTRIBUTING.md +0 -23
  223. data/ext/libgraphqlparser/GraphQLParser.cpp +0 -76
  224. data/ext/libgraphqlparser/GraphQLParser.h +0 -55
  225. data/ext/libgraphqlparser/JsonVisitor.cpp +0 -161
  226. data/ext/libgraphqlparser/JsonVisitor.h +0 -121
  227. data/ext/libgraphqlparser/LICENSE +0 -22
  228. data/ext/libgraphqlparser/README.clang-tidy +0 -7
  229. data/ext/libgraphqlparser/README.md +0 -84
  230. data/ext/libgraphqlparser/ast/ast.ast +0 -203
  231. data/ext/libgraphqlparser/ast/ast.py +0 -61
  232. data/ext/libgraphqlparser/ast/c.py +0 -100
  233. data/ext/libgraphqlparser/ast/c.pyc +0 -0
  234. data/ext/libgraphqlparser/ast/c_impl.py +0 -61
  235. data/ext/libgraphqlparser/ast/c_impl.pyc +0 -0
  236. data/ext/libgraphqlparser/ast/c_visitor_impl.py +0 -39
  237. data/ext/libgraphqlparser/ast/c_visitor_impl.pyc +0 -0
  238. data/ext/libgraphqlparser/ast/casing.py +0 -26
  239. data/ext/libgraphqlparser/ast/casing.pyc +0 -0
  240. data/ext/libgraphqlparser/ast/cxx.py +0 -197
  241. data/ext/libgraphqlparser/ast/cxx.pyc +0 -0
  242. data/ext/libgraphqlparser/ast/cxx_impl.py +0 -61
  243. data/ext/libgraphqlparser/ast/cxx_impl.pyc +0 -0
  244. data/ext/libgraphqlparser/ast/cxx_json_visitor_header.py +0 -42
  245. data/ext/libgraphqlparser/ast/cxx_json_visitor_header.pyc +0 -0
  246. data/ext/libgraphqlparser/ast/cxx_json_visitor_impl.py +0 -80
  247. data/ext/libgraphqlparser/ast/cxx_json_visitor_impl.pyc +0 -0
  248. data/ext/libgraphqlparser/ast/cxx_visitor.py +0 -64
  249. data/ext/libgraphqlparser/ast/cxx_visitor.pyc +0 -0
  250. data/ext/libgraphqlparser/ast/js.py +0 -65
  251. data/ext/libgraphqlparser/ast/license.py +0 -10
  252. data/ext/libgraphqlparser/ast/license.pyc +0 -0
  253. data/ext/libgraphqlparser/c/GraphQLAstNode.cpp +0 -25
  254. data/ext/libgraphqlparser/c/GraphQLAstNode.h +0 -33
  255. data/ext/libgraphqlparser/c/GraphQLAstToJSON.cpp +0 -21
  256. data/ext/libgraphqlparser/c/GraphQLAstToJSON.h +0 -24
  257. data/ext/libgraphqlparser/c/GraphQLAstVisitor.cpp +0 -55
  258. data/ext/libgraphqlparser/c/GraphQLAstVisitor.h +0 -53
  259. data/ext/libgraphqlparser/c/GraphQLParser.cpp +0 -35
  260. data/ext/libgraphqlparser/c/GraphQLParser.h +0 -54
  261. data/ext/libgraphqlparser/clang-tidy-all.sh +0 -3
  262. data/ext/libgraphqlparser/cmake/version.cmake +0 -16
  263. data/ext/libgraphqlparser/dump_json_ast.cpp +0 -48
  264. data/ext/libgraphqlparser/go/README.md +0 -20
  265. data/ext/libgraphqlparser/go/callbacks.go +0 -18
  266. data/ext/libgraphqlparser/go/gotest.go +0 -64
  267. data/ext/libgraphqlparser/lexer.lpp +0 -324
  268. data/ext/libgraphqlparser/libgraphqlparser.pc.in +0 -11
  269. data/ext/libgraphqlparser/parser.ypp +0 -693
  270. data/ext/libgraphqlparser/parsergen/lexer.cpp +0 -2633
  271. data/ext/libgraphqlparser/parsergen/lexer.h +0 -528
  272. data/ext/libgraphqlparser/parsergen/location.hh +0 -189
  273. data/ext/libgraphqlparser/parsergen/parser.tab.cpp +0 -3300
  274. data/ext/libgraphqlparser/parsergen/parser.tab.hpp +0 -646
  275. data/ext/libgraphqlparser/parsergen/position.hh +0 -179
  276. data/ext/libgraphqlparser/parsergen/stack.hh +0 -156
  277. data/ext/libgraphqlparser/python/CMakeLists.txt +0 -14
  278. data/ext/libgraphqlparser/python/README.md +0 -5
  279. data/ext/libgraphqlparser/python/example.py +0 -31
  280. data/ext/libgraphqlparser/syntaxdefs.h +0 -19
  281. data/ext/libgraphqlparser/test/BuildCAPI.c +0 -5
  282. data/ext/libgraphqlparser/test/CMakeLists.txt +0 -25
  283. data/ext/libgraphqlparser/test/JsonVisitorTests.cpp +0 -28
  284. data/ext/libgraphqlparser/test/ParserTests.cpp +0 -352
  285. data/ext/libgraphqlparser/test/kitchen-sink.graphql +0 -59
  286. data/ext/libgraphqlparser/test/kitchen-sink.json +0 -1
  287. data/ext/libgraphqlparser/test/schema-kitchen-sink.graphql +0 -78
  288. data/ext/libgraphqlparser/test/schema-kitchen-sink.json +0 -1
  289. data/ext/libgraphqlparser/test/valgrind.supp +0 -33
  290. data/ext/version.cpp +0 -21
  291. data/lib/graphqlparser.so +0 -0
  292. data/lib/rails/graphql/native/functions.rb +0 -38
  293. data/lib/rails/graphql/native/location.rb +0 -41
  294. data/lib/rails/graphql/native/pointers.rb +0 -23
  295. data/lib/rails/graphql/native/visitor.rb +0 -349
  296. data/lib/rails/graphql/native.rb +0 -56
  297. data/test/integration/schemas/authorization.rb +0 -12
@@ -3,8 +3,8 @@
3
3
  require 'concurrent/map'
4
4
  require 'active_support/core_ext/class/subclasses'
5
5
 
6
- module Rails # :nodoc:
7
- module GraphQL # :nodoc:
6
+ module Rails
7
+ module GraphQL
8
8
  # = GraphQL Type Map
9
9
  #
10
10
  # Inspired by ActiveRecord::Type::TypeMap, this class stores all the things
@@ -19,26 +19,42 @@ module Rails # :nodoc:
19
19
  class TypeMap
20
20
  FILTER_REGISTER_TRACE = /((inherited|initialize)'$|schema\.rb:\d+)/.freeze
21
21
 
22
- # Store all the base classes and if they were eager loaded by the type map
23
- # Be aware of the order because hard reset is based in this order
24
- mattr_accessor :base_classes, instance_writer: false, default: {
25
- Type: false,
26
- Directive: false,
27
- Schema: false,
28
- }
22
+ NsList = Class.new(Set)
29
23
 
30
- def self.loaded!(base_class)
31
- base_classes[base_class] = true
24
+ # Store all the base classes that are managed by the Type Map
25
+ mattr_accessor :base_classes, instance_writer: false,
26
+ default: Set.new(%i[Type Directive Schema])
27
+
28
+ # Get the current version of the Type Map. On each reset, the version is
29
+ # changed and can be used to invalidate cache and similar things
30
+ def version
31
+ @version ||= GraphQL.config.version&.first(8) || SecureRandom.hex(8)
32
32
  end
33
33
 
34
34
  # Reset the state of the type mapper
35
35
  def reset!
36
36
  @objects = 0 # Number of types and directives defined
37
-
38
- @pending = []
39
- @callbacks = Hash.new { |h, k| h[k] = [] }
37
+ @version = nil # Make sure to not keep the same version
40
38
  @skip_register = nil
41
39
 
40
+ @pending = Concurrent::Array.new
41
+ @reported_fallbacks = Concurrent::Set.new
42
+
43
+ # Initialize the callbacks
44
+ @callbacks = Concurrent::Map.new do |hc, key|
45
+ hc.fetch_or_store(key, Concurrent::Array.new)
46
+ end
47
+
48
+ # Initialize the dependencies
49
+ @dependencies = Concurrent::Map.new do |hd, key|
50
+ hd.fetch_or_store(key, Concurrent::Array.new)
51
+ end
52
+
53
+ # A registered list of modules and to which namespaces they are
54
+ # associated with
55
+ @module_namespaces = Concurrent::Map.new
56
+
57
+ # Initialize the index structure
42
58
  @index = Concurrent::Map.new do |h1, key1| # Namespaces
43
59
  base_class = Concurrent::Map.new do |h2, key2| # Base classes
44
60
  ensure_base_class!(key2)
@@ -48,141 +64,76 @@ module Rails # :nodoc:
48
64
  h1.fetch_or_store(key1, base_class)
49
65
  end
50
66
 
51
- @checkpoint.map(&:register!) if defined?(@checkpoint)
67
+ # Provide the first dependencies
68
+ seed_dependencies!
52
69
  end
53
70
 
54
71
  alias initialize reset!
55
72
 
56
- # This will do a full reset of the type map, re-registering all the
57
- # descendant classes for all the base classes
58
- def hard_reset!
59
- remove_instance_variable(:@checkpoint) if defined?(@checkpoint)
60
-
61
- reset!
62
- base_classes.each_key do |base_class|
63
- GraphQL.const_get(base_class).descendants.each(&:register!)
64
- end
65
- end
66
-
67
- # Save or restore a checkpoint that can the type map can be reseted to
68
- # TODO: With hard reset, we might not need checkpoint anymore
69
- def use_checkpoint!
70
- return reset! if defined?(@checkpoint)
71
-
72
- register_pending!
73
- @checkpoint = objects
73
+ # Add a list of dependencies to the type map, so it can lazy load them
74
+ def add_dependencies(*list, to:)
75
+ @dependencies[to].concat(list.flatten.compact)
74
76
  end
75
77
 
76
- # Get the list of all registred objects
77
- def objects(base_classes: nil, namespaces: nil)
78
- (Array.wrap(namespaces).presence || @index.keys).map do |namespace|
79
- (Array.wrap(base_classes).presence || @index[namespace].keys).map do |base_class|
80
- @index[namespace][base_class].values.map(&:call) \
81
- if @index[namespace].key?(base_class)
82
- end if @index.key?(namespace)
83
- end.flatten.compact.uniq.select do |obj|
84
- obj.respond_to?(:register!)
85
- end
78
+ # Mark the given object to be registered later, when a fetch is triggered
79
+ # TODO: Improve this with a Backtrace Cleaner
80
+ def postpone_registration(object)
81
+ source = caller(3).find { |item| item !~ FILTER_REGISTER_TRACE }
82
+ @pending << [object, source]
86
83
  end
87
84
 
88
- # Same as +fetch+ but it will raise an exception or retry depending if the
89
- # base type was already loaded or not
90
- def fetch!(*args, base_class: :Type, **xargs)
91
- xargs[:base_class] = base_class
92
-
93
- result = fetch(*args, **xargs)
94
- return result unless result.nil?
95
-
96
- raise ArgumentError, <<~MSG.squish if base_classes[base_class]
97
- Unable to find #{args.first.inspect} #{base_class} object.
98
- MSG
99
-
100
- GraphQL.const_get(base_class).eager_load!
101
- fetch!(*args, **xargs)
85
+ # Associate the given +module+ to a given +namespace+. If registered
86
+ # objects have no namespaces, but its +module_parents+ have been
87
+ # associated, then use the value
88
+ # TODO: Maybe turn this into a 1-to-Many association
89
+ def associate(namespace, mod)
90
+ @module_namespaces[mod] = namespace
102
91
  end
103
92
 
104
- # Find the given key or name inside the base class either on the given
105
- # namespace or in the base +:base+ namespace
106
- def fetch(key_or_name, prevent_register: nil, **xargs)
107
- if prevent_register != true
108
- skip_register << Array.wrap(prevent_register)
109
- register_pending!
110
- end
111
-
112
- namespaces = xargs[:namespaces]
113
- namespaces = namespaces.is_a?(Set) ? namespaces.to_a : Array.wrap(namespaces)
114
- namespaces += [:base] unless xargs.fetch(:exclusive, false)
115
-
116
- possibilities = Array.wrap(key_or_name)
117
- possibilities += Array.wrap(xargs[:fallback]) if xargs.key?(:fallback)
118
-
119
- namespaces.uniq.find do |namespace|
120
- possibilities.find do |item|
121
- result = dig(namespace, xargs.fetch(:base_class, :Type), item)
122
- return result.call unless result.nil?
123
- end
93
+ # Grab all the +module_parents+ from the object and try to return the
94
+ # first matching result
95
+ def associated_namespace_of(object)
96
+ return if @module_namespaces.empty?
97
+ object.module_parents.find do |mod|
98
+ ns = @module_namespaces[mod]
99
+ break ns unless ns.nil?
124
100
  end
125
- ensure
126
- skip_register.pop
127
- end
128
-
129
- # Checks if a given key or name is already defined under the same base
130
- # class and namespace. If +exclusive+ is set to +false+, then it won't
131
- # check the +:base+ namespace when not found on the given namespace.
132
- def exist?(name_or_key, base_class: :Type, namespaces: :base, exclusive: false)
133
- namespaces = namespaces.is_a?(Set) ? namespaces.to_a : Array.wrap(namespaces)
134
- namespaces += [:base] unless exclusive
135
- namespaces.uniq.any? { |namespace| dig(namespace, base_class, name_or_key).present? }
136
- end
137
-
138
- # Find if a given object is already defined. If +exclusive+ is set to
139
- # +false+, then it won't check the +:base+ namespace
140
- def object_exist?(object, **xargs)
141
- xargs[:base_class] = find_base_class(object)
142
- xargs[:namespaces] ||= object.namespaces
143
- exist?(object, **xargs)
144
- end
145
-
146
- # Mark the given object to be registered later, when a fetch is triggered
147
- # TODO: Improve this with a Backtracer Cleaner
148
- def postpone_registration(object)
149
- source = caller(3).find { |item| item !~ FILTER_REGISTER_TRACE }
150
- @pending << [object, source]
151
101
  end
152
102
 
153
103
  # Register a given object, which must be a class where the namespaces and
154
104
  # the base class can be inferred
155
105
  def register(object)
156
- namespaces = object.namespaces.dup
157
- namespaces = namespaces.is_a?(Set) ? namespaces.to_a : Array.wrap(namespaces)
106
+ namespaces = sanitize_namespaces(namespaces: object.namespaces, exclusive: true)
107
+ namespaces << :base if namespaces.empty?
158
108
 
159
- base_namespace = namespaces.shift || :base
160
109
  base_class = find_base_class(object)
161
110
  ensure_base_class!(base_class)
162
111
 
163
112
  # Cache the name, the key, and the alias proc
113
+ object_base = namespaces.first
164
114
  object_name = object.gql_name
165
115
  object_key = object.to_sym
166
116
  alias_proc = -> do
167
- fetch(object_key,
168
- base_class: base_class,
169
- namespaces: base_namespace,
170
- exclusive: true,
171
- )
117
+ fetch(object_key, base_class: base_class, namespaces: object_base, exclusive: true)
172
118
  end
173
119
 
174
- # Register the main type object
175
- add(base_namespace, base_class, object_key, -> { object })
120
+ # TODO Warn when the base key is being assigned to a different object
121
+ # Register the main type object for both key and name
122
+ add(object_base, base_class, object_key, object)
123
+ add(object_base, base_class, object_name, alias_proc)
176
124
 
177
125
  # Register all the aliases plus the object name
178
- aliases = object.try(:aliases) || []
179
- [object_name, *aliases].each do |alias_name|
180
- add(base_namespace, base_class, alias_name, alias_proc)
126
+ aliases = object.try(:aliases)
127
+ aliases&.each do |alias_name|
128
+ add(object_base, base_class, alias_name, alias_proc)
181
129
  end
182
130
 
183
131
  # For each remaining namespace, register a key and a name alias
184
- namespaces.product([object_key, object_name, *aliases]) do |(namespace, key_or_name)|
185
- add(namespace, base_class, key_or_name, alias_proc)
132
+ if namespaces.size > 1
133
+ keys_and_names = [object_key, object_name, *aliases]
134
+ namespaces.drop(1).product(keys_and_names) do |(namespace, key_or_name)|
135
+ add(namespace, base_class, key_or_name, alias_proc)
136
+ end
186
137
  end
187
138
 
188
139
  # Return the object for chain purposes
@@ -190,22 +141,32 @@ module Rails # :nodoc:
190
141
  object
191
142
  end
192
143
 
144
+ # Unregister all the provided objects by simply assigning nil to their
145
+ # final value on the index
146
+ def unregister(*objects)
147
+ objects.each do |object|
148
+ namespaces = sanitize_namespaces(namespaces: object.namespaces, exclusive: true)
149
+ namespaces << :base if namespaces.empty?
150
+ base_class = find_base_class(object)
151
+
152
+ @objects -= 1
153
+ @index[namespaces.first][base_class][object.to_sym] = nil
154
+ end
155
+ end
156
+
193
157
  # Register an item alias. Either provide a block that trigger the fetch
194
158
  # method to return that item, or a key from the same namespace and base
195
159
  # class
196
160
  def register_alias(name_or_key, key = nil, **xargs, &block)
197
- raise ArgumentError, <<~MSG.squish unless key.nil? ^ block.nil?
161
+ raise ArgumentError, (+<<~MSG).squish unless key.nil? ^ block.nil?
198
162
  Provide either a key or a block in order to register an alias.
199
163
  MSG
200
164
 
201
165
  base_class = xargs.delete(:base_class) || :Type
202
166
  ensure_base_class!(base_class)
203
167
 
204
- namespaces = xargs.delete(:namespaces) || []
205
- namespaces = namespaces.to_a if namespaces.is_a?(Set)
206
- namespaces << xargs.delete(:namespace)
207
-
208
- namespaces = namespaces.compact.presence || [:base]
168
+ namespaces = sanitize_namespaces(**xargs, exclusive: true)
169
+ namespaces << :base if namespaces.empty?
209
170
 
210
171
  block ||= -> do
211
172
  fetch(key, base_class: base_class, namespaces: namespaces, exclusive: true)
@@ -214,57 +175,131 @@ module Rails # :nodoc:
214
175
  namespaces.each { |ns| add(ns, base_class, name_or_key, block) }
215
176
  end
216
177
 
178
+ # Same as +fetch+ but it will raise an exception or retry depending if the
179
+ # base type was already loaded or not
180
+ def fetch!(key_or_name, base_class: :Type, fallback: nil, **xargs)
181
+ xargs[:base_class] = base_class
182
+
183
+ result = fetch(key_or_name, **xargs)
184
+ return result unless result.nil?
185
+
186
+ new_loads = load_dependencies!(**xargs)
187
+ result = fetch(key_or_name, **xargs) if new_loads
188
+
189
+ if result.nil? && fallback
190
+ result = fetch(fallback, **xargs)
191
+ report_fallback(key_or_name, result, base_class)
192
+ end
193
+
194
+ raise NotFoundError, (+<<~MSG).squish if result.nil?
195
+ Unable to find #{key_or_name.inspect} #{base_class} object.
196
+ MSG
197
+
198
+ result
199
+ end
200
+
201
+ # Find the given key or name inside the base class either on the given
202
+ # namespace or in the base +:base+ namespace
203
+ def fetch(key_or_name, prevent_register: nil, **xargs)
204
+ if prevent_register != true
205
+ items = prevent_register == true ? nil : ::Array.wrap(prevent_register)
206
+ skip_register << items.to_set
207
+ register_pending!
208
+ end
209
+
210
+ possibilities = ::Array.wrap(key_or_name)
211
+ possibilities << xargs[:fallback] if xargs.key?(:fallback)
212
+
213
+ base_class = xargs.fetch(:base_class, :Type)
214
+ sanitize_namespaces(**xargs).find do |namespace|
215
+ possibilities.find do |item|
216
+ next if (result = dig(namespace, base_class, item)).nil?
217
+ next if (result.is_a?(Proc) && (result = result.call).nil?)
218
+ return result
219
+ end
220
+ end
221
+ ensure
222
+ skip_register.pop
223
+ end
224
+
225
+ # Checks if a given key or name is already defined under the same base
226
+ # class and namespace. If +exclusive+ is set to +false+, then it won't
227
+ # check the +:base+ namespace when not found on the given namespace.
228
+ def exist?(name_or_key, **xargs)
229
+ !fetch(name_or_key, **xargs, prevent_register: true).nil?
230
+ end
231
+
232
+ # Find if a given object is already defined. If +exclusive+ is set to
233
+ # +false+, then it won't check the +:base+ namespace
234
+ def object_exist?(object, **xargs)
235
+ xargs[:base_class] = find_base_class(object)
236
+ xargs[:namespaces] ||= object.namespaces
237
+ exist?(object, **xargs)
238
+ end
239
+
217
240
  # Iterate over the types of the given +base_class+ that are defined on the
218
241
  # given +namespaces+.
219
- def each_from(namespaces, base_class: :Type, exclusive: false, &block)
242
+ def each_from(namespaces, base_class: nil, exclusive: false, base_classes: nil, &block)
243
+ namespaces = sanitize_namespaces(namespaces: namespaces, exclusive: exclusive)
244
+ load_dependencies!(_ns: namespaces)
220
245
  register_pending!
221
246
 
222
- namespaces = namespaces.is_a?(Set) ? namespaces.to_a : Array.wrap(namespaces)
223
- namespaces += [:base] unless namespaces.include?(:base) || exclusive
247
+ iterated = Set.new
248
+ base_classes = GraphQL.enumerate(base_class || base_classes || :Type)
249
+ enumerator = Enumerator::Lazy.new(namespaces) do |yielder, namespace|
250
+ next unless @index.key?(namespace)
224
251
 
225
- iterated = []
226
- enumerator = Enumerator::Lazy.new(namespaces.uniq) do |yielder, item|
227
- next unless @index.key?(item)
252
+ base_classes.each do |a_base_class|
253
+ @index[namespace][a_base_class]&.each do |key, value|
254
+ value = value.is_a?(Proc) ? value.call : value
255
+ next if value.blank? || iterated.include?(value.gql_name)
228
256
 
229
- # Only iterate over string based types
230
- @index[item][base_class]&.each do |_key, value|
231
- next if iterated.include?(value = value.call) || value.blank?
232
- iterated << value
233
- yielder << value
257
+ iterated << value.gql_name
258
+ yielder << value
259
+ end
234
260
  end
235
261
  end
236
262
 
237
263
  block.present? ? enumerator.each(&block) : enumerator
238
264
  end
239
265
 
266
+ # Get the list of all registered objects
267
+ # TODO: Maybe keep it as a lazy enumerator
268
+ def objects(base_classes: nil, namespaces: nil)
269
+ base_classes ||= self.class.base_classes
270
+ each_from(namespaces || @index.keys, base_classes: base_classes).select do |obj|
271
+ obj.is_a?(Helpers::Registerable)
272
+ end.force
273
+ end
274
+
240
275
  # Add a callback that will trigger when a type is registered under the
241
276
  # given set of settings of this method
242
- def after_register(name_or_key, base_class: :Type, namespaces: :base, &block)
243
- item = fetch(name_or_key,
244
- prevent_register: true,
245
- base_class: base_class,
246
- namespaces: namespaces,
247
- )
248
-
277
+ def after_register(name_or_key, base_class: :Type, **xargs, &block)
278
+ item = fetch(name_or_key, prevent_register: true, base_class: base_class, **xargs)
249
279
  return block.call(item) unless item.nil?
250
280
 
251
- namespaces = namespaces.is_a?(Set) ? namespaces.to_a : Array.wrap(namespaces)
252
- position = callbacks[name_or_key].size
253
-
254
- callbacks[name_or_key] << ->(n, b, result) do
281
+ namespaces = sanitize_namespaces(**xargs)
282
+ callback = ->(n, b, result) do
255
283
  return unless b === base_class && (n === :base || namespaces.include?(n))
256
284
  block.call(result)
257
- position
285
+ true
258
286
  end
287
+
288
+ callbacks[name_or_key].unshift(callback)
259
289
  end
260
290
 
261
- def inspect # :nodoc:
262
- <<~INFO.squish + '>'
291
+ def inspect
292
+ dependencies = @dependencies.each_pair.map do |key, list|
293
+ +("#{key}: #{list.size}")
294
+ end.join(', ')
295
+
296
+ (+<<~INFO).squish << '>'
263
297
  #<Rails::GraphQL::TypeMap [index]
264
- @namespaces=#{@index.size}
265
- @base_classes=#{base_classes.size}
266
- @objects=#{@objects}
267
- @pending=#{@pending.size}
298
+ @namespaces=#{@index.size}
299
+ @base_classes=#{base_classes.size}
300
+ @objects=#{@objects}
301
+ @pending=#{@pending.size}
302
+ @dependencies={#{dependencies}}
268
303
  INFO
269
304
  end
270
305
 
@@ -278,14 +313,25 @@ module Rails # :nodoc:
278
313
  return unless callbacks.key?(key)
279
314
 
280
315
  result = nil
281
- removeables = callbacks[key].map do |callback|
282
- callback.call(namespace, base_class, result ||= raw_result.call)
316
+ callbacks[key].delete_if do |callback|
317
+ result ||= raw_result.is_a?(Proc) ? raw_result.call : raw_result
318
+ callback.call(namespace, base_class, result)
283
319
  end
284
320
 
285
- removeables.compact.reverse_each(&callbacks[key].method(:delete_at))
286
321
  callbacks.delete(key) if callbacks[key].empty?
287
322
  end
288
323
 
324
+ # Make sure to parse the provided options and names and return a
325
+ # quality list of namespaces
326
+ def sanitize_namespaces(**xargs)
327
+ xargs[:_ns] ||= begin
328
+ result = xargs[:namespaces] || xargs[:namespace]
329
+ result = result.is_a?(Set) ? result.dup : Array.wrap(result).to_set
330
+ result << :base unless xargs.fetch(:exclusive, false)
331
+ result
332
+ end
333
+ end
334
+
289
335
  # A list of classes to prevent the registration, since they might be
290
336
  # the source of a fetch
291
337
  def skip_register
@@ -296,27 +342,38 @@ module Rails # :nodoc:
296
342
  def register_pending!
297
343
  return if @pending.blank?
298
344
 
299
- skip = skip_register.flatten
345
+ skip = skip_register.compact.reduce(:+)
300
346
  keep = []
301
- validate = []
302
347
 
303
348
  while (klass, source = @pending.shift)
304
349
  next if klass.registered?
305
350
 
306
- if skip.include?(klass)
351
+ if skip&.include?(klass)
307
352
  keep << [klass, source]
308
353
  else
309
- validate << klass.register!
354
+ klass.register!
310
355
  end
311
356
  end
312
-
313
- validate.compact.each(&:call)
314
357
  rescue DefinitionError => e
315
- raise e.class, e.message + "\n Defined at: #{source}"
358
+ raise e.class, +"#{e.message}\n Defined at: #{source}"
316
359
  ensure
317
360
  @pending += keep unless keep.nil?
318
361
  end
319
362
 
363
+ # Check the given namespaces and load any pending dependency, it returns
364
+ # true if anything was actually loaded
365
+ def load_dependencies!(**xargs)
366
+ sanitize_namespaces(**xargs).reduce(false) do |result, namespace|
367
+ next result if (list = @dependencies[namespace]).empty?
368
+
369
+ while (src = list.shift)
370
+ src.is_a?(Proc) ? src.call : require(src)
371
+ end
372
+
373
+ true
374
+ end
375
+ end
376
+
320
377
  # Since concurrent map doesn't implement this method, use this to
321
378
  # navigate through the index
322
379
  def dig(*parts)
@@ -336,12 +393,47 @@ module Rails # :nodoc:
336
393
  base_class.name.demodulize.to_sym
337
394
  end
338
395
 
396
+ # This is the minimum set of dependencies that Type Map needs
397
+ # TODO: Maybe add even looser dependency
398
+ def seed_dependencies!
399
+ @dependencies[:base] += [
400
+ "#{__dir__}/type/scalar",
401
+ "#{__dir__}/type/object",
402
+ "#{__dir__}/type/interface",
403
+ "#{__dir__}/type/union",
404
+ "#{__dir__}/type/enum",
405
+ "#{__dir__}/type/input",
406
+
407
+ "#{__dir__}/type/scalar/int_scalar",
408
+ "#{__dir__}/type/scalar/float_scalar",
409
+ "#{__dir__}/type/scalar/string_scalar",
410
+ "#{__dir__}/type/scalar/boolean_scalar",
411
+ "#{__dir__}/type/scalar/id_scalar",
412
+
413
+ "#{__dir__}/directive/deprecated_directive",
414
+ "#{__dir__}/directive/include_directive",
415
+ "#{__dir__}/directive/skip_directive",
416
+ ]
417
+ end
418
+
339
419
  # Make sure that the given key is a valid base class key
340
420
  def ensure_base_class!(key)
341
- raise ArgumentError, <<~MSG.squish unless base_classes.keys.include?(key)
421
+ raise ArgumentError, (+<<~MSG).squish unless base_classes.include?(key)
342
422
  Unsupported base class "#{key.inspect}".
343
423
  MSG
344
424
  end
425
+
426
+ # Warn whenever a new key was resolved as a fallback
427
+ def report_fallback(key_or_name, result, base_class)
428
+ return if @reported_fallbacks.include?(key_or_name)
429
+
430
+ @reported_fallbacks << key_or_name
431
+ GraphQL.logger.warn((+<<~MSG).squish)
432
+ \e[95m[GraphQL]
433
+ \e[33m#{base_class} "#{key_or_name}" is not defined,
434
+ using #{result.gql_name} instead.\e[0m
435
+ MSG
436
+ end
345
437
  end
346
438
  end
347
439
  end