Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ In our experience, nearly all of the stuff we need for the content of the docume

- `example`: When provided for a Scalar, Field or Argument, this value will be used as an "example" for the Field or Argument. It can be any value supported in JSON.
- `examples`: Same as `example`, but allows an Array of examples to be provided, from which one random one will be used during generation.
- `examples` (for Queries and Mutations): When provided for a Query or Mutation field, this allows you to provide custom operation-level examples that include the full query/mutation string, variables, and response. This overrides the auto-generated examples. See [Operation-Level Examples](#operation-level-examples) for details.
- `undocumented`: A Boolean value that can be provided on a Type, Field, Argument, Query or Mutation indicating that this item is _**not**_ to be included in the resulting output. Useful for 1-off hiding of things where the default was to show them.
- `documented`: Just like `undocumented`, except it _**will**_ include it in the resulting output. Useful for 1-off showing of things where the default was to hide them.

Expand Down Expand Up @@ -198,6 +199,133 @@ type MyType {
}
```

## Operation-Level Examples

SpectaQL supports custom operation-level examples for queries, mutations, and subscriptions. This allows you to provide complete examples including the query/mutation string, variables, and response, which will be used instead of the auto-generated examples.

### Supported Formats

Operation-level examples can be provided in two metadata formats:

#### Format 1: OBJECT Format (Recommended)

In this format, examples are nested under the Query or Mutation type in the OBJECT structure:

```json
{
"OBJECT": {
"Query": {
"fields": {
"getStorePlans": {
"documentation": {
"examples": [
{
"name": "Get active store plans",
"request": {
"query": "query getStorePlans($status: PlanStatus, $language: String) {\n getStorePlans(status: $status, language: $language) {\n id\n storeNumber\n status\n }\n}",
"variables": {
"status": "ACTIVE",
"language": "en-US"
}
},
"response": {
"data": {
"getStorePlans": [
{
"id": "SP-planId1",
"storeNumber": "00000000",
"status": "DRAFT"
}
]
}
}
}
]
}
}
}
}
}
}
```

#### Format 2: Alternative Format (Queries/Mutations)

In this format, examples are provided at the top level under `queries` or `mutations`:

```json
{
"queries": {
"getStorePlans": {
"examples": [
{
"name": "Get active store plans",
"query": "query getStorePlans($status: PlanStatus) { getStorePlans(status: $status) { id } }",
"variables": {
"status": "ACTIVE"
},
"response": {
"data": {
"getStorePlans": [{ "id": "SP-planId1" }]
}
}
}
]
}
},
"mutations": {
"createStorePlan": {
"examples": [
{
"name": "Create plan with one focus area",
"request": {
"query": "mutation createStorePlan($createPlan: CreatePlanInput!) { createStorePlan(createPlan: $createPlan) { id } }",
"variables": {
"createPlan": {
"focusAreas": [
{
"id": "customer_connection",
"currentStateAndGap": "Current score 32, target 45"
}
]
}
}
},
"response": {
"data": {
"createStorePlan": {
"id": "SP-new-plan-id"
}
}
}
}
]
}
}
}
```

### Example Structure

Each example supports two formats for the request:

1. **Request wrapper format**: `{ request: { query, variables }, response }`
2. **Direct format**: `{ query, variables, response }`

Both formats are supported and will be normalized automatically. The following fields are supported:

- `name` (optional): A descriptive name for the example
- `query` or `request.query`: The GraphQL query/mutation string (required)
- `variables` or `request.variables` (optional): Variables object
- `response` (optional): Expected response object, which can include `data` and `errors`

### Behavior

- If custom examples are provided, SpectaQL will use the first example instead of auto-generating one
- If no custom examples are provided, SpectaQL will fall back to auto-generating examples (existing behavior)
- Multiple examples can be provided; all are stored but only the first is currently used in the generated documentation
- Missing `variables` or `response` fields are handled gracefully

## Dynamic Example Generators

In addition to being able to use any static examples you've provided, SpectaQL also supports dynamically generating examples for Scalars, Fields and Arguments. When it comes time to generate an example, SpectaQL can pass all the necessary information about the Scalar, Field or Argument to your generator in order for it to decide what the example should look like. See the included [example generator](https://github.com/anvilco/spectaql/blob/main/examples/customizations/examples/index.js) to see how it works.
Expand Down
21 changes: 21 additions & 0 deletions examples/data/metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,27 @@
"nonReqQueryArg": {
"documentation": { "example": "Metadata example of `nonReqQueryArg`" }
}
},
"documentation": {
"examples": [
{
"name": "Example query with custom example",
"request": {
"query": "query myQuery($nonReqQueryArg: String) { myQuery(nonReqQueryArg: $nonReqQueryArg) { id name } }",
"variables": {
"nonReqQueryArg": "exampleValue"
}
},
"response": {
"data": {
"myQuery": {
"id": "1",
"name": "Example Result"
}
}
}
}
]
}
}
}
Expand Down
11 changes: 4 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@
"npm": ">=7"
},
"main": "index.js",
"bin": {
"spectaql": "./bin/spectaql.js"
},
"bin": "./bin/spectaql.js",
"files": [
"package.json",
"README.md",
Expand Down Expand Up @@ -146,12 +144,11 @@
"resolutions": {
"grunt-compile-handlebars/lodash.merge": "^4.6.2",
"grunt-prettify/globby": "^11.0.4",
"grunt-prettify/underscore.string": "^3.3.5",
"**/lodash": "^4.17.20",
"**/resolve/path-parse": "^1.0.7"
"grunt-prettify/underscore.string": "^3.3.5"
},
"lint-staged": {
"*.js": "eslint --cache --fix",
"*.{js,css,md}": "prettier --write"
}
},
"packageManager": "[email protected]+sha512.f95ce356460e05be48d66401c1ae64ef84d163dd689964962c6888a9810865e39097a5e9de748876c2e0bf89b232d583c33982773e9903ae7a76257270986538"
}
6 changes: 6 additions & 0 deletions src/spectaql/build-schemas.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from './graphql-loaders'

import {
loadMetadataFromFile,
addMetadataFromFile,
addMetadataFromDirectables,
} from './metadata-loaders'
Expand Down Expand Up @@ -113,7 +114,11 @@ export function buildSchemas(opts) {
throw new Error('Problem with Introspection Query Response')
}

let originalMetadata = null
if (metadataFile) {
// Load original metadata for access to queries/mutations format
originalMetadata = loadMetadataFromFile({ pathToFile: metadataFile })

addMetadataFromFile({
...introspectionOptions,
pathToFile: metadataFile,
Expand All @@ -137,6 +142,7 @@ export function buildSchemas(opts) {
return {
introspectionResponse: augmentedIntrospectionResponse,
graphQLSchema,
originalMetadata,
}
}

Expand Down
11 changes: 10 additions & 1 deletion src/spectaql/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,16 @@ async function run(opts) {

const { protocol, host, pathname } = url.parse(urlToParse)

const { introspectionResponse, graphQLSchema } = buildSchemas(opts)
const {
introspectionResponse,
graphQLSchema,
originalMetadata,
} = buildSchemas(opts)

// Store original metadata in specData for access in preProcess
if (originalMetadata) {
spec.originalMetadata = originalMetadata
}

// Figure out what data arranger to use...the default one, or the one from the theme
const customDataArrangerSuffixThatExists = [
Expand Down
Loading