Skip to content

Commit

Permalink
fix(resolvers): rename values to resolvers
Browse files Browse the repository at this point in the history
  • Loading branch information
jfcieslak committed Nov 7, 2019
1 parent 021ddd4 commit 9e72467
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 71 deletions.
27 changes: 15 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ By default, this parser assumes a simple structural convention for writing your

2. Use nested objects for embedded queries, like so:

`{ nested: { level1: { level2: { _NE: 10 } } } }` will parse to: `{ 'nested.level1.level2': { $ne: 10 } }`
`{ nested: { level1: { level2: { _NE: 10 } } } }` will parse to:
`{ 'nested.level1.level2': { $ne: 10 } }`


## Usage:
Expand All @@ -44,7 +45,7 @@ import GQLMongoQuery from 'graphql-mongo-query'
// Example arguments:
const query = { _OR: [{ num: 10 }, { nested: {property: 'X'} }] }

const parser = GQLMongoQuery(<keywords?>, <values?>, <merge?>)
const parser = GQLMongoQuery(<keywords?>, <resolvers?>, <merge?>)
const MongoFilters = parser(query)

// MongoFilters will equal to:
Expand All @@ -58,7 +59,7 @@ import GQLMongoQuery from 'graphql-mongo-query/class'
// Example arguments:
const query = { _OR: [{ num: 10 }, { nested: {property: 'X'} }] }

const parser = new GQLMongoQuery(<keywords?>, <values?>, <merge?>)
const parser = new GQLMongoQuery(<keywords?>, <resolvers?>, <merge?>)
const MongoFilters = parser.buildFiltersquerys)

