Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Helpers for using graphql
with Relay. Includes support for serving Relay connections from Array
s, ActiveRecord::Relation
s and Sequel::Dataset
s.
gem "graphql-relay"
bundle install
graphql-relay
provides several helpers for making a Relay-compliant GraphQL endpoint in Ruby:
Global ids (or UUIDs) provide refetching & global identification for Relay.
Use GraphQL::Relay::GlobalNodeIdentification
helper by defining object_from_id(global_id, ctx)
& type_from_object(object)
. Then, assign the result to Schema#node_identification
so that it can be used for query execution.
For example, define a node identification helper:
NodeIdentification = GraphQL::Relay::GlobalNodeIdentification.define do
# Given a UUID & the query context,
# return the corresponding application object
object_from_id -> (id, ctx) do
type_name, id = NodeIdentification.from_global_id(id)
# "Post" -> Post.find(id)
Object.const_get(type_name).find(id)
end
# Given an application object,
# return a GraphQL ObjectType to expose that object
type_from_object -> (object) do
if object.is_a?(Post)
PostType
else
CommentType
end
end
end
Then assign it to the schema:
MySchema = GraphQL::Schema.new(...)
# Assign your node identification helper:
MySchema.node_identification = NodeIdentification
ObjectTypes in your schema should implement NodeIdentification.interface
with the global_id_field
helper, for example:
PostType = GraphQL::ObjectType.define do
name "Post"
interfaces [NodeIdentification.interface]
# `id` exposes the UUID
global_id_field :id
# ...
end
node
field (find-by-UUID)You should also add a field to your root query type for Relay to re-fetch objects:
QueryType = GraphQL::ObjectType.define do
name "Query"
# Used by Relay to lookup objects by UUID:
field :node, field: NodeIdentification.field
# ...
end
By default, graphql-relay
uses Base64.strict_encode64
to generate opaque global ids. You can modify this behavior by providing two configurations. They work together to encode and decode ids:
NodeIdentification = GraphQL::Relay::GlobalNodeIdentification.define do
# ...
# Return a string for re-fetching this object
to_global_id -> (type_name, id) {
"#{type_name}/#{id}"
}
# Based on the incoming string, extract the type_name and id
from_global_id -> (global_id) {
id_parts = global_id.split("/")
type_name = id_parts[0]
id = id_parts[1]
# Return *both*:
[type_name, id]
}
end
# ...
MySchema.node_identification = NodeIdentification
graphql-relay
will use those procs for interacting with global ids.
Connections provide pagination and pageInfo
for Array
s, ActiveRecord::Relation
s or Sequel::Dataset
s.
To define a connection field, use the connection
helper. For a return type, get a type's .connection_type
. For example:
PostType = GraphQL::ObjectType.define do
# `comments` field returns a CommentsConnection:
connection :comments, CommentType.connection_type
# To avoid circular dependencies, wrap the return type in a proc:
connection :similarPosts, -> { PostType.connection_type }
# ...
end
You can also define custom arguments and a custom resolve function for connections, just like other fields:
connection :featured_comments, CommentType.connection_type do
# Use a name to disambiguate this from `CommentType.connection_type`
name "CommentConnectionWithSince"
# Add an argument:
argument :since, types.String
# Return an Array or ActiveRecord::Relation
resolve -> (post, args, ctx) {
comments = post.comments.featured
if args[:since]
comments = comments.where("created_at >= ", since)
end
comments
}
end
You can limit the number of results with max_page_size:
:
connection :featured_comments, CommentType.connection_type, max_page_size: 50
You can customize a connection type with .define_connection
:
PostConnectionWithTotalCountType = PostType.define_connection do
field :totalCount do
type types.Int
# `obj` is the Connection, `obj.object` is the collection of Posts
resolve -> (obj, args, ctx) { obj.object.count }
end
end
Now, you can use PostConnectionWithTotalCountType
to define a connection with the "totalCount" field:
AuthorType = GraphQL::ObjectType.define do
# Use the custom connection type:
connection :posts, PostConnectionWithTotalCountType
end
If you need custom fields on edge
s, you can define an edge type and pass it to a connection:
# Person => Membership => Team
MembershipSinceEdgeType = BaseType.define_edge do
name "MembershipSinceEdge"
field :memberSince, types.Int, "The date that this person joined this team" do
resolve -> (obj, args, ctx) {
obj # => GraphQL::Relay::Edge instnce
person = obj.parent
team = obj.node
membership = Membership.where(person: person, team: team).first
membership.created_at.to_i
}
end
end
Then, pass the edge type when defining the connection type:
TeamMembershipsConnectionType = TeamType.define_connection(edge_type: MembershipSinceEdgeType) do
# Use a name so it doesn't conflict with "TeamConnection"
name "TeamMembershipsConnection"
end
Now, you can query custom fields on the edge
:
{
me {
teams {
edge {
memberSince # <= Here's your custom field
node {
teamName: name
}
}
}
}
}
For more robust custom edges, you can define a custom edge class. It will be obj
in the edge type's resolve function. For example, to define a membership edge:
# Make sure to familiarize yourself with GraphQL::Relay::Edge --
# you have to avoid naming conflicts here!
class MembershipSinceEdge < GraphQL::Relay::Edge
# Cache `membership` to avoid multiple DB queries
def membership
@membership ||= begin
# "parent" and "node" are passed in from the surrounding Connection,
# See `Edge#initialize` for details
person = self.parent
team = self.node
Membership.where(person: person, team: team).first
end
end
def member_since
membership.created_at.to_i
end
def leader?
membership.leader?
end
end
Then, hook it up with custom edge type and custom connection type:
# Person => Membership => Team
MembershipSinceEdgeType = BaseType.define_edge do
name "MembershipSinceEdge"
field :memberSince, types.Int, "The date that this person joined this team", property: :member_since
field :isPrimary, types.Boolean, "Is this person the team leader?". property: :primary?
end
end
TeamMembershipsConnectionType = TeamType.define_connection(
edge_class: MembershipSinceEdge,
edge_type: MembershipSinceEdgeType,
) do
# Use a name so it doesn't conflict with "TeamConnection"
name "TeamMembershipsConnection"
end
Maybe you need to make a connection object yourself (for example, to return a connection type from a mutation). You can create a connection object like this:
items = [...] # your collection objects
args = {} # stub out arguments for this connection object
connection_class = GraphQL::Relay::BaseConnection.connection_for_items(items)
connection_class.new(items, args)
.connection_for_items
will return RelationConnection or ArrayConnection depending on items
, then you can make a new connection
You can define a custom connection class and add it to GraphQL::Relay
.
First, define the custom connection:
class SetConnection < BaseConnection
# derive a cursor from `item`
def cursor_from_node(item)
# ...
end
private
# apply `#first` & `#last` to limit results
def paged_nodes
# ...
end
# apply cursor, order, filters, etc
# to get a subset of matching objects
def sliced_nodes
# ...
end
end
Then, register the new connection with GraphQL::Relay::BaseConnection
:
# When exposing a `Set`, use `SetConnection`:
GraphQL::Relay::BaseConnection.register_connection_implementation(Set, SetConnection)
At runtime, GraphQL::Relay
will use SetConnection
to expose Set
s.
If you need lower-level access to Connection fields, you can create them programmatically. Given a GraphQL::Field
which returns a collection of items, you can turn it into a connection field with ConnectionField.create
.
For example, to wrap a field with a connection field:
field = GraphQL::Field.new
# ... define the field
connection_field = GraphQL::Relay::ConnectionField.create(field)
Mutations allow Relay to mutate your system. They conform to a strict API which makes them predictable to the client.
To add mutations to your GraphQL schema, define a mutation type and pass it to your schema:
# Define the mutation type
MutationType = GraphQL::ObjectType.define do
name "Mutation"
# ...
end
# and pass it to the schema
MySchema = GraphQL::Schema.new(
query: QueryType,
mutation: MutationType
)
Like QueryType
, MutationType
is a root of the schema.
Members of MutationType
are mutation fields. For GraphQL in general, mutation fields are identical to query fields except that they have side-effects (which mutate application state, eg, update the database).
For Relay-compliant GraphQL, a mutation field must comply to a strict API. GraphQL::Relay
includes a mutation definition helper (see below) to make it simple.
After defining a mutation (see below), add it to your mutation type:
MutationType = GraphQL::ObjectType.define do
name "Mutation"
# Add the mutation's derived field to the mutation type
field :addComment, field: AddCommentMutation.field
# ...
end
To define a mutation, use GraphQL::Relay::Mutation.define
. Inside the block, you should configure:
name
, which will name the mutation field & derived typesinput_field
s, which will be applied to the derived InputObjectType
return_field
s, which will be applied to the derived ObjectType
resolve(-> (inputs, ctx) { ... })
, the mutation which will actually happenFor example:
AddCommentMutation = GraphQL::Relay::Mutation.define do
# Used to name derived types:
name "AddComment"
# Accessible from `input` in the resolve function:
input_field :postId, !types.ID
input_field :authorId, !types.ID
input_field :content, !types.String
# The result has access to these fields,
# resolve must return a hash with these keys
return_field :post, PostType
return_field :comment, CommentType
# The resolve proc is where you alter the system state.
resolve -> (inputs, ctx) {
post = Post.find(inputs[:postId])
comment = post.comments.create!(author_id: inputs[:authorId], content: inputs[:content])
{comment: comment, post: post}
}
end
Under the hood, GraphQL creates:
mutation
rootInputObjectType
for input valuesObjectType
for return valuesThe resolve proc:
inputs
, which is a hash whose keys are the ones defined by input_field
ctx
, which is the query context you passed with the context:
keywordreturn_field
sGlobalNodeIdentification.to_global_id
should receive the type name and object, not id
. (Or, maintain the "type_name, id
in, type_name, id
out" pattern?)Connection#object
=> Connection#collection
with deprecation#ruby
channel!graphql
Ruby gemgraphql-relay-js
JavaScript helpers for GraphQL and RelayFAQs
Unknown package
We found that graphql-relay demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.