@coderich/graphql-shape
Advanced tools
Comparing version 1.1.0 to 2.0.0
{ | ||
"name": "@coderich/graphql-shape", | ||
"main": "src/GraphQLShape.js", | ||
"version": "1.1.0", | ||
"version": "2.0.0", | ||
"publishConfig": { | ||
@@ -6,0 +6,0 @@ "access": "public" |
const Util = require('@coderich/util'); | ||
const { JSONPath } = require('jsonpath-plus'); | ||
const { Kind, visit, parse, print } = require('graphql'); | ||
const functions = require('./functions'); | ||
const core = require('./core'); | ||
class GraphQLShape { | ||
module.exports = class GraphQLShape { | ||
static define(key, fn) { | ||
GraphQLShape.functions[key] = fn; | ||
functions[key] = fn; | ||
} | ||
@@ -49,10 +51,10 @@ | ||
if (name === options.name) { | ||
const args = node.arguments.reduce((prev, arg) => { | ||
const ops = node.arguments.map((arg) => { | ||
const key = arg.name.value; | ||
const value = GraphQLShape.#resolveNodeValue(arg.value); | ||
return Object.assign(prev, { [key]: value }); | ||
}, {}); | ||
return { [key]: value }; | ||
}); | ||
const $paths = isFragment ? fpaths : paths; | ||
target.push({ ...args, key: $paths.join('.') }); | ||
target.push({ key: $paths.join('.'), ops }); | ||
} | ||
@@ -65,3 +67,2 @@ break; | ||
else paths.push(key); | ||
// if (key === 'state') console.log(isFragment, node); | ||
break; | ||
@@ -78,5 +79,2 @@ } | ||
switch (node.kind) { | ||
case Kind.OPERATION_DEFINITION: { | ||
break; | ||
} | ||
case Kind.FRAGMENT_DEFINITION: { | ||
@@ -108,13 +106,8 @@ isFragment = false; | ||
// Preprocess transforms meta-data (for better performance) | ||
transforms.reverse().forEach((transform) => { | ||
if (transform.map) { | ||
transform.map = Util.ensureArray(transform.map).map((mix) => { | ||
const [, name = mix, args = ''] = mix.match(/(\w+)\s*\((.*?)\)|(.*)/); | ||
return { name, args: args.split(',').map(el => el.trim()) }; | ||
}); | ||
} | ||
}); | ||
return { query, transforms, fragments }; | ||
return { | ||
query, | ||
fragments, | ||
transforms: transforms.reverse(), // We must reverse the order since we do depth-first traversal | ||
transform: data => GraphQLShape.transform(data, transforms), | ||
}; | ||
} | ||
@@ -126,29 +119,37 @@ | ||
// Apply transformations (in place) | ||
transforms.forEach(({ key, path = '$', ...rest }) => { | ||
Util.pathmap(key, data, (json, info) => { | ||
let value = JSONPath({ path, json, wrap: false }); | ||
transforms.forEach(({ key, ops = [] }) => { | ||
// We assign data here because it's possible to modify the root/data itself (key: '') | ||
data = Util.pathmap(key, data, (value, info) => { | ||
const values = [value]; | ||
// Apply the rest (in the order they are defined!) | ||
if (value != null) { | ||
Object.entries(rest).forEach(([fn, mixed]) => { | ||
switch (fn) { | ||
case 'map': { | ||
Util.map(mixed, ({ name, args }) => { | ||
value = Util.map(value, v => GraphQLShape.#resolveValueFunction(v, name, args)); | ||
}); | ||
break; | ||
} | ||
case 'hoist': { | ||
if (!mixed) hoisted.push(info); | ||
Object.assign(info.parent, value); | ||
break; | ||
} | ||
default: { | ||
value = GraphQLShape.#resolveValueFunction(value, fn, mixed); | ||
break; | ||
} | ||
ops.forEach((op) => { | ||
const [[fn, mixed]] = Object.entries(op); | ||
switch (fn) { | ||
case 'self': case 'parent': case 'root': { | ||
const json = [value, info.parent, data][['self', 'parent', 'root'].indexOf(fn)]; | ||
value = Util.isPlainObjectOrArray(json) ? JSONPath({ path: mixed, json, wrap: false }) : json; | ||
break; | ||
} | ||
}); | ||
} | ||
case 'map': { | ||
Util.map(mixed, (el) => { | ||
const [[fnName, args]] = Object.entries(el); | ||
value = Util.map(value, v => GraphQLShape.#resolveValueFunction(v, values, fnName, args)); | ||
}); | ||
break; | ||
} | ||
case 'hoist': { | ||
if (!mixed) hoisted.push(info); | ||
Object.assign(info.parent, value); | ||
break; | ||
} | ||
default: { | ||
value = GraphQLShape.#resolveValueFunction(value, values, fn, mixed); | ||
break; | ||
} | ||
} | ||
values.push(value); | ||
}); | ||
// Set the value back | ||
@@ -165,5 +166,20 @@ return value; | ||
static #resolveValueFunction(v, fn, ...args) { | ||
args = args.flat(); | ||
return Object.prototype.hasOwnProperty.call(GraphQLShape.functions, fn) ? GraphQLShape.functions[fn](v, ...args) : v[fn](...args); | ||
static #resolveValueFunction(value, values, fn, ...args) { | ||
// Argument replacement variables | ||
args = Util.ensureArray(args).flat().map((arg) => { | ||
const match = `${arg}`.match(/\$(\d)/); | ||
return match ? values[match[1]] : arg; | ||
}); | ||
// Core functions have a special syntax | ||
if (core[fn]) { | ||
const method = args.shift(); | ||
if (method === 'new') return new core[fn](value, ...args); | ||
if (method === null) return core[fn](value, ...args); | ||
return core[fn][method](value, ...args); | ||
} | ||
if (functions[fn]) return functions[fn](value, ...args); | ||
if (value?.[fn]) return value[fn](...args); | ||
return value; | ||
} | ||
@@ -183,11 +199,2 @@ | ||
} | ||
} | ||
GraphQLShape.functions = { | ||
keys: v => Object.keys(v), | ||
values: v => Object.values(v), | ||
entries: v => Object.entries(v), | ||
fromEntries: v => Object.fromEntries(v), | ||
}; | ||
module.exports = GraphQLShape; |
8426
4
234