Module: FragmentClient::TypedEntries
- Extended by:
- T::Sig
- Defined in:
- lib/fragment_client/typed_entries.rb
Overview
Derives typed addLedgerEntries payloads from the per-entry-type
addLedgerEntry operations the Fragment CLI generates for a Schema.
A generated operation names the entry type as a string literal and binds each
parameter to a typed variable, which is what addLedgerEntries alone cannot
express: its parameters is an opaque JSON scalar.
FragmentClient::TypedEntries.load('app/graphql/entries.graphql')
entry = FragmentClient::Entries::AuthCaptureV1.new(
ik: 'ik-1', ledger_ik: 'prod', capture_amount: '100'
)
client.add_ledger_entries(entries: [entry])
Payload classes are built at load time, so Sorbet sees them only through the
RBI bundle exec tapioca dsl generates.
Implements typed-batch-entries.md from fragment-dev/graphql-queries.
Section references throughout are to that spec; docs/spec-conformance.md
maps it onto this SDK and explains the choices.
Defined Under Namespace
Classes: EntrySpec, Error, Parameter, UnknownEntryTypeError, Unset
Constant Summary
collapse
- ADD_LEDGER_ENTRY_FIELD =
The only field a typed entry operation may select (spec 2.1).
'addLedgerEntry'
- DEFAULT_TYPE_VERSION =
What an entry with no typeVersion resolves to server-side (spec 2.5).
1
- UNSET =
T.let(Unset.new.freeze, Unset)
Class Method Summary
collapse
Class Method Details
.constant_name(entry_type) ⇒ Object
359
360
361
362
363
364
365
366
367
|
# File 'lib/fragment_client/typed_entries.rb', line 359
def constant_name(entry_type)
parts = entry_type
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
.split(/[^a-zA-Z\d]+/)
.reject(&:empty?)
name = parts.map { |part| part[0].to_s.upcase + T.must(part[1..]) }.join
name.match?(/\A[A-Z]/) ? name : "Entry#{name}"
end
|
.defined_constants ⇒ Object
212
213
214
|
# File 'lib/fragment_client/typed_entries.rb', line 212
def defined_constants
@defined_constants ||= T.let([], T.nilable(T::Array[[Module, String]]))
end
|
.entry_argument(operation) ⇒ Object
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
|
# File 'lib/fragment_client/typed_entries.rb', line 220
def (document)
specs = T.let({}, T::Hash[[String, Integer], EntrySpec])
document.definitions.each do |definition|
next unless definition.is_a?(GraphQL::Language::Nodes::OperationDefinition)
spec = (definition)
next if spec.nil?
existing = specs[spec.identity]
if existing
warn_on_conflict(existing, spec)
next
end
specs[spec.identity] = spec
end
specs.values
end
|
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
|
# File 'lib/fragment_client/typed_entries.rb', line 287
def (node, operation)
return [] unless node.is_a?(GraphQL::Language::Nodes::InputObject)
types = GraphqlAst.variable_types(operation)
taken = T.let({}, T::Hash[Symbol, String])
node.arguments.filter_map do |argument|
value = argument.value
next unless value.is_a?(GraphQL::Language::Nodes::VariableIdentifier)
type = types[value.name]
if type.nil?
logger.warn(
"Fragment: parameter #{argument.name.inspect} in operation " \
"#{operation.name} is bound to undeclared variable $#{value.name}; " \
'treating it as an optional untyped parameter.'
)
end
Parameter.new(
wire_name: argument.name,
name: local_name(argument.name, taken, operation),
graphql_type: type&.to_query_string || 'JSON',
required: type.is_a?(GraphQL::Language::Nodes::NonNullType)
)
end
end
|
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
|
# File 'lib/fragment_client/typed_entries.rb', line 248
def (operation)
name = operation.name
return nil if name.nil?
entry = entry_argument(operation)
return nil if entry.nil?
entry_type = GraphqlAst.object_field(entry, 'type')
return nil unless entry_type.is_a?(String)
version = GraphqlAst.object_field(entry, 'typeVersion')
EntrySpec.new(
entry_type: entry_type,
type_version: version.is_a?(Integer) ? version : DEFAULT_TYPE_VERSION,
operation_name: name,
parameters: (GraphqlAst.object_field(entry, 'parameters'), operation)
)
end
|
.fetch(entry_type, type_version = DEFAULT_TYPE_VERSION) ⇒ Object
164
165
166
167
168
169
170
171
|
# File 'lib/fragment_client/typed_entries.rb', line 164
def fetch(entry_type, type_version = DEFAULT_TYPE_VERSION)
registry.fetch([entry_type, type_version]) do
raise UnknownEntryTypeError,
"No typed payload loaded for Ledger Entry type #{entry_type.inspect} " \
"version #{type_version}. Pass the .graphql file declaring its " \
'addLedgerEntry operation to FragmentClient::TypedEntries.load.'
end
end
|
.load(*paths, namespace: Entries) ⇒ Object
144
145
146
|
# File 'lib/fragment_client/typed_entries.rb', line 144
def load(*paths, namespace: Entries)
paths.flat_map { |path| load_string(File.read(path), namespace: namespace, origin: path) }
end
|
.load_string(source, namespace: Entries, origin: nil) ⇒ Object
153
154
155
|
# File 'lib/fragment_client/typed_entries.rb', line 153
def load_string(source, namespace: Entries, origin: nil)
define((GraphQL.parse(source)), namespace: namespace, origin: origin)
end
|
.local_name(wire_name, taken, operation) ⇒ Object
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
|
# File 'lib/fragment_client/typed_entries.rb', line 328
def local_name(wire_name, taken, operation)
name = wire_name.to_sym
return claim(name, wire_name, taken) unless taken.key?(name) || reserved?(name)
candidate = :"#{wire_name}_"
counter = 2
while taken.key?(candidate) || reserved?(candidate)
candidate = :"#{wire_name}_#{counter}"
counter += 1
end
clash = taken[name]
reason = clash ? "already used by parameter #{clash.inspect}" : 'reserved by the payload class'
logger.warn(
"Fragment: parameter #{wire_name.inspect} of #{operation.name} cannot be exposed " \
"under that name (#{reason}); it is available as #{candidate.inspect} instead. " \
'The wire payload is unchanged.'
)
claim(candidate, wire_name, taken)
end
|
.lock ⇒ Object
205
206
207
|
# File 'lib/fragment_client/typed_entries.rb', line 205
def lock
@lock ||= T.let(Thread::Mutex.new, T.nilable(Thread::Mutex))
end
|
.registry ⇒ Object
175
176
177
178
|
# File 'lib/fragment_client/typed_entries.rb', line 175
def registry
@registry ||= T.let({}, T.nilable(T::Hash[[String, Integer],
T.class_of(FragmentClient::TypedLedgerEntry)]))
end
|
.reserved?(name) ⇒ Boolean
.reset! ⇒ Object
182
183
184
185
186
187
188
|
# File 'lib/fragment_client/typed_entries.rb', line 182
def reset!
defined_constants.each do |namespace, name|
namespace.send(:remove_const, name) if namespace.const_defined?(name, false)
end
defined_constants.clear
registry.clear
end
|
.to_entry_inputs(entries) ⇒ Object
193
194
195
196
197
|
# File 'lib/fragment_client/typed_entries.rb', line 193
def to_entry_inputs(entries)
entries.map do |entry|
entry.is_a?(FragmentClient::TypedLedgerEntry) ? entry.to_entry_input : entry
end
end
|