Skip to content

Commit

Permalink
Add documentation for Omnigraph
Browse files Browse the repository at this point in the history
  • Loading branch information
ardatan committed Dec 8, 2021
1 parent 4cd8054 commit faf42fd
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 0 deletions.
99 changes: 99 additions & 0 deletions packages/loaders/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Omnigraph

Omnigraph is a set of libraries and tools helps you generate local `GraphQLSchema` instances from different API Schema specifications such as JSON Schema, MySQL, SOAP, OpenAPI and RAML.

You can consume this `GraphQLSchema` inside any tools in GraphQL Ecosystem such as GraphQL Config, GraphQL Code Generator and GraphQL ESLint. You can either bind it to a GraphQL Server like Envelop, Express-GraphQL, GraphQL Helix, Apollo Server or GraphQL Yoga.

### GraphQL Config
GraphQL Config is a way of specifying your GraphQL Project in a standard way so the most of tools around GraphQL Ecosystem can recognize your project such as VSCode GraphQL Extension, GraphQL ESLint and GraphQL Code Generator
```yml
schema: ./packages/server/modules/**/*.graphql # Backend
documents: ./packages/client/pages/**/*.graphql # Frontend
```
Omnigraph acts like as a custom loader with GraphQL Config
```yml
schema:
MyOmnigraph:
loader: "@omnigraph/openapi" # This provides GraphQLSchema to GraphQL Config
source: https://my-openapi-source.com/openapi.yml
operationHeaders:
Authorization: Bearer {context.token}
```
### GraphQL Code Generator
Let's say we want to create a type-safe SDK from the generated schema using GraphQL Code Generator and remember GraphQL Code Generator has nothing to do except GraphQL so Omnigraph helps GraphQL Config to consume the nonGraphQL source as GraphQL then it provides the schema and operations to GraphQL Code Generator;
Like any other GraphQL project. We can use `extensions.codegen`
```yml
schema:
MyOmnigraph:
loader: "@omnigraph/openapi" # This provides GraphQLSchema to GraphQL Config
source: https://my-openapi-source.com/openapi.yml
documents: ./src/modules/my-sdk/operations/*.graphql
extensions:
codegen:
generates:
schema.graphql:
# This plugin outputs the generated schema as a human-readable SDL format
- schema-ast
sdk.ts:
# This plugin creates an SDK for each operation found in `documents`
- typescript-jit-sdk
```
### Bundling
As you can see above, Omnigraph is able to download sources via HTTP on runtime. But this has some downsides. The remote API might be down or we might have some bandwidth concerns to avoid. So Omnigraph has "Bundling" feature that helps to store the downloaded and resolved resources once ahead-of-time. But this needs some extra code files with programmatic usage by splitting buildtime and runtime configurations;
We can create a script called `generate-bundle.js` and every time we run `npm run generate-bundle` it will download the sources and generate the bundle.
```js
import { createBundle } from '@omnigraph/openapi'
import { writeFileSync } from 'fs'
async function main() {
const createdBundle = await createBundle('my-omnigraph', {
// We don't need operation specific configuration like `operationHeaders` here
// We will use those later
source: 'https://my-openapi-source.com/openapi.yml',
schemaHeaders: {
// Some headers needed to download resources
}
});

// Save it to the disk
writeFileSync('./bundle.json', JSON.stringify(createdBundle));
}

main().catch((e) => {
console.error(e);
process.exit(1);
});
```

Then we can load the schema from another file called `load-schema-from-bundle.js`
```js
import { getGraphQLSchemaFromBundle } from '@omnigraph/openapi'
// Load it as a module so bundlers can recognize and add it to the bundle
import omnigraphBundle from './bundle.json'

export default function loadSchemaFromBundle() {
return getGraphQLSchemaFromBundle(omnigraphBundle, {
// Now use the operation specific configuration here
operationHeaders: {
'Content-Type': 'application/json',
'Authorization': 'Bearer {context.apiToken}'
}
})
}
```
And use our new loader in GraphQL Config by replacing `loader`

```yml
schema:
MyOmnigraph:
loader: ./load-schema-from-bundle.js # This provides GraphQLSchema to GraphQL Config
```
### String interpolation on runtime
You can have dynamic values in `operationHeaders` by using interpolation pattern;
`{context.apiToken}` or `{env.BASE_URL}`
In this case, `context` refers to the context you executed your schema with.
45 changes: 45 additions & 0 deletions packages/loaders/json-schema/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
## JSON Schema (@omnigraph/json-schema)

This package generates GraphQL Schema from JSON Schema and sample JSON request and responses. You can define your root field endpoints like below in your GraphQL Config for example;