// MongoFilters will equal to:
Expand All @@ -73,7 +74,7 @@ const MongoFilters = parser.buildFiltersquerys)
#### `keywords` (optional)
Maps the query keywords to mongo keywords.
Maps the query keywords to mongo keywords. Every key in this object will be replaced by corresponding value.
```javascript
// Defaults:
Expand Down Expand Up @@ -107,17 +108,17 @@ Maps the query keywords to mongo keywords.
}
```
#### `values` (optional)
#### `resolvers` (optional)
An object mapping specified query keys to functions that will resolve their value.
An object mapping specified query keys to custom resolver functions that will return a new key and value.
These resolver functions take `parent` object as the only parameter, and should return a value that will replace that parent. Parent object is a single single `{key: value}` pair.
The parser will iterate through a query, and when finding a key that matches, it will replace the entire `{key: value}` pair with the result of the function.
The parser will iterate through a query, and when finding a key that matches, it will replace the entire `{key: value}` pair with the result of the resolver function.
```typescript
// Examples:
const values = {
const resolvers = {
test1(parent) {
return {test1: !!parent.test1}
},
Expand All @@ -140,7 +141,7 @@ const values = {
```
#### `merge` (optional, default: `true`)
If set to true, `keywords` and `values` from options will be merged with defaults. Otherwise they will overwrite the defaults.
If set to true, `keywords` and `resolvers` from options will be merged with defaults. Otherwise they will overwrite the defaults.
## Examples:
Expand All @@ -149,13 +150,15 @@ For examples checkout the [tests](https://github.com/jfcieslak/graphql-mongo-que
An example of a complex Input filter and it’s parsed value:
```javascript
// arg received from graphQL input:
const values = {
// resolvers option object
const resolvers = {
dateField(parent) {
parent.dateField = new Date(parent.dateField)
return parent
}
}

// query received from graphQL input:
const query = {
_OR: [
{ field1: { _NE: 'not me' } },
Expand All @@ -166,7 +169,7 @@ const query = {
}

// Parsed filter:
const filter = GQLMongoQuery(null, values)querys)
const filter = GQLMongoQuery(null, resolvers)querys)
expect(filter).toEqual({
$or: [
{ field1: { $ne: 'not me' } },
Expand Down
20 changes: 10 additions & 10 deletions src/class.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
export type ArgType = 'OPERATOR' | 'COMPUTED' | 'PRIMITIVE' | 'ARRAY' | 'NESTED' | 'FLAT'

export type ValueParser = (parent: any) => any
export type Resolver = (parent: any) => any

export interface Values {
[key: string]: ValueParser
export interface Resolvers {
[key: string]: Resolver
}

export interface Keywords {
Expand Down Expand Up @@ -37,7 +37,7 @@ const defaultKeywords: Keywords = {
_MAX_DISTANCE: '$maxDistance',
_MIN_DISTANCE: '$minDistance'
}
const defaultValues: Values = {}
const defaultValues: Resolvers = {}

const primitives = [
'string',
Expand All @@ -51,15 +51,15 @@ const primitives = [

export default class GQLMongoQuery {
keywords: Keywords
values: Values
resolvers: Resolvers

constructor(
keywords: Keywords = defaultKeywords,
values: Values = defaultValues,
resolvers: Resolvers = defaultValues,
merge: boolean = true
) {
this.keywords = merge ? { ...defaultKeywords, ...keywords } : keywords
this.values = merge ? { ...defaultValues, ...values } : values
this.resolvers = merge ? { ...defaultValues, ...resolvers } : resolvers
}

private isOperator(key): boolean {
Expand All @@ -72,7 +72,7 @@ export default class GQLMongoQuery {
}

private isComputable(key): boolean {
return Object.keys(this.values).includes(key)
return Object.keys(this.resolvers).includes(key)
}

private isNested(obj): boolean {
Expand All @@ -88,9 +88,9 @@ export default class GQLMongoQuery {
}

private computedValue(args) {
for (const valueKey in this.values) {
for (const valueKey in this.resolvers) {
if (args[valueKey] !== undefined) {
return this.values[valueKey](args)
return this.resolvers[valueKey](args)
}
}
}
Expand Down
80 changes: 40 additions & 40 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
export type ArgType = 'OPERATOR' | 'COMPUTED' | 'PRIMITIVE' | 'ARRAY' | 'NESTED' | 'FLAT'

export type ValueParser = (parent: any) => any
export type Resolver = (parent: any) => any

export interface Values {
[key: string]: ValueParser
export interface Resolvers {
[key: string]: Resolver
}

export interface Keywords {
Expand Down Expand Up @@ -47,7 +47,7 @@ export const defaultKeywords: Keywords = {
_MAX_DISTANCE: '$maxDistance',
_MIN_DISTANCE: '$minDistance'
}
const defaultValues: Values = {}
const defaultValues: Resolvers = {}

export const isOperator = (key: string, keywords: object): boolean => {
return Object.keys(keywords).includes(key)
Expand All @@ -58,18 +58,18 @@ export const isPrimitive = (val): boolean => {
else return false
}

export const isComputable = (key: string, values: object): boolean => {
return Object.keys(values).includes(key)
export const isComputable = (key: string, resolvers: object): boolean => {
return Object.keys(resolvers).includes(key)
}

export const isNested = (value, keywords: object, values: object): boolean => {
export const isNested = (value, keywords: object, resolvers: object): boolean => {
if (typeof value !== 'object') return false
let nested = false
for (const key in value) {
if (
!isOperator(key, keywords) &&
!isPrimitive(value[key]) &&
!isComputable(key, values)
!isComputable(key, resolvers)
) {
nested = true
break
Expand All @@ -78,83 +78,83 @@ export const isNested = (value, keywords: object, values: object): boolean => {
return nested
}

export const computedValue = (parent: object, values: object) => {
for (const valueKey in values) {
export const computedValue = (parent: object, resolvers: object) => {
for (const valueKey in resolvers) {
if (parent[valueKey] !== undefined) {
return values[valueKey](parent)
return resolvers[valueKey](parent)
}
}
}

export const argType = (
keywords: object,
values: object,
resolvers: object,
key?: string,
val?: string
): ArgType => {
if (isOperator(key, keywords)) return 'OPERATOR'
else if (isComputable(key, values)) return 'COMPUTED'
else if (isComputable(key, resolvers)) return 'COMPUTED'
else if (isPrimitive(val)) return 'PRIMITIVE'
else if (Array.isArray(val)) return 'ARRAY'
else if (isNested(val, keywords, values)) return 'NESTED'
else if (isNested(val, keywords, resolvers)) return 'NESTED'
else if (typeof val === 'object') return 'FLAT'
else return null
}

const parseNested = (
keywords: Keywords,
values: Values,
resolvers: Resolvers,
key: string,
val,
lastResult = {}
) => {
if (isComputable(key, values)) {
return buildFilters(val, key, keywords, values)
if (isComputable(key, resolvers)) {
return buildFilters(val, key, keywords, resolvers)
}
let result = lastResult

for (const k in val) {
let isFinal = false

// COMPUTABLE VALUE
if (isComputable(k, values)) {
result = { ...result, ...buildFilters(val[k], k, keywords, values) }
if (isComputable(k, resolvers)) {
result = { ...result, ...buildFilters(val[k], k, keywords, resolvers) }
return result
}

// OPERATOR
if (isOperator(k, keywords)) {
result = { ...result, [key]: buildFilters(val, key, keywords, values) }
result = { ...result, [key]: buildFilters(val, key, keywords, resolvers) }
return result
}

const subkey = key + '.' + k
const subval = val[k]

// subval is COMPUTABLE VALUE
if (isComputable(subkey, values)) {
result = { ...result, ...buildFilters(subval, subkey, keywords, values) }
if (isComputable(subkey, resolvers)) {
result = { ...result, ...buildFilters(subval, subkey, keywords, resolvers) }
isFinal = true
}

// subval is a PRIMITIVE
else if (isPrimitive(subval)) {
result[subkey] = buildFilters(subval, null, keywords, values)
result[subkey] = buildFilters(subval, null, keywords, resolvers)
isFinal = true
}

// subval is NESTED
else {
for (const sk in subval) {
const t = argType(keywords, values, sk, subval)
const t = argType(keywords, resolvers, sk, subval)
if (t !== 'NESTED' && t !== 'FLAT') {
result[subkey] = buildFilters(subval, null, keywords, values)
result[subkey] = buildFilters(subval, null, keywords, resolvers)
isFinal = true
break
}
}
}
if (!isFinal) parseNested(keywords, values, subkey, subval, result)
if (!isFinal) parseNested(keywords, resolvers, subkey, subval, result)
}
return result
}
Expand All @@ -163,11 +163,11 @@ export const buildFilters = (
args,
parentKey?: string,
keywords: Keywords = {},
values: Values = {}
resolvers: Resolvers = {}
) => {
// PARENT IS A COMPUTABLE VALUE
if (isComputable(parentKey, values)) {
return computedValue({ [parentKey]: args }, values)
if (isComputable(parentKey, resolvers)) {
return computedValue({ [parentKey]: args }, resolvers)
}

// NO PARENT AND ARGS IS A DIRECT VALUE
Expand All @@ -181,11 +181,11 @@ export const buildFilters = (
for (const key in args) {
if (!filters) filters = {}
const val = args[key]
const t = argType(keywords, values, key, val)
const t = argType(keywords, resolvers, key, val)

// COMPUTED VALUE
if (isComputable(key, values)) {
const computed = buildFilters(val, key, keywords, values)
if (isComputable(key, resolvers)) {
const computed = buildFilters(val, key, keywords, resolvers)
filters = {
...filters,
...computed
Expand All @@ -195,35 +195,35 @@ export const buildFilters = (
else if (t === 'OPERATOR') {
const keyword = keywords[key]
if (Array.isArray(val)) {
filters[keyword] = val.map(v => buildFilters(v, null, keywords, values))
filters[keyword] = val.map(v => buildFilters(v, null, keywords, resolvers))
} else {
filters[keyword] = buildFilters(val, null, keywords, values)
filters[keyword] = buildFilters(val, null, keywords, resolvers)
}
}
// NESTED QUERY
else if (t === 'NESTED' || t === 'FLAT') {
filters = { ...filters, ...parseNested(keywords, values, key, val) }
filters = { ...filters, ...parseNested(keywords, resolvers, key, val) }
}
// ARRAY
else if (t === 'ARRAY') {
filters[key] = val.map(v => buildFilters(v, null, keywords, values))
filters[key] = val.map(v => buildFilters(v, null, keywords, resolvers))
}
// ELSE
else {
filters[key] = buildFilters(val, null, keywords, values)
filters[key] = buildFilters(val, null, keywords, resolvers)
}
}
return filters
}

export default (
customKeywords: Keywords = {},
customValues: Values = {},
customResolvers: Resolvers = {},
merge: boolean = true
) => {
const keywords: Keywords = merge
? { ...defaultKeywords, ...customKeywords }
: customKeywords
const values: Values = merge ? { ...defaultValues, ...customValues } : customValues
return (args: object): object => buildFilters(args, null, keywords, values)
const resolvers: Resolvers = merge ? { ...defaultValues, ...customResolvers } : customResolvers
return (args: object): object => buildFilters(args, null, keywords, resolvers)
}
6 changes: 3 additions & 3 deletions tests/class.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import GMQ from '../src/class'

test('Flat computed values', () => {
const values = {
test('Resolvers', () => {
const resolvers = {
test1(parent) {
parent.test1 = true
return parent
Expand Down Expand Up @@ -43,7 +43,7 @@ test('Flat computed values', () => {
_AND: [{ nested: { a: 'aaa' } }, { b: true }]
}

const filter = new GMQ(null, values).buildFilters(arg)
const filter = new GMQ(null, resolvers).buildFilters(arg)
expect(filter).toEqual({
test1: true,
logical: { $or: [1, 2] },
Expand Down
Loading

0 comments on commit 9e72467

Please sign in to comment.