graphiti 2.0.0.beta.4 → 2.0.0.beta.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/.github/workflows/ci.yml +5 -0
- data/.github/workflows/docs.yml +4 -7
- data/CHANGELOG.md +29 -0
- data/docs/concepts/links.md +3 -3
- data/docs/concepts/relationships.md +109 -9
- data/docs/topics/debugging.md +26 -0
- data/docs/upgrading.md +18 -17
- data/graphiti.gemspec +1 -19
- data/lib/generators/graphiti/resource_generator.rb +25 -1
- data/lib/generators/graphiti/templates/controller.rb.erb +1 -1
- data/lib/graphiti/audit/report.rb +226 -0
- data/lib/graphiti/audit.rb +231 -0
- data/lib/graphiti/errors.rb +47 -0
- data/lib/graphiti/rails/rake_helpers.rb +42 -0
- data/lib/graphiti/request_validators/validator.rb +10 -1
- data/lib/graphiti/resource/configuration.rb +16 -1
- data/lib/graphiti/resource/polymorphism.rb +10 -0
- data/lib/graphiti/schema.rb +1 -1
- data/lib/graphiti/sideload/belongs_to.rb +22 -29
- data/lib/graphiti/sideload.rb +48 -13
- data/lib/graphiti/spec_helpers/matchers.rb +196 -0
- data/lib/graphiti/spec_helpers/rspec.rb +1 -0
- data/lib/graphiti/spec_helpers.rb +1 -0
- data/lib/graphiti/util/serializer_relationships.rb +27 -4
- data/lib/graphiti/version.rb +1 -1
- data/lib/graphiti.rb +2 -0
- data/lib/tasks/graphiti.rake +16 -32
- data/website/docusaurus.config.js +67 -11
- data/website/src/css/custom.css +29 -2
- data/website/static/1.13/assets/img/fancy-cushion.png +0 -0
- data/website/static/1.13/assets/img/home-bg.jpg +0 -0
- data/website/static/1.13/assets/img/sunrise.png +0 -0
- data/website/static/1.13/assets/main.css +4 -4
- data/website/static/CNAME +1 -0
- metadata +9 -9
|
@@ -3,40 +3,33 @@ class Graphiti::Sideload::BelongsTo < Graphiti::Sideload
|
|
|
3
3
|
:belongs_to
|
|
4
4
|
end
|
|
5
5
|
|
|
6
|
-
def
|
|
7
|
-
|
|
6
|
+
def default_render_resource_ids?
|
|
7
|
+
case parent_resource_class&.belongs_to_resource_ids_by_default
|
|
8
|
+
when :always then renderable_at_all?
|
|
9
|
+
when :never then false
|
|
10
|
+
else resource_ids_from_foreign_key?
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def renderable_at_all?
|
|
15
|
+
readable_guarded? || readable?
|
|
8
16
|
end
|
|
9
17
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
# relationship, so the key alone cannot say what type the id has
|
|
20
|
-
# - a remote resource has no local foreign key to read
|
|
21
|
-
# - a custom primary_key points the relationship at some other column, so
|
|
22
|
-
# the key holds that column's value rather than the related id
|
|
23
|
-
def linkage_from_foreign_key?
|
|
24
|
-
# Ask before resolving #resource: an unreadable relationship renders
|
|
25
|
-
# nothing, and its resource class may not even be inferrable.
|
|
26
|
-
return false unless readable?
|
|
27
|
-
return false unless foreign_key_is_related_id?
|
|
28
|
-
return false if polymorphic_child?
|
|
29
|
-
return false if self.class.scope_proc || self.class.params_proc
|
|
30
|
-
return false if @base_scope
|
|
31
|
-
return false if remote?
|
|
32
|
-
return false if resource.class.polymorphic.present?
|
|
18
|
+
def resource_ids_blocker
|
|
19
|
+
return :unreadable unless renderable_at_all?
|
|
20
|
+
return :custom_primary_key unless foreign_key_is_related_id?
|
|
21
|
+
return :polymorphic_child if polymorphic_child?
|
|
22
|
+
return :scope_block if self.class.scope_proc
|
|
23
|
+
return :params_block if self.class.params_proc
|
|
24
|
+
return :base_scope if @base_scope
|
|
25
|
+
return :remote if remote?
|
|
26
|
+
return :polymorphic_resource if resource.class.polymorphic.present?
|
|
33
27
|
|
|
34
|
-
|
|
28
|
+
nil
|
|
35
29
|
end
|
|
36
30
|
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
# that at another column means the key holds that column instead.
|
|
31
|
+
# base_filter matches the foreign key against primary_key, so a custom
|
|
32
|
+
# primary_key means the key holds that column's value, not the related id.
|
|
40
33
|
def foreign_key_is_related_id?
|
|
41
34
|
primary_key == :id
|
|
42
35
|
end
|
data/lib/graphiti/sideload.rb
CHANGED
|
@@ -20,6 +20,7 @@ module Graphiti
|
|
|
20
20
|
def initialize(name, opts)
|
|
21
21
|
@name = name
|
|
22
22
|
validate_options!(opts)
|
|
23
|
+
translate_deprecated_options!(opts)
|
|
23
24
|
@parent_resource_class = opts[:parent_resource]
|
|
24
25
|
@resource_class_name = opts[:resource]
|
|
25
26
|
@primary_key = opts[:primary_key]
|
|
@@ -42,7 +43,7 @@ module Graphiti
|
|
|
42
43
|
@group_name = opts[:group_name]
|
|
43
44
|
@polymorphic_child = opts[:polymorphic_child]
|
|
44
45
|
@parent = opts[:parent]
|
|
45
|
-
@
|
|
46
|
+
@render_resource_ids = opts[:resource_ids]
|
|
46
47
|
|
|
47
48
|
if polymorphic_child?
|
|
48
49
|
parent.resource.polymorphic << resource_class
|
|
@@ -110,6 +111,31 @@ module Graphiti
|
|
|
110
111
|
dynamic_flag?(@readable) || dynamic_flag?(@writable)
|
|
111
112
|
end
|
|
112
113
|
|
|
114
|
+
def readable_guarded?
|
|
115
|
+
dynamic_flag?(@readable)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def readable_guard_name
|
|
119
|
+
@readable.to_sym if @readable.is_a?(Symbol) || @readable.is_a?(String)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def non_default_options
|
|
123
|
+
options = {}
|
|
124
|
+
options[:as] = association_name if @as
|
|
125
|
+
options[:primary_key] = primary_key unless primary_key == :id
|
|
126
|
+
options[:single] = true if single?
|
|
127
|
+
options[:remote] = @remote if remote?
|
|
128
|
+
options[:link] = @link unless @link.nil?
|
|
129
|
+
options[:readable] = @readable unless @readable.nil? || @readable == true
|
|
130
|
+
options[:writable] = @writable unless @writable.nil? || @writable == true
|
|
131
|
+
options[:resource_ids] = @render_resource_ids unless @render_resource_ids.nil?
|
|
132
|
+
options
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def customized_base_scope?
|
|
136
|
+
!!@base_scope
|
|
137
|
+
end
|
|
138
|
+
|
|
113
139
|
def single?
|
|
114
140
|
!!@single
|
|
115
141
|
end
|
|
@@ -122,24 +148,21 @@ module Graphiti
|
|
|
122
148
|
!!@polymorphic_as
|
|
123
149
|
end
|
|
124
150
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
def linkage_from_foreign_key?
|
|
128
|
-
false
|
|
151
|
+
def resource_ids_from_foreign_key?
|
|
152
|
+
resource_ids_blocker.nil?
|
|
129
153
|
end
|
|
130
154
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
return !!@always_include_resource_ids unless @always_include_resource_ids.nil?
|
|
155
|
+
def resource_ids_blocker
|
|
156
|
+
:no_foreign_key_on_parent
|
|
157
|
+
end
|
|
135
158
|
|
|
136
|
-
|
|
137
|
-
return
|
|
159
|
+
def render_resource_ids?
|
|
160
|
+
return !!@render_resource_ids unless @render_resource_ids.nil?
|
|
138
161
|
|
|
139
|
-
|
|
162
|
+
default_render_resource_ids?
|
|
140
163
|
end
|
|
141
164
|
|
|
142
|
-
def
|
|
165
|
+
def default_render_resource_ids?
|
|
143
166
|
false
|
|
144
167
|
end
|
|
145
168
|
|
|
@@ -434,6 +457,18 @@ module Graphiti
|
|
|
434
457
|
false
|
|
435
458
|
end
|
|
436
459
|
|
|
460
|
+
def translate_deprecated_options!(opts)
|
|
461
|
+
return unless opts.key?(:always_include_resource_ids)
|
|
462
|
+
|
|
463
|
+
Graphiti::DEPRECATOR.deprecation_warning(
|
|
464
|
+
:always_include_resource_ids,
|
|
465
|
+
"Use :resource_ids instead (#{opts[:parent_resource]&.name}##{@name})"
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
value = opts.delete(:always_include_resource_ids)
|
|
469
|
+
opts[:resource_ids] = value unless opts.key?(:resource_ids)
|
|
470
|
+
end
|
|
471
|
+
|
|
437
472
|
def validate_options!(opts)
|
|
438
473
|
if opts[:remote]
|
|
439
474
|
if opts[:resource]
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
module Graphiti
|
|
2
|
+
module SpecHelpers
|
|
3
|
+
# Assertions against a resource's DSL. The schema specs cannot cover
|
|
4
|
+
# adapter-dependent options (primary key, foreign key, and so on) because
|
|
5
|
+
# the schema does not carry them; these read the resource config directly.
|
|
6
|
+
module Matchers
|
|
7
|
+
class BaseMatcher
|
|
8
|
+
GRAPHITI_OPTS = [].freeze
|
|
9
|
+
GRAPHITI_CONFIG_KEY = ""
|
|
10
|
+
EXPECTED_ACTION = ""
|
|
11
|
+
|
|
12
|
+
def description
|
|
13
|
+
"#{self.class::EXPECTED_ACTION} #{target}"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def failure_message
|
|
17
|
+
"expected that #{resource.class} would #{self.class::EXPECTED_ACTION} #{target}\n#{@opt_failures.join("\n")}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def failure_message_when_negated
|
|
21
|
+
"expected that #{resource.class} would not #{self.class::EXPECTED_ACTION} #{target}"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def opt_failure_message(opt, expected, actual)
|
|
25
|
+
"expected that #{opt} would be #{expected}, was #{actual}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def does_not_match?(resource)
|
|
29
|
+
!matches?(resource)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def matches?(resource)
|
|
33
|
+
@resource = resource
|
|
34
|
+
|
|
35
|
+
expected? && expected_opts?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def with_options(opts)
|
|
39
|
+
@opts = opts
|
|
40
|
+
self
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def actual_opts
|
|
46
|
+
self.class::GRAPHITI_OPTS & opts.keys
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def config
|
|
50
|
+
@config ||= resource.class.config[self.class::GRAPHITI_CONFIG_KEY][target]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def expected_opts?
|
|
54
|
+
return false unless config
|
|
55
|
+
|
|
56
|
+
actual_opts.map { |opt| assert_opt(opt) }.all?(true)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
class RelationMatcher < BaseMatcher
|
|
61
|
+
GRAPHITI_OPTS = %i[primary_key foreign_key resource readable writable link single].freeze
|
|
62
|
+
GRAPHITI_CONFIG_KEY = :sideloads
|
|
63
|
+
EXPECTED_ACTION = ""
|
|
64
|
+
|
|
65
|
+
def initialize(target)
|
|
66
|
+
@target = target
|
|
67
|
+
@opts = {}
|
|
68
|
+
@opt_failures = []
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
attr_reader :target, :opts, :resource
|
|
74
|
+
|
|
75
|
+
def assert_opt(opt)
|
|
76
|
+
asserted_opt = opt == :resource ? :resource_class : opt
|
|
77
|
+
return true if config.send(asserted_opt) == opts[opt]
|
|
78
|
+
|
|
79
|
+
@opt_failures << opt_failure_message(opt, opts[opt], config.send(asserted_opt))
|
|
80
|
+
false
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def expected?
|
|
84
|
+
config && config.type == relation_name
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# BelongsToMatcher -> :belongs_to, matching Sideload#type.
|
|
88
|
+
def relation_name
|
|
89
|
+
self.class.name.demodulize.gsub("Matcher", "").underscore.to_sym
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
class BelongsToMatcher < RelationMatcher
|
|
94
|
+
EXPECTED_ACTION = "belong to"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
class HasManyMatcher < RelationMatcher
|
|
98
|
+
EXPECTED_ACTION = "have many"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
class HasOneMatcher < RelationMatcher
|
|
102
|
+
EXPECTED_ACTION = "have one"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
class ResourceDSLMatcher < BaseMatcher
|
|
106
|
+
def initialize(target, type)
|
|
107
|
+
@target = target
|
|
108
|
+
@type = type
|
|
109
|
+
@opts = {}
|
|
110
|
+
@opt_failures = []
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
attr_reader :target, :type, :opts, :resource
|
|
116
|
+
|
|
117
|
+
def expected?
|
|
118
|
+
config && assert_type
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def assert_type
|
|
122
|
+
return true if config[:type] == type
|
|
123
|
+
|
|
124
|
+
@opt_failures << opt_failure_message("type", type, config[:type])
|
|
125
|
+
false
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def assert_opt(opt)
|
|
129
|
+
return true if config[opt] == opts[opt]
|
|
130
|
+
|
|
131
|
+
@opt_failures << opt_failure_message(opt, opts[opt], config[opt])
|
|
132
|
+
false
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
class ExposeAttributeMatcher < ResourceDSLMatcher
|
|
137
|
+
GRAPHITI_OPTS = %i[writable readable sortable filterable].freeze
|
|
138
|
+
GRAPHITI_CONFIG_KEY = :attributes
|
|
139
|
+
EXPECTED_ACTION = "expose"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
class FilterAttributeMatcher < ResourceDSLMatcher
|
|
143
|
+
GRAPHITI_OPTS = %i[allow deny single required allow_nil deny_empty].freeze
|
|
144
|
+
GRAPHITI_CONFIG_KEY = :filters
|
|
145
|
+
EXPECTED_ACTION = "filter"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# @param [Symbol] relation
|
|
149
|
+
#
|
|
150
|
+
# @example expect(subject).to belong_to_resource(:user)
|
|
151
|
+
# @example expect(subject).to belong_to_resource(:user).with_options(foreign_key: :user_id, resource: UserResource)
|
|
152
|
+
# @example expect(subject).not_to belong_to_resource(:user)
|
|
153
|
+
def belong_to_resource(relation)
|
|
154
|
+
BelongsToMatcher.new(relation)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# @param [Symbol] relation
|
|
158
|
+
#
|
|
159
|
+
# @example expect(subject).to have_many_resources(:posts)
|
|
160
|
+
# @example expect(subject).to have_many_resources(:posts).with_options(foreign_key: :post_id, resource: PostResource)
|
|
161
|
+
# @example expect(subject).not_to have_many_resources(:posts)
|
|
162
|
+
def have_many_resources(relation)
|
|
163
|
+
HasManyMatcher.new(relation)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# @param [Symbol] relation
|
|
167
|
+
#
|
|
168
|
+
# @example expect(subject).to have_one_resource(:post)
|
|
169
|
+
# @example expect(subject).to have_one_resource(:post).with_options(foreign_key: :post_id, resource: PostResource)
|
|
170
|
+
# @example expect(subject).not_to have_one_resource(:post)
|
|
171
|
+
def have_one_resource(relation)
|
|
172
|
+
HasOneMatcher.new(relation)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# @param [Symbol] attribute
|
|
176
|
+
# @param [Symbol] type
|
|
177
|
+
#
|
|
178
|
+
# @example expect(subject).to expose_attribute(:name, :string)
|
|
179
|
+
# @example expect(subject).to expose_attribute(:name, :string).with_options(writable: false)
|
|
180
|
+
# @example expect(subject).not_to expose_attribute(:name, :string)
|
|
181
|
+
def expose_attribute(attribute, type)
|
|
182
|
+
ExposeAttributeMatcher.new(attribute, type)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# @param [Symbol] attribute
|
|
186
|
+
# @param [Symbol] type
|
|
187
|
+
#
|
|
188
|
+
# @example expect(subject).to filter_attribute(:name, :string)
|
|
189
|
+
# @example expect(subject).to filter_attribute(:name, :string).with_options(allow_nil: false)
|
|
190
|
+
# @example expect(subject).not_to filter_attribute(:name, :string)
|
|
191
|
+
def filter_attribute(attribute, type)
|
|
192
|
+
FilterAttributeMatcher.new(attribute, type)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
@@ -68,9 +68,9 @@ module Graphiti
|
|
|
68
68
|
# sideload can resolve it to something the foreign key alone would
|
|
69
69
|
# not predict, so the loaded records win. Only the un-included case
|
|
70
70
|
# is worth short-circuiting.
|
|
71
|
-
if sideload_ref.
|
|
71
|
+
if sideload_ref.resource_ids_from_foreign_key? &&
|
|
72
72
|
!self_ref.send(:included_anywhere?, @proxy.query.include_hash, sideload_ref.name)
|
|
73
|
-
linkage always: sideload_ref.
|
|
73
|
+
linkage always: sideload_ref.render_resource_ids? do
|
|
74
74
|
foreign_key = @object.public_send(sideload_ref.foreign_key)
|
|
75
75
|
|
|
76
76
|
unless foreign_key.nil?
|
|
@@ -81,7 +81,7 @@ module Graphiti
|
|
|
81
81
|
end
|
|
82
82
|
end
|
|
83
83
|
else
|
|
84
|
-
linkage always: sideload_ref.
|
|
84
|
+
linkage always: sideload_ref.render_resource_ids?
|
|
85
85
|
end
|
|
86
86
|
|
|
87
87
|
if link_ref
|
|
@@ -107,8 +107,31 @@ module Graphiti
|
|
|
107
107
|
|
|
108
108
|
def data_proc
|
|
109
109
|
sideload_ref = @sideload
|
|
110
|
+
resource_class_ref = @resource_class
|
|
110
111
|
->(_) {
|
|
111
|
-
|
|
112
|
+
begin
|
|
113
|
+
records = @object.public_send(sideload_ref.association_name)
|
|
114
|
+
rescue NoMethodError => error
|
|
115
|
+
# #receiver raises ArgumentError when the error was built by hand
|
|
116
|
+
# rather than raised by a failed call, and a hand-built one can
|
|
117
|
+
# still carry a matching #name.
|
|
118
|
+
receiver = begin
|
|
119
|
+
error.receiver
|
|
120
|
+
rescue ArgumentError
|
|
121
|
+
nil
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
raise unless error.name == sideload_ref.association_name &&
|
|
125
|
+
receiver.equal?(@object)
|
|
126
|
+
|
|
127
|
+
# A private method exists, so "has no such method" would be a lie.
|
|
128
|
+
raise if @object.respond_to?(sideload_ref.association_name, true)
|
|
129
|
+
|
|
130
|
+
raise Errors::MissingRelationshipMethod
|
|
131
|
+
.new(resource_class_ref, sideload_ref, @object)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
if records
|
|
112
135
|
if records.respond_to?(:to_ary)
|
|
113
136
|
records.each { |r| sideload_ref.resource.decorate_record(r) }
|
|
114
137
|
else
|
data/lib/graphiti/version.rb
CHANGED
data/lib/graphiti.rb
CHANGED
|
@@ -166,6 +166,8 @@ require "graphiti/configuration"
|
|
|
166
166
|
require "graphiti/context"
|
|
167
167
|
require "graphiti/errors"
|
|
168
168
|
require "graphiti/types"
|
|
169
|
+
require "graphiti/audit"
|
|
170
|
+
require "graphiti/audit/report"
|
|
169
171
|
require "graphiti/schema"
|
|
170
172
|
require "graphiti/schema_diff"
|
|
171
173
|
require "graphiti/adapters/abstract"
|
data/lib/tasks/graphiti.rake
CHANGED
|
@@ -1,52 +1,36 @@
|
|
|
1
|
-
|
|
2
|
-
include Graphiti::Rails::TestHelpers
|
|
3
|
-
|
|
4
|
-
def session
|
|
5
|
-
@session ||= ActionDispatch::Integration::Session.new(Rails.application)
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
def setup_rails!
|
|
9
|
-
Rails.application.eager_load!
|
|
10
|
-
Rails.application.config.cache_classes = true
|
|
11
|
-
Rails.application.config.action_controller.perform_caching = false
|
|
12
|
-
end
|
|
1
|
+
require "graphiti/rails/rake_helpers"
|
|
13
2
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
path = "#{ApplicationResource.endpoint_namespace}#{path}"
|
|
17
|
-
end
|
|
18
|
-
path << if path.include?("?")
|
|
19
|
-
"&cache=bust"
|
|
20
|
-
else
|
|
21
|
-
"?cache=bust"
|
|
22
|
-
end
|
|
23
|
-
path = "#{path}&debug=true" if debug
|
|
24
|
-
handle_request_exceptions do
|
|
25
|
-
headers = {Authorization: ENV["AUTHORIZATION_HEADER"]}.compact
|
|
26
|
-
session.get(path.to_s, headers: headers)
|
|
27
|
-
end
|
|
28
|
-
JSON.parse(session.response.body)
|
|
29
|
-
end
|
|
3
|
+
namespace :graphiti do
|
|
4
|
+
helpers = Graphiti::Rails::RakeHelpers
|
|
30
5
|
|
|
31
6
|
desc "Execute request without web server."
|
|
32
7
|
task :request, [:path, :debug] => [:environment] do |_, args|
|
|
33
|
-
setup_rails!
|
|
8
|
+
helpers.setup_rails!
|
|
34
9
|
Graphiti.logger = Graphiti.stdout_logger
|
|
35
10
|
Graphiti::Debugger.preserve = true
|
|
36
11
|
require "pp"
|
|
37
12
|
path, debug = args[:path], args[:debug]
|
|
38
13
|
puts "Graphiti Request: #{path}"
|
|
39
|
-
json = make_request(path, debug)
|
|
14
|
+
json = helpers.make_request(path, debug)
|
|
40
15
|
pp json
|
|
41
16
|
Graphiti::Debugger.flush if debug
|
|
42
17
|
end
|
|
43
18
|
|
|
19
|
+
desc "Audit every relationship: what will raise, what loads to render ids, which render no ids, and which checks passed."
|
|
20
|
+
task audit: [:environment] do
|
|
21
|
+
helpers.setup_rails!
|
|
22
|
+
rows = Graphiti::Audit.run
|
|
23
|
+
puts Graphiti::Audit::Report.new(rows).to_s
|
|
24
|
+
|
|
25
|
+
exit 1 if rows.any?(&:error?)
|
|
26
|
+
end
|
|
27
|
+
|
|
44
28
|
desc "Execute benchmark without web server."
|
|
45
29
|
task :benchmark, [:path, :requests] => [:environment] do |_, args|
|
|
46
|
-
setup_rails!
|
|
30
|
+
helpers.setup_rails!
|
|
47
31
|
took = Benchmark.ms {
|
|
48
32
|
args[:requests].to_i.times do
|
|
49
|
-
make_request(args[:path])
|
|
33
|
+
helpers.make_request(args[:path])
|
|
50
34
|
end
|
|
51
35
|
}
|
|
52
36
|
puts "Took: #{(took / args[:requests].to_f).round(2)}ms"
|
|
@@ -1,16 +1,41 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
const {themes} = require('prism-react-renderer');
|
|
3
3
|
|
|
4
|
+
const codeBlockTheme = (theme, backgroundColor) => ({
|
|
5
|
+
...theme,
|
|
6
|
+
plain: {...theme.plain, backgroundColor},
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// Docusaurus prepends baseUrl to navbar and Link hrefs, but not to raw HTML
|
|
10
|
+
// like announcementBar content, so that markup has to build its own absolute
|
|
11
|
+
// paths. Always has a trailing slash.
|
|
12
|
+
const baseUrl = process.env.DOCS_BASE_URL || '/';
|
|
13
|
+
|
|
14
|
+
// The theme's strikethrough and underline on diff lines are inline styles, so
|
|
15
|
+
// CSS cannot undo them. Colour and the +/- signs already carry it.
|
|
16
|
+
const withoutDiffDecoration = (theme) => ({
|
|
17
|
+
...theme,
|
|
18
|
+
styles: theme.styles.map(({types, style}) =>
|
|
19
|
+
types.some((type) => type === 'inserted' || type === 'deleted')
|
|
20
|
+
? {types, style: {...style, textDecorationLine: undefined}}
|
|
21
|
+
: {types, style}
|
|
22
|
+
),
|
|
23
|
+
});
|
|
24
|
+
|
|
4
25
|
/** @type {import('@docusaurus/types').Config} */
|
|
5
26
|
const config = {
|
|
6
27
|
title: 'Graphiti',
|
|
7
28
|
tagline: 'Stylish Graph APIs',
|
|
8
29
|
favicon: 'img/favicon.ico',
|
|
9
30
|
|
|
10
|
-
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
|
|
31
|
+
// Apex, matching the CNAME. This feeds canonical tags and sitemap.xml, so it
|
|
32
|
+
// has to agree with the host actually serving the site or the two split
|
|
33
|
+
// search ranking between them.
|
|
34
|
+
url: 'https://graphiti.dev',
|
|
35
|
+
// graphiti.dev resolves to this repo, so the site serves from the root.
|
|
36
|
+
// DOCS_BASE_URL still overrides it for a build served from the project-pages
|
|
37
|
+
// path, which is what /graphiti/ was during the beta.
|
|
38
|
+
baseUrl,
|
|
14
39
|
organizationName: 'graphiti-api',
|
|
15
40
|
projectName: 'graphiti',
|
|
16
41
|
|
|
@@ -75,7 +100,7 @@ const config = {
|
|
|
75
100
|
{from: ['/cookbooks/customizing-sideloads'], to: '/topics/customizing-sideloads'},
|
|
76
101
|
{from: ['/cookbooks/hopping-relationships'], to: '/topics/hopping-relationships'},
|
|
77
102
|
|
|
78
|
-
{from: ['/js/introduction'], to: '/js'},
|
|
103
|
+
{from: ['/js/introduction'], to: '/js/'},
|
|
79
104
|
{
|
|
80
105
|
from: [
|
|
81
106
|
'/js/reads/index',
|
|
@@ -100,11 +125,24 @@ const config = {
|
|
|
100
125
|
to: '/js/writes',
|
|
101
126
|
},
|
|
102
127
|
],
|
|
128
|
+
|
|
129
|
+
// The 2.0 docs were staged under /graphiti/ for the whole beta, so
|
|
130
|
+
// those URLs are in the wild. createRedirects sees every generated
|
|
131
|
+
// route, which the static /1.13/ tree is not: that one is by hand.
|
|
132
|
+
createRedirects(existingPath) {
|
|
133
|
+
return [`/graphiti${existingPath}`];
|
|
134
|
+
},
|
|
103
135
|
},
|
|
104
136
|
],
|
|
105
137
|
],
|
|
106
138
|
|
|
107
139
|
themeConfig: {
|
|
140
|
+
announcementBar: {
|
|
141
|
+
// Changing the id un-dismisses the bar for everyone who has closed it.
|
|
142
|
+
id: 'graphiti-2-0-beta',
|
|
143
|
+
content: `Graphiti 2.0 is in beta, and the docs have been reorganized to describe it. <a href="${baseUrl}1.13/">Docs for 1.x</a>.`,
|
|
144
|
+
isCloseable: true,
|
|
145
|
+
},
|
|
108
146
|
colorMode: {
|
|
109
147
|
// Dark for a first-time visitor. Flip respectPrefersColorScheme to true
|
|
110
148
|
// to follow the OS setting instead, in which case defaultMode only
|
|
@@ -122,19 +160,37 @@ const config = {
|
|
|
122
160
|
position: 'right',
|
|
123
161
|
// 1.x stays as the original Jekyll site, frozen under /1.13.
|
|
124
162
|
//
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
163
|
+
// The pathname:// prefix is required. It is Docusaurus' escape hatch
|
|
164
|
+
// for a path that is served statically but is not an app route:
|
|
165
|
+
// without it the link renders as a react-router push, which finds no
|
|
166
|
+
// match and shows the 404 page even though the file is served fine.
|
|
167
|
+
// The prefix is stripped before baseUrl is applied, and it also makes
|
|
168
|
+
// the link count as external, hence target '_self' to keep it from
|
|
169
|
+
// opening in a new tab.
|
|
128
170
|
dropdownItemsAfter: [
|
|
129
|
-
{href: '
|
|
171
|
+
{href: 'pathname:///1.13/', label: '1.x', target: '_self'},
|
|
130
172
|
],
|
|
131
173
|
},
|
|
132
174
|
{href: 'https://github.com/graphiti-api/graphiti', label: 'GitHub', position: 'right'},
|
|
133
175
|
{href: 'https://discord.gg/wgqkMBsSRV', label: 'Discord', position: 'right'},
|
|
134
176
|
],
|
|
135
177
|
},
|
|
136
|
-
footer: {
|
|
137
|
-
|
|
178
|
+
footer: {
|
|
179
|
+
style: 'dark',
|
|
180
|
+
copyright: [
|
|
181
|
+
'Originally created by <a href="https://github.com/richmolj">Lee Richmond</a>,',
|
|
182
|
+
'<a href="https://github.com/wadetandy">Wade Tandy</a>, and',
|
|
183
|
+
'<a href="https://github.com/wagenet">Peter Wagenet</a>.',
|
|
184
|
+
'Maintained by <a href="https://github.com/jkeen">Jeff Keen</a>',
|
|
185
|
+
'with <a href="https://github.com/graphiti-api/graphiti/graphs/contributors">many contributors</a>.',
|
|
186
|
+
'<br />Graphiti is released under the MIT license.',
|
|
187
|
+
].join(' '),
|
|
188
|
+
},
|
|
189
|
+
prism: {
|
|
190
|
+
theme: withoutDiffDecoration(themes.oneLight),
|
|
191
|
+
darkTheme: withoutDiffDecoration(codeBlockTheme(themes.oneDark, '#21252b')),
|
|
192
|
+
additionalLanguages: ['ruby', 'bash', 'json', 'http', 'diff'],
|
|
193
|
+
},
|
|
138
194
|
},
|
|
139
195
|
};
|
|
140
196
|
|