Shape the response of your GraphQL queries, declaratively!
- Annotate a query with transformation rules
- Parse the query AST/String pre-request
- Transform the result post-response
const { parse } = require('@coderich/graphql-shape');
const { query, transform } = parse(annotatedQuery, [options]);
const data = await graphqlClient.request(query, args); // Your own client
const shaped = transform(data);
Annotations can be defined on any field that requires transformation. By default, the directive name is shape
and may be configured via options.name
when calling parse()
annotation | description | .parse() |
---|---|---|
@shape |
Transform an existing field in the GraphQL Schema | The annotation is removed from the field |
@_shape |
Define/Transform a non-existing field in the GraphQL Schema | The field is removed from the query |
Transformations are performed via annotation arguments where each key:value pair maps to a transformation name:args function call:
- Transformations are evaluated depth-first, from left-to-right, and in natural order
- Each transformation assigns it's return value to the annotated field (mutating it)
- Each transformation receives the current field value as it's first argument
query {
books @shape(self: "edges[*].node") {
edges {
node {
isbn
title
author @shape(self: "name") { name }
published: publishDate @shape(Date: "new", toISOString: null)
# Must specify "parent" because self/field/compound is made up (removed from query)
compound @_shape(parent: "$[isbn,title]", map: [{ toLowerCase: null }, { replace: [" ", "-"] }, { join: ":" }])
# Hoist all attributes and remove "detail"
detail @shape(hoist: false) {
summary
rating
}
}
}
}
}
{
"books": [
{
"isbn": "0-061-96436-0",
"title": "Moby Dick",
"author": "Herman Melville",
"published": "1851-10-18T04:56:02.000Z",
"compound": "0-061-96436-0:moby-dick",
"summary": "A legendary tale...",
"rating": "4.90"
},
"...",
]
}
Each transformation falls into 1 of the following lookup tables (referenced in order of preference):
name | arg | description |
---|---|---|
self |
JSONPath | JSONPath from the current field |
parent |
JSONPath | JSONPath from the field's parent |
root |
JSONPath | JSONPath from the root object |
map |
Transform(s) | Iterate field value(s) and apply transform(s) to each |
assign |
Value | Assign any value to the field |
rename |
Key | Rename the field key |
hoist |
Keep? | Hoist all field attributes to the parent and optionally keep field |
name | arg | description | eg. to produce |
---|---|---|---|
* |
null | Invoke a core object (no method) | String(value) |
* |
"new" | Instantiate a core object | new Array(value) |
* |
Method | Invoke a core object method | Date.now(value) |
Where
*
is one of[Object, Array, Number, String, Boolean, Symbol, Date, RegExp, Set, Map, WeakMap, WeakSet, Buffer, Math, JSON, Intl]
name | arg | description |
---|---|---|
push |
Value(s) | Push value(s); return array |
unshift |
Value(s) | Unshift value(s); return array |
in |
Value(s) | Boolean: if value in values |
nin |
Value(s) | Boolean: if value not in values |
eq |
[v1, r1, v2, r2, ..., v?] | Return first r# if value === v#; else v? |
ne |
[v1, r1, v2, r2, ..., v?] | Return first r# if value !== v#; else v? |
gt |
[v1, r1, v2, r2, ..., v?] | Return first r# if value > v#; else v? |
gte |
[v1, r1, v2, r2, ..., v?] | Return first r# if value >= v#; else v? |
lt |
[v1, r1, v2, r2, ..., v?] | Return first r# if value < v#; else v? |
lte |
[v1, r1, v2, r2, ..., v?] | Return first r# if value <= v#; else v? |
not |
null | Negate value |
or |
Value(s) | Boolean: if any value.concat(values) is truthy |
and |
Value(s) | Boolean: if all value.concat(values) are truthy |
add |
Number(s) | Add (sum) |
sub |
Number(s) | Subtract |
div |
Number(s) | Divide |
mul |
Number(s) | Multiply |
mod |
Number(s) | Modulus |
get |
Path(s) | Lodash.get like |
set |
[Key, Value] | Lodash.set like |
nvl |
Value(s) | Return first ! === null value from [value, ...values] |
uvl |
Value(s) | Return first ! === undefined value from [value, ...values] |
default |
Value(s) | Return first ! == null value from [value, ...values] |
filter |
RegExp | Filter an array of values that match a given RegExp |
pick |
Key(s) | Pick only the key(s) you want from the field/object |
pairs |
null | Transform flat array to 2D elements of 2 (pair) length |
flatten |
* | Flat.flatten like |
unflatten |
* | Flat.unflatten like |
Lastly, invoke value.key(...args)
if function; otherwise return value (noop).
You may define
(or redefine) a user transformation via:
GraphQLShape.define(name, function); // or
GraphQLShape.define(Map); // { name: function, name: function, ... }
Function signature:
(value, ...args) => newValue