graphql-field-mask
Advanced tools
Comparing version 0.1.3 to 0.1.4
@@ -1,2 +0,2 @@ | ||
import { FragmentDefinitionNode, GraphQLAbstractType, GraphQLField, GraphQLObjectType, GraphQLResolveInfo, GraphQLSchema, InlineFragmentNode } from "graphql"; | ||
import { FieldNode, FragmentDefinitionNode, GraphQLAbstractType, GraphQLField, GraphQLObjectType, GraphQLResolveInfo, GraphQLScalarType, GraphQLSchema, InlineFragmentNode } from "graphql"; | ||
export declare type GetFieldNameFunc = (field: GraphQLField<any, any>, type: GraphQLObjectType, schema: GraphQLSchema) => string | null; | ||
@@ -9,2 +9,7 @@ export declare type GetAbstractTypeFieldMaskPathsFunc = (info: { | ||
}, getFieldMaskPaths: () => string[]) => string[]; | ||
export declare type GetCustomScalarTypeFieldMaskPathsFunc = (path: string, info: { | ||
node: FieldNode; | ||
type: GraphQLScalarType; | ||
field: GraphQLField<any, any>; | ||
}) => string[]; | ||
export declare type FieldMaskPathsFromResolveInfoOptions = { | ||
@@ -22,2 +27,7 @@ /** | ||
getAbstractTypeFieldMaskPaths?: GetAbstractTypeFieldMaskPathsFunc; | ||
/** | ||
* Determine field mask paths in custom scalar type. | ||
* By default, it returns only the field name of the scalar type. | ||
*/ | ||
getCustomScalarFieldMaskPaths?: GetCustomScalarTypeFieldMaskPathsFunc; | ||
}; | ||
@@ -24,0 +34,0 @@ /** |
@@ -5,2 +5,3 @@ "use strict"; | ||
const graphql_1 = require("graphql"); | ||
const defaultScalarTypeNames = new Set(["Int", "Float", "String", "Boolean", "ID"]); | ||
/** | ||
@@ -46,2 +47,3 @@ * Create field mask paths from `GraphQLResolveInfo`. | ||
} | ||
const fieldType = (0, graphql_1.getNamedType)(field.type); | ||
if (selection.selectionSet) { | ||
@@ -52,2 +54,7 @@ const childTypename = (0, graphql_1.getNamedType)(field.type).name; | ||
} | ||
else if (fieldType instanceof graphql_1.GraphQLScalarType && | ||
!defaultScalarTypeNames.has(fieldType.name) && | ||
opts.getCustomScalarFieldMaskPaths) { | ||
fields.push(...opts.getCustomScalarFieldMaskPaths(fieldName, { field, type: fieldType, node: selection })); | ||
} | ||
else { | ||
@@ -78,2 +85,5 @@ fields.push(fieldName); | ||
} | ||
default: { | ||
throw new Error("unreachable"); | ||
} | ||
} | ||
@@ -80,0 +90,0 @@ const fragmentType = getType(fragmentTypename, schema); |
@@ -1,2 +0,2 @@ | ||
import { FragmentDefinitionNode, GraphQLAbstractType, GraphQLField, GraphQLObjectType, GraphQLResolveInfo, GraphQLSchema, InlineFragmentNode } from "graphql"; | ||
import { FieldNode, FragmentDefinitionNode, GraphQLAbstractType, GraphQLField, GraphQLObjectType, GraphQLResolveInfo, GraphQLScalarType, GraphQLSchema, InlineFragmentNode } from "graphql"; | ||
export declare type GetFieldNameFunc = (field: GraphQLField<any, any>, type: GraphQLObjectType, schema: GraphQLSchema) => string | null; | ||
@@ -9,2 +9,7 @@ export declare type GetAbstractTypeFieldMaskPathsFunc = (info: { | ||
}, getFieldMaskPaths: () => string[]) => string[]; | ||
export declare type GetCustomScalarTypeFieldMaskPathsFunc = (path: string, info: { | ||
node: FieldNode; | ||
type: GraphQLScalarType; | ||
field: GraphQLField<any, any>; | ||
}) => string[]; | ||
export declare type FieldMaskPathsFromResolveInfoOptions = { | ||
@@ -22,2 +27,7 @@ /** | ||
getAbstractTypeFieldMaskPaths?: GetAbstractTypeFieldMaskPathsFunc; | ||
/** | ||
* Determine field mask paths in custom scalar type. | ||
* By default, it returns only the field name of the scalar type. | ||
*/ | ||
getCustomScalarFieldMaskPaths?: GetCustomScalarTypeFieldMaskPathsFunc; | ||
}; | ||
@@ -24,0 +34,0 @@ /** |
@@ -1,2 +0,3 @@ | ||
import { getNamedType, GraphQLObjectType, isAbstractType, } from "graphql"; | ||
import { getNamedType, GraphQLObjectType, GraphQLScalarType, isAbstractType, } from "graphql"; | ||
const defaultScalarTypeNames = new Set(["Int", "Float", "String", "Boolean", "ID"]); | ||
/** | ||
@@ -41,2 +42,3 @@ * Create field mask paths from `GraphQLResolveInfo`. | ||
} | ||
const fieldType = getNamedType(field.type); | ||
if (selection.selectionSet) { | ||
@@ -47,2 +49,7 @@ const childTypename = getNamedType(field.type).name; | ||
} | ||
else if (fieldType instanceof GraphQLScalarType && | ||
!defaultScalarTypeNames.has(fieldType.name) && | ||
opts.getCustomScalarFieldMaskPaths) { | ||
fields.push(...opts.getCustomScalarFieldMaskPaths(fieldName, { field, type: fieldType, node: selection })); | ||
} | ||
else { | ||
@@ -73,2 +80,5 @@ fields.push(fieldName); | ||
} | ||
default: { | ||
throw new Error("unreachable"); | ||
} | ||
} | ||
@@ -75,0 +85,0 @@ const fragmentType = getType(fragmentTypename, schema); |
{ | ||
"name": "graphql-field-mask", | ||
"version": "0.1.3", | ||
"version": "0.1.4", | ||
"description": "google.protobuf.FieldMask from GraphQL query", | ||
@@ -17,3 +17,3 @@ "sideEffects": false, | ||
], | ||
"repository": "https://github.com/izumin5210/graphql-field-mask", | ||
"repository": "https://github.com/proto-graphql/graphql-field-mask", | ||
"author": "izumin5210 <m@izum.in>", | ||
@@ -29,3 +29,3 @@ "license": "MIT", | ||
"peerDependencies": { | ||
"graphql": "^15.0.0" | ||
"graphql": "^15.0.0 || ^16.0.0" | ||
}, | ||
@@ -35,3 +35,7 @@ "devDependencies": { | ||
"frolint": "^2.8.2", | ||
"graphql": "^15.6.1", | ||
"graphql": "link:./node_modules/graphql-16.x", | ||
"graphql-15.0.0": "npm:graphql@15.0.0", | ||
"graphql-15.x": "npm:graphql@15.x", | ||
"graphql-16.0.0": "npm:graphql@16.0.0", | ||
"graphql-16.x": "npm:graphql@16.x", | ||
"jest": "^27.3.1", | ||
@@ -38,0 +42,0 @@ "ts-jest": "^27.0.7", |
# graphql-field-mask | ||
[![CI](https://github.com/izumin5210/graphql-field-mask/actions/workflows/ci.yml/badge.svg)](https://github.com/izumin5210/graphql-field-mask/actions/workflows/ci.yml) | ||
[![Coverage Status](https://coveralls.io/repos/github/izumin5210/graphql-field-mask/badge.svg?branch=main)](https://coveralls.io/github/izumin5210/graphql-field-mask?branch=main) | ||
[![CI](https://github.com/proto-graphql/graphql-field-mask/actions/workflows/ci.yml/badge.svg)](https://github.com/proto-graphql/graphql-field-mask/actions/workflows/ci.yml) | ||
[![Coverage Status](https://coveralls.io/repos/github/proto-graphql/graphql-field-mask/badge.svg?branch=main)](https://coveralls.io/github/proto-graphql/graphql-field-mask?branch=main) | ||
[![npm](https://img.shields.io/npm/v/graphql-field-mask)](https://www.npmjs.com/package/graphql-field-mask) | ||
[![LICENSE](https://img.shields.io/github/license/izumin5210/graphql-field-mask)](./LICENSE) | ||
[![LICENSE](https://img.shields.io/github/license/proto-graphql/graphql-field-mask)](./LICENSE) | ||
@@ -31,2 +31,23 @@ `google.protobuf.FieldMask` from GraphQL query | ||
### With custom scalar | ||
```ts | ||
import { fieldMaskPathsFromResolveInfo, GetCustomScalarFieldMaskPaths } from "graphql-field-mask"; | ||
const getCustomScalarFieldMaskPaths: GetCustomScalarFieldMaskPaths = (fieldName, info) => { | ||
switch (info.type.name) { | ||
case 'Date': | ||
return ['year', 'month', 'day'].map(c => `${fieldName}.${c}`); | ||
// ... | ||
} | ||
}; | ||
resolve(_source, _args, ctx, info) { | ||
const paths = fieldMaskPathsFromResolveInfo("User", info, { getCustomScalarFieldMaskPaths }); | ||
const mask = new FieldMask().setPathsList(paths); | ||
// ... | ||
} | ||
``` | ||
### Convert to snake case | ||
@@ -73,1 +94,5 @@ | ||
``` | ||
## Author | ||
- [Masayuki Izumi (@izumin5210)](https://github.com/izumin5210) |
import { | ||
extendSchema, | ||
graphql, | ||
@@ -10,2 +11,3 @@ GraphQLInt, | ||
GraphQLUnionType, | ||
parse, | ||
} from "graphql"; | ||
@@ -27,7 +29,7 @@ import { fieldMaskPathsFromResolveInfo, GetFieldNameFunc } from "./fieldMaskPathsFromResolveInfo"; | ||
targetField: { | ||
type: GraphQLNonNull(GraphQLString), | ||
type: new GraphQLNonNull(GraphQLString), | ||
extensions: { fieldMask: { fieldName: "target_field" } } as FieldMaskExtensions, | ||
}, | ||
otherField: { | ||
type: GraphQLNonNull(GraphQLString), | ||
type: new GraphQLNonNull(GraphQLString), | ||
}, | ||
@@ -59,3 +61,7 @@ }, | ||
const fetchObject1 = jest.fn().mockReturnValue({ targetField: "target field", otherField: "other field" }); | ||
const result = await graphql(schema, "{ object1 { targetField, otherField } }", undefined, { fetchObject1 }); | ||
const result = await graphql({ | ||
schema, | ||
source: "{ object1 { targetField, otherField } }", | ||
contextValue: { fetchObject1 }, | ||
}); | ||
@@ -80,3 +86,7 @@ expect(result.errors).toBeUndefined(); | ||
const fetchObject1 = jest.fn().mockReturnValue({ targetField: "target field", otherField: "other field" }); | ||
const result = await graphql(schema, "{ object1 { targetField, otherField } }", undefined, { fetchObject1 }); | ||
const result = await graphql({ | ||
schema, | ||
source: "{ object1 { targetField, otherField } }", | ||
contextValue: { fetchObject1 }, | ||
}); | ||
@@ -93,3 +103,7 @@ expect(result.errors).toBeUndefined(); | ||
const fetchObject1 = jest.fn().mockReturnValue({ targetField: "target field" }); | ||
const result = await graphql(schema, "{ object1 { aliasedField: targetField } }", undefined, { fetchObject1 }); | ||
const result = await graphql({ | ||
schema, | ||
source: "{ object1 { aliasedField: targetField } }", | ||
contextValue: { fetchObject1 }, | ||
}); | ||
@@ -106,3 +120,7 @@ expect(result.errors).toBeUndefined(); | ||
const fetchObject1 = jest.fn().mockReturnValue({ targetField: "target field" }); | ||
const result = await graphql(schema, "{ object1 { __typename, targetField } }", undefined, { fetchObject1 }); | ||
const result = await graphql({ | ||
schema, | ||
source: "{ object1 { __typename, targetField } }", | ||
contextValue: { fetchObject1 }, | ||
}); | ||
@@ -119,5 +137,5 @@ expect(result.errors).toBeUndefined(); | ||
const fetchObject1 = jest.fn().mockReturnValue({ targetField: "target field", otherField: "other field" }); | ||
const result = await graphql( | ||
const result = await graphql({ | ||
schema, | ||
` | ||
source: ` | ||
query { | ||
@@ -133,5 +151,4 @@ object1 { | ||
`, | ||
undefined, | ||
{ fetchObject1 } | ||
); | ||
contextValue: { fetchObject1 }, | ||
}); | ||
@@ -148,5 +165,5 @@ expect(result.errors).toBeUndefined(); | ||
const fetchObject1 = jest.fn().mockReturnValue({ targetField: "target field", otherField: "other field" }); | ||
const result = await graphql( | ||
const result = await graphql({ | ||
schema, | ||
` | ||
source: ` | ||
query { | ||
@@ -161,5 +178,4 @@ object1 { | ||
`, | ||
undefined, | ||
{ fetchObject1 } | ||
); | ||
contextValue: { fetchObject1 }, | ||
}); | ||
@@ -176,5 +192,5 @@ expect(result.errors).toBeUndefined(); | ||
const fetchObject1 = jest.fn().mockReturnValue({ targetField: "target field", otherField: "other field" }); | ||
const result = await graphql( | ||
const result = await graphql({ | ||
schema, | ||
` | ||
source: ` | ||
query { | ||
@@ -189,5 +205,4 @@ object1 { | ||
`, | ||
undefined, | ||
{ fetchObject1 } | ||
); | ||
contextValue: { fetchObject1 }, | ||
}); | ||
@@ -206,3 +221,3 @@ expect(result.errors).toBeUndefined(); | ||
parentField: { type: GraphQLInt }, | ||
object1: { type: GraphQLNonNull(object1Type) }, | ||
object1: { type: new GraphQLNonNull(object1Type) }, | ||
}, | ||
@@ -223,8 +238,7 @@ }); | ||
.mockReturnValue({ parentField: 1, object1: { targetField: "target field", otherField: "other field" } }); | ||
const result = await graphql( | ||
const result = await graphql({ | ||
schema, | ||
"{ parent { parentField, object1 { targetField, otherField } } }", | ||
undefined, | ||
{ fetchParent } | ||
); | ||
source: "{ parent { parentField, object1 { targetField, otherField } } }", | ||
contextValue: { fetchParent }, | ||
}); | ||
@@ -239,2 +253,51 @@ expect(result.errors).toBeUndefined(); | ||
describe("with custom scalar", () => { | ||
it("returns field mask paths with getCustomScalarFieldMaskPath result", async () => { | ||
let schema = createSchema({ | ||
queryFields: { | ||
object1: { | ||
type: object1Type, | ||
resolve(_source, _args, ctx, info) { | ||
return ctx.fetchObject1( | ||
fieldMaskPathsFromResolveInfo("Object1", info, { | ||
getCustomScalarFieldMaskPaths: (path, info) => { | ||
if (info.type.name === "Date") return ["year", "month", "day"].map((c) => `${path}.${c}`); | ||
throw new Error(`unkonwn scalar type: ${info.type.name}`); | ||
}, | ||
}) | ||
); | ||
}, | ||
}, | ||
}, | ||
}); | ||
schema = extendSchema( | ||
schema, | ||
parse(` | ||
scalar Date | ||
extend type Object1 { date: Date! } | ||
`) | ||
); | ||
const fetchObject1 = jest | ||
.fn() | ||
.mockReturnValue({ targetField: "target field", otherField: "other field", date: "2021-11-01" }); | ||
const result = await graphql({ | ||
schema, | ||
source: `query { object1 { targetField, otherField, date } }`, | ||
contextValue: { fetchObject1 }, | ||
}); | ||
expect(result.errors).toBeUndefined(); | ||
expect(result.data).toEqual({ | ||
object1: { targetField: "target field", otherField: "other field", date: "2021-11-01" }, | ||
}); | ||
expect(fetchObject1.mock.calls[0][0]).toEqual([ | ||
"targetField", | ||
"otherField", | ||
"date.year", | ||
"date.month", | ||
"date.day", | ||
]); | ||
}); | ||
}); | ||
describe("with union type", () => { | ||
@@ -260,5 +323,5 @@ const object2Type = new GraphQLObjectType({ name: "Object2", fields: { field2: { type: GraphQLString } } }); | ||
.mockReturnValue({ __typename: "Object1", targetField: "target field", otherField: "other field" }); | ||
const result = await graphql( | ||
const result = await graphql({ | ||
schema, | ||
` | ||
source: ` | ||
{ | ||
@@ -277,5 +340,4 @@ union { | ||
`, | ||
undefined, | ||
{ fetchUnion } | ||
); | ||
contextValue: { fetchUnion }, | ||
}); | ||
expect(result.errors).toBeUndefined(); | ||
@@ -310,3 +372,3 @@ expect(result.data).toEqual({ union: { otherField: "other field", targetField: "target field" } }); | ||
`; | ||
const result = await graphql(schema, query, undefined, { fetchUnion }); | ||
const result = await graphql({ schema, source: query, contextValue: { fetchUnion } }); | ||
expect(result.errors).toBeUndefined(); | ||
@@ -329,3 +391,3 @@ expect(result.data).toEqual({ union: { otherField: "other field", targetField: "target field" } }); | ||
union: { | ||
type: GraphQLNonNull(unionType), | ||
type: new GraphQLNonNull(unionType), | ||
extensions: { fieldMaskPathPrefix: { Object1: "object1", Object2: "object2" } }, | ||
@@ -366,3 +428,3 @@ }, | ||
.mockReturnValue({ union: { __typename: "Object1", targetField: "target field", otherField: "other field" } }); | ||
const result = await graphql(schema, query, undefined, { fetchParent }); | ||
const result = await graphql({ schema, source: query, contextValue: { fetchParent } }); | ||
expect(result.errors).toBeUndefined(); | ||
@@ -383,3 +445,3 @@ expect(result.data).toEqual({ parent: { union: { otherField: "other field", targetField: "target field" } } }); | ||
// eslint-disable-next-line dot-notation | ||
const prefix = info.field.extensions?.["fieldMaskPathPrefix"][info.concreteType.name]; | ||
const prefix = (info.field.extensions as any)?.["fieldMaskPathPrefix"][info.concreteType.name]; | ||
return getFieldMaskPaths().map((p) => `${prefix}.${p}`); | ||
@@ -396,3 +458,3 @@ }, | ||
.mockReturnValue({ union: { __typename: "Object1", targetField: "target field", otherField: "other field" } }); | ||
const result = await graphql(schema, query, undefined, { fetchParent }); | ||
const result = await graphql({ schema, source: query, contextValue: { fetchParent } }); | ||
expect(result.errors).toBeUndefined(); | ||
@@ -424,3 +486,3 @@ expect(result.data).toEqual({ parent: { union: { otherField: "other field", targetField: "target field" } } }); | ||
// eslint-disable-next-line dot-notation | ||
const prefix = info.field.extensions?.["fieldMaskPathPrefix"][info.concreteType.name]; | ||
const prefix = (info.field.extensions as any)?.["fieldMaskPathPrefix"][info.concreteType.name]; | ||
return getFieldMaskPaths().map((p) => `${prefix}.${p}`); | ||
@@ -437,3 +499,3 @@ }, | ||
.mockReturnValue({ union: { __typename: "Object1", targetField: "target field", otherField: "other field" } }); | ||
const result = await graphql(schema, query, undefined, { fetchParent }); | ||
const result = await graphql({ schema, source: query, contextValue: { fetchParent } }); | ||
expect(result.errors).toBeUndefined(); | ||
@@ -455,3 +517,3 @@ expect(result.data).toEqual({ parent: { union: { otherField: "other field", targetField: "target field" } } }); | ||
object1: { | ||
type: GraphQLNonNull(object1Type), | ||
type: new GraphQLNonNull(object1Type), | ||
resolve(_source, _args, ctx, info) { | ||
@@ -474,4 +536,6 @@ return ctx.fetchObject1(fieldMaskPathsFromResolveInfo("Object1", info)); | ||
const fetchObject1 = jest.fn().mockReturnValue({ targetField: "target field", otherField: "other field" }); | ||
const result = await graphql(schema, "{ parent { object1 { targetField otherField } } }", undefined, { | ||
fetchObject1, | ||
const result = await graphql({ | ||
schema, | ||
source: "{ parent { object1 { targetField otherField } } }", | ||
contextValue: { fetchObject1 }, | ||
}); | ||
@@ -496,3 +560,7 @@ expect(result.errors).toBeUndefined(); | ||
const fetchObject1 = jest.fn().mockReturnValue({ targetField: "target field", otherField: "other field" }); | ||
const result = await graphql(schema, "{ object1 { targetField, otherField } }", undefined, { fetchObject1 }); | ||
const result = await graphql({ | ||
schema, | ||
source: "{ object1 { targetField, otherField } }", | ||
contextValue: { fetchObject1 }, | ||
}); | ||
@@ -499,0 +567,0 @@ expect(result.errors).toHaveLength(1); |
@@ -10,2 +10,3 @@ import { | ||
GraphQLResolveInfo, | ||
GraphQLScalarType, | ||
GraphQLSchema, | ||
@@ -32,2 +33,13 @@ InlineFragmentNode, | ||
export type GetCustomScalarTypeFieldMaskPathsFunc = ( | ||
path: string, | ||
info: { | ||
node: FieldNode; | ||
type: GraphQLScalarType; | ||
field: GraphQLField<any, any>; | ||
} | ||
) => string[]; | ||
const defaultScalarTypeNames = new Set(["Int", "Float", "String", "Boolean", "ID"]); | ||
export type FieldMaskPathsFromResolveInfoOptions = { | ||
@@ -45,2 +57,7 @@ /** | ||
getAbstractTypeFieldMaskPaths?: GetAbstractTypeFieldMaskPathsFunc; | ||
/** | ||
* Determine field mask paths in custom scalar type. | ||
* By default, it returns only the field name of the scalar type. | ||
*/ | ||
getCustomScalarFieldMaskPaths?: GetCustomScalarTypeFieldMaskPathsFunc; | ||
}; | ||
@@ -110,2 +127,3 @@ | ||
} | ||
const fieldType = getNamedType(field.type); | ||
if (selection.selectionSet) { | ||
@@ -115,2 +133,8 @@ const childTypename = getNamedType(field.type).name; | ||
fields.push(...childFields.map((field) => `${fieldName}.${field}`)); | ||
} else if ( | ||
fieldType instanceof GraphQLScalarType && | ||
!defaultScalarTypeNames.has(fieldType.name) && | ||
opts.getCustomScalarFieldMaskPaths | ||
) { | ||
fields.push(...opts.getCustomScalarFieldMaskPaths(fieldName, { field, type: fieldType, node: selection })); | ||
} else { | ||
@@ -141,2 +165,5 @@ fields.push(fieldName); | ||
} | ||
default: { | ||
throw new Error("unreachable"); | ||
} | ||
} | ||
@@ -143,0 +170,0 @@ const fragmentType = getType(fragmentTypename, schema); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
61834
23
1064
97
10