From a1c915a9db62e55f11845525259ace108ca4c23a Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Wed, 8 Dec 2021 14:16:58 +0300 Subject: [PATCH] Add documentation for Omnigraph --- packages/loaders/README.md | 114 ++++++++++++++++++ packages/loaders/json-schema/README.md | 45 +++++++ packages/loaders/json-schema/src/bundle.ts | 5 + packages/loaders/raml/README.md | 12 ++ packages/loaders/raml/src/bundle.ts | 4 + .../getJSONSchemaOptionsFromRAMLOptions.ts | 5 + .../raml/src/loadGraphQLSchemaFromRAML.ts | 7 ++ 7 files changed, 192 insertions(+) create mode 100644 packages/loaders/README.md create mode 100644 packages/loaders/json-schema/README.md create mode 100644 packages/loaders/raml/README.md diff --git a/packages/loaders/README.md b/packages/loaders/README.md new file mode 100644 index 0000000000000..dc2b3c8a21e3c --- /dev/null +++ b/packages/loaders/README.md @@ -0,0 +1,114 @@ +# 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: + ./src/modules/my-sdk/schema.graphql: + # This plugin outputs the generated schema as a human-readable SDL format + - schema-ast + ./src/modules/my-sdk/sdk.ts: + # This plugin creates an SDK for each operation found in `documents` + - typescript-jit-sdk +``` + +#### Example: GraphQL JIT SDK Usage with Omnigraph Bundle + +```ts +// Get GraphQL JIT SDK Factory +import { getSdk } from './sdk'; +// Load our schema from bundle +import loadSchemaFromBundle from './load-schema-from-bundle' + +export async function getOmnigraphSDK() { + const schema = await loadSchemaFromBundle(); + // JIT SDK takes local `GraphQLSchema` instance + return getSdk(schema) +} +``` + +### 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. diff --git a/packages/loaders/json-schema/README.md b/packages/loaders/json-schema/README.md new file mode 100644 index 0000000000000..7024082219f92 --- /dev/null +++ b/packages/loaders/json-schema/README.md @@ -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 +``` diff --git a/packages/loaders/json-schema/src/bundle.ts b/packages/loaders/json-schema/src/bundle.ts index a591b917b2830..32ba07588b8db 100644 --- a/packages/loaders/json-schema/src/bundle.ts +++ b/packages/loaders/json-schema/src/bundle.ts @@ -68,6 +68,11 @@ export interface JSONSchemaLoaderBundleToGraphQLSchemaOptions { logger?: Logger; operationHeaders?: Record; } + +/** + * Generates a local GraphQLSchema instance from + * previously generated JSON Schema bundle + */ export async function getGraphQLSchemaFromBundle( { name, diff --git a/packages/loaders/raml/README.md b/packages/loaders/raml/README.md new file mode 100644 index 0000000000000..81e44d1c1a68d --- /dev/null +++ b/packages/loaders/raml/README.md @@ -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} +``` diff --git a/packages/loaders/raml/src/bundle.ts b/packages/loaders/raml/src/bundle.ts index 865250e0492d3..99816f0cf0785 100644 --- a/packages/loaders/raml/src/bundle.ts +++ b/packages/loaders/raml/src/bundle.ts @@ -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 { const { operations, baseUrl, cwd, fetch } = await getJSONSchemaOptionsFromRAMLOptions(ramlLoaderOptions); return createJSONSchemaLoaderBundle(name, { ...ramlLoaderOptions, baseUrl, operations, cwd, fetch }); diff --git a/packages/loaders/raml/src/getJSONSchemaOptionsFromRAMLOptions.ts b/packages/loaders/raml/src/getJSONSchemaOptionsFromRAMLOptions.ts index 6ac0de44a45b8..7d95b94220e91 100644 --- a/packages/loaders/raml/src/getJSONSchemaOptionsFromRAMLOptions.ts +++ b/packages/loaders/raml/src/getJSONSchemaOptionsFromRAMLOptions.ts @@ -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(), diff --git a/packages/loaders/raml/src/loadGraphQLSchemaFromRAML.ts b/packages/loaders/raml/src/loadGraphQLSchemaFromRAML.ts index 901bfb77fe928..a60a20c66f41e 100644 --- a/packages/loaders/raml/src/loadGraphQLSchemaFromRAML.ts +++ b/packages/loaders/raml/src/loadGraphQLSchemaFromRAML.ts @@ -2,9 +2,16 @@ import { JSONSchemaLoaderOptions, loadGraphQLSchemaFromJSONSchemas } from '@omni import { getJSONSchemaOptionsFromRAMLOptions } from './getJSONSchemaOptionsFromRAMLOptions'; export interface RAMLLoaderOptions extends Partial { + // 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, {