```yml
schema:
myOmnigraph:
loader: '@omnigraph/json-schema'
# The base URL of your following endpoints
baseUrl: http://www.my-api.com
# The headers will be used while downloading JSON Schemas and Samples
schemaHeaders:
Content-Type: application/json
# The headers will be used while making requests via HTTP
operationHeaders:
Accept: application/json
Content-Type: application/json
Authorization: Bearer TOKEN
# In case of error, the path of the error message shown to the user
errorMessage: error.message
# Endpoints
operations:
# Root Type
- type: Query
# The Field Name in the specified Root Type
field: me
# Description
description: My Profile
# The relative URL to the defined `baseUrl`
path: /user/{args.id} # args will generate an argument for the field and put it here automatically
# The HTTP method
method: GET
# The path of the relevant JSON Schema for the return type
responseSchema: ./json-schemas/user.json#/definitions/User
- type: Mutation
field: createUser
path: /user
method: PUT
# A JSON sample file for the request body
requestSample: ./json-samples/user-input.json
# GraphQL type name for the request body
requestTypeName: CreateUpdateUser
# A JSON sample file for the response body and it points to the specific definition using JSON Pointer pattern
responseSchema: ./json-schemas/user.json#/definitions/User
```
5 changes: 5 additions & 0 deletions packages/loaders/json-schema/src/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ export interface JSONSchemaLoaderBundleToGraphQLSchemaOptions {
logger?: Logger;
operationHeaders?: Record<string, string>;
}

/**
* Generates a local GraphQLSchema instance from
* previously generated JSON Schema bundle
*/
export async function getGraphQLSchemaFromBundle(
{
name,
Expand Down
12 changes: 12 additions & 0 deletions packages/loaders/raml/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
## RAML (@omnigraph/raml)

This package generates `GraphQLSchema` instance from **RAML API Document** (`.raml`) file located at a URL or FileSystem by resolving the JSON Schema dependencies. It uses `@omnigraph/json-schema` by generating the necessary configuration.

```yml
schema:
myOmnigraph:
loader: "@omnigraph/raml"
ramlFilePath: https://www.my-api.com/api.raml
operationHeaders:
Authorization: Bearer {context.apiToken}
```
4 changes: 4 additions & 0 deletions packages/loaders/raml/src/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import {
import { getJSONSchemaOptionsFromRAMLOptions } from './getJSONSchemaOptionsFromRAMLOptions';
import { RAMLLoaderOptions } from './types';

/**
* Creates a bundle by downloading and resolving the internal references once
* to load the schema locally later
*/
export async function createBundle(name: string, ramlLoaderOptions: RAMLLoaderOptions): Promise<RAMLLoaderBundle> {
const { operations, baseUrl, cwd, fetch } = await getJSONSchemaOptionsFromRAMLOptions(ramlLoaderOptions);
return createJSONSchemaLoaderBundle(name, { ...ramlLoaderOptions, baseUrl, operations, cwd, fetch });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ import { fetch as crossUndiciFetch } from 'cross-undici-fetch';
import toJsonSchema from 'to-json-schema';
import { RAMLLoaderOptions } from './types';

/**
* Generates the options for JSON Schema Loader
* from RAML Loader options by extracting the JSON Schema references
* from RAML API Document
*/
export async function getJSONSchemaOptionsFromRAMLOptions({
ramlFilePath,
cwd: ramlFileCwd = process.cwd(),
Expand Down
7 changes: 7 additions & 0 deletions packages/loaders/raml/src/loadGraphQLSchemaFromRAML.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,16 @@ import { JSONSchemaLoaderOptions, loadGraphQLSchemaFromJSONSchemas } from '@omni
import { getJSONSchemaOptionsFromRAMLOptions } from './getJSONSchemaOptionsFromRAMLOptions';

export interface RAMLLoaderOptions extends Partial<JSONSchemaLoaderOptions> {
// The URL or FileSystem path to the RAML API Document.
ramlFilePath: string;
}

/**
* Creates a local GraphQLSchema instance from a RAML API Document.
* Everytime this function is called, the RAML file and its dependencies will be resolved on runtime.
* If you want to avoid this, use `createBundle` function to create a bundle once and save it to a storage
* then load it with `loadGraphQLSchemaFromBundle`.
*/
export async function loadGraphQLSchemaFromRAML(name: string, options: RAMLLoaderOptions) {
const extraJSONSchemaOptions = await getJSONSchemaOptionsFromRAMLOptions(options);
return loadGraphQLSchemaFromJSONSchemas(name, {
Expand Down

1 comment on commit faf42fd

@vercel
Copy link

@vercel vercel bot commented on faf42fd Dec 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.