schema: support shallow-nested object fields (#916)
Summary: See #915 for context. This commit changes the `schema` module only. I had a hard time picking names that clearly distinguish the top-level field on the object and the subfields that it contains. @decentralion and I independently came up with “nest” and “egg”. It’s a bit colorful, but it’s certainly easy to remember which one is which, and it doesn’t conflict with existing notions like “parent”/“child”. Test Plan: Unit tests expanded slightly, retaining full coverage. wchargin-branch: schema-shallow
This commit is contained in:
parent
d8d857fdd3
commit
3d2206088c
|
@ -1056,6 +1056,8 @@ export class Mirror {
|
||||||
case "CONNECTION":
|
case "CONNECTION":
|
||||||
// Not handled by this function.
|
// Not handled by this function.
|
||||||
return null;
|
return null;
|
||||||
|
case "NESTED":
|
||||||
|
throw new Error("Nested fields not supported.");
|
||||||
// istanbul ignore next
|
// istanbul ignore next
|
||||||
default:
|
default:
|
||||||
throw new Error((field.type: empty));
|
throw new Error((field.type: empty));
|
||||||
|
@ -1563,6 +1565,8 @@ export function _buildSchemaInfo(schema: Schema.Schema): SchemaInfo {
|
||||||
case "CONNECTION":
|
case "CONNECTION":
|
||||||
entry.connectionFieldNames.push(fieldname);
|
entry.connectionFieldNames.push(fieldname);
|
||||||
break;
|
break;
|
||||||
|
case "NESTED":
|
||||||
|
throw new Error("Nested fields not supported.");
|
||||||
// istanbul ignore next
|
// istanbul ignore next
|
||||||
default:
|
default:
|
||||||
throw new Error((field.type: empty));
|
throw new Error((field.type: empty));
|
||||||
|
|
|
@ -31,15 +31,41 @@ export type ObjectId = string;
|
||||||
// represented as `PRIMITIVE`s (except for `ID`s).
|
// represented as `PRIMITIVE`s (except for `ID`s).
|
||||||
// - Connections are supported as object fields, but arbitrary lists
|
// - Connections are supported as object fields, but arbitrary lists
|
||||||
// are not.
|
// are not.
|
||||||
|
//
|
||||||
|
// To accommodate schemata where some object types do not have IDs,
|
||||||
|
// objects may have "nested" fields of primitive or node-reference type.
|
||||||
|
// These may be nested to depth exactly 1. Suppose that `Foo` is an
|
||||||
|
// object type that includes `bar: Bar!`, but `Bar` is an object type
|
||||||
|
// without an `id`. Then `Bar` may not be a first-class type, but `Foo`
|
||||||
|
// may pull properties off of it using
|
||||||
|
//
|
||||||
|
// bar: nested({x: primitive(), y: node("Baz")});
|
||||||
|
//
|
||||||
|
// The property "bar" in the above example is called a _nested_
|
||||||
|
// property, and its fields "x" and "y" are called _eggs_. (The nest
|
||||||
|
// contains the eggs.)
|
||||||
export type Schema = {+[Typename]: NodeType};
|
export type Schema = {+[Typename]: NodeType};
|
||||||
export type NodeType =
|
export type NodeType =
|
||||||
| {|+type: "OBJECT", +fields: {|+[Fieldname]: FieldType|}|}
|
| {|+type: "OBJECT", +fields: {|+[Fieldname]: FieldType|}|}
|
||||||
| {|+type: "UNION", +clauses: {|+[Typename]: true|}|};
|
| {|+type: "UNION", +clauses: {|+[Typename]: true|}|};
|
||||||
|
|
||||||
export type FieldType =
|
export type FieldType =
|
||||||
| {|+type: "ID"|}
|
| IdFieldType
|
||||||
| {|+type: "PRIMITIVE"|}
|
| PrimitiveFieldType
|
||||||
| {|+type: "NODE", +elementType: Typename|}
|
| NodeFieldType
|
||||||
| {|+type: "CONNECTION", +elementType: Typename|};
|
| ConnectionFieldType
|
||||||
|
| NestedFieldType;
|
||||||
|
export type IdFieldType = {|+type: "ID"|};
|
||||||
|
export type PrimitiveFieldType = {|+type: "PRIMITIVE"|};
|
||||||
|
export type NodeFieldType = {|+type: "NODE", +elementType: Typename|};
|
||||||
|
export type ConnectionFieldType = {|
|
||||||
|
+type: "CONNECTION",
|
||||||
|
+elementType: Typename,
|
||||||
|
|};
|
||||||
|
export type NestedFieldType = {|
|
||||||
|
+type: "NESTED",
|
||||||
|
+eggs: {+[Fieldname]: PrimitiveFieldType | NodeFieldType},
|
||||||
|
|};
|
||||||
|
|
||||||
// Every object must have exactly one `id` field, and it must have this
|
// Every object must have exactly one `id` field, and it must have this
|
||||||
// name.
|
// name.
|
||||||
|
@ -111,18 +137,24 @@ export function union(clauses: $ReadOnlyArray<Typename>): NodeType {
|
||||||
return {type: "UNION", clauses: clausesMap};
|
return {type: "UNION", clauses: clausesMap};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function id(): FieldType {
|
export function id(): IdFieldType {
|
||||||
return {type: "ID"};
|
return {type: "ID"};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function primitive(): FieldType {
|
export function primitive(): PrimitiveFieldType {
|
||||||
return {type: "PRIMITIVE"};
|
return {type: "PRIMITIVE"};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function node(elementType: Typename): FieldType {
|
export function node(elementType: Typename): NodeFieldType {
|
||||||
return {type: "NODE", elementType};
|
return {type: "NODE", elementType};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function connection(elementType: Typename): FieldType {
|
export function connection(elementType: Typename): ConnectionFieldType {
|
||||||
return {type: "CONNECTION", elementType};
|
return {type: "CONNECTION", elementType};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function nested(eggs: {
|
||||||
|
+[Fieldname]: PrimitiveFieldType | NodeFieldType,
|
||||||
|
}): NestedFieldType {
|
||||||
|
return {type: "NESTED", eggs: {...eggs}};
|
||||||
|
}
|
||||||
|
|
|
@ -19,6 +19,14 @@ describe("graphql/schema", () => {
|
||||||
title: s.primitive(),
|
title: s.primitive(),
|
||||||
comments: s.connection("IssueComment"),
|
comments: s.connection("IssueComment"),
|
||||||
}),
|
}),
|
||||||
|
Commit: s.object({
|
||||||
|
id: s.id(),
|
||||||
|
oid: s.primitive(),
|
||||||
|
author: /* GitActor */ s.nested({
|
||||||
|
date: s.primitive(),
|
||||||
|
user: s.node("User"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
IssueComment: s.object({
|
IssueComment: s.object({
|
||||||
id: s.id(),
|
id: s.id(),
|
||||||
body: s.primitive(),
|
body: s.primitive(),
|
||||||
|
|
Loading…
Reference in New Issue