@coderich/graphql-shape
v4.0.0
Published
Transform data using GraphQL
Maintainers
Readme
GraphQLShape
Shape the response of your GraphQL queries, declaratively!
Usage
- 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 (directives)
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 (annotation arguments)
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
Transformations (by example)
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"
},
"...",
]
}API
Each transformation falls into 1 of the following lookup tables (referenced in order of preference):
Lib
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
Core
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]
User
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
Value
Lastly, invoke value.key(...args) if function; otherwise return value (noop).
Extension
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
