@stoplight/graph v0.0.4
Graph (temporary name)
Graph is a PoC for what will be the key piece of tech at Stoplight to analyze projects, extract and normalize relevant structured data, and query that data in memory.
Currently, the focus is on OAS2, OAS3 specifications and, although it's not a top priority, GraphQL might be the next supported format.
Motivations
To date, many components in Stoplight have been dealing with API specification formats in different ways:
Prism, the mock server, is directly dealing with the source file of the only supported format so far (OAS2)
The Studio POC has an internal
Graphlibrary that's parsing an OpenAPI file and decomposing into a series of nodes that are then enriched with UI-specific elements (such as css classes, components to render, Icon providers)The Platform Designer is directly dealing with source files, and laying form widgets/ui on top of them.
All the various components in Stoplight have different opinions on the level of detail of a particular part of the document, as well as different way to deal with it. There's a lot of repeated code, as well as some unique parts depending on the piece of software dealing with the specification in particular.
Design Decisions
Node identification
Each node has three identifiers
id- locally unique identifier. Any two nodes withing one graph instance WILL have distinctid. Some nodes that belong to two different graph instances MAY have differentid.graphId- globally unique identifier. Any two nodes representing the same entity in a graph's hierarchy will have identicalgraphId, even if they belong to different graphs.graphIdis applicable to bothVirtual NodesandPhysical NodesabsolutePath- globally unique identifier that co-locating a node with a particular part of the file that node originates from. Similar tographIdbut only applicable toPhysical Nodes
Main node types
Physical Node- nodes that originate directly from a file and represent a portion of the file's content. E.g. it can be a nested element in a json file.Virtual Node- nodes that have no physical representation in a file, but were most likely derived from aPhysical Node. A good example of such node is theIHttpOperationnode.
Graph "Laws"
Physical Node's parent can only be ofPhysical Nodetype- There is a one-to-one relation between a
hookand anodeType
Aims
As we want to support more API Specification formats, it is clear that the current solution wouldn't have scaled. That's what Graph (temporary name) aims to solve by providing:
- A consistent set of elements and terminology to represent structured data, regardless of the format (OAS2, OAS3, GraphQL, next fancy api stuff).
- A consistent way to source this data (a directory on your computer, a postgres database, etc).
- A plugin system that hooks into the Graph construction lifecycle, and enables developers to add additional transformations, metadata, or even nodes to the graph.
- A simple query system to pull data out of the graph.
API
The final API is still being defined/polished. However this is the current one
Plugins List
FileSystem
Listens of any node of type directory and recursively walks the directory, adding all the files that encounters on the
way. It produces a node representing the root tree with a series of directory nodes with file leafes.
JSON
Listes for any node with content/type yaml|yml and parses the content using an internal parser. Returns an
IParseResult object that looks like this:
{
data: {},
pointers: {},
validations: [],
};YAML
Listes for any node of with content/type json and parses the content using an internal parser. Returns an
IParseResult object that looks like this:
{
data: {},
pointers: {},
validations: [],
};OpenAPI Specification 2.0
Listens for any PARSED node and will expand the graph with a series of nodes that's the result of the document
decomposition, representing the various parts.
Writing a plugin
The Graph has been designed to be expandible by default. Actually the graph itself can't really do anything, most of the features are provided by external plugins.
A plugin is a container of hooks and loaders. Let's explore these concepts:
Hooks
An hook intercepts various parts of the graph's life and gives the possibility to react with actions. Each hook will
need to specify a selector function — where you basically ask to be notified only for certain node types. Once such
test pass, your "event handlers" will be called for such nodes. (The reason why there are quotes is because they're not
really event handlers.)
selector?: (node: INodeInstance) => boolean; // Return true to express your interest in this kind of node
onWillCreateNode?: (node: Partial<Output>) => void; // The node is going to be added in the graph
onDidCreateNode?: (parent: Parent, opts: IGraphHookApi<Input, Output, Parent>) => Promise<any>; // The node has just been added in the graphThe HookApi provides some limited actions you can perform on the graph:
{
createNode: (node: Input, opts?: { parent?: Parent }) => Promise<Output>;
createEdge: (source: INodeInstance, destination: INodeInstance) => void;
getNodeById: (id: string) => INodeInstance | undefined;
}Loaders
Notes
This is a PoC — we haven't come up with the right API and data format yet.
Contributing
- Clone repo.
- Create / checkout
feature/{name},chore/{name}, orfix/{name}branch. - Install deps:
yarn. - Make your changes.
- Run tests:
yarn test.prod. - Stage relevant files to git.
- Commit:
yarn commit. NOTE: Commits that don't follow the conventional format will be rejected.yarn commitcreates this format for you, or you can put it together manually and then do a regulargit commit. - Push:
git push. - Open PR targeting the
developbranch.