Skip to content

Commit 5571f4a

Browse files
authored
Merge pull request #15 from thefrontside/dl/add-docs
Add frontside.com documentation minisite
2 parents 9456142 + 33b1a42 commit 5571f4a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+5916
-110
lines changed

.eslintignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
dist
22
examples
3+
www

README.md

+9-9
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ types and how to resolve them.
88
- [`@field`](#field)
99
- [`@implements`](#implements)
1010
- [`@discriminates`](#discriminates)
11-
- [`@discriminationAlias`](#discriminationalias)
1211
- [`@resolve`](#resolve)
1312
- [Getting started](#getting-started)
1413
- [GraphQL Application](#graphql-application)
@@ -122,6 +121,8 @@ type Service @implements(interface: "Entity") {
122121
_NOTE: In this example if we have data of `Entity` type and it has `kind` field_
123122
_with `Component` value, that means data will be resolved to `Component` type_
124123

124+
#### `opaqueType`
125+
125126
There is a special case when your runtime data doesn't have a value
126127
that can be used to discriminate the interface or there is no type
127128
that matches the value. In this case, you can define `opaqueType` argument
@@ -141,26 +142,25 @@ plugin will generate it for you.
141142
There is another way to define opaque types for all interfaces by using `generateOpaqueTypes`
142143
option for GraphQL plugin.
143144

144-
### `@discriminationAlias`
145+
#### `aliases`
145146

146-
By default value from `with` argument is used to find a type as-is or converted to PascalCase.
147-
Sometimes you need to match the value with a type that has a different name.
148-
In this case, you can use `@discriminationAlias` directive.
147+
By default value from `with` argument is used to find a type as-is or converted to PascalCase.
148+
Sometimes you need to match the value with a type that has a different name.
149+
In this case, you can define `aliases` argument.
149150

150151
```graphql
151152
interface API
152153
@implements(interface: "Node")
153-
@discriminates(with: "spec.type")
154-
@discriminationAlias(value: "openapi", type: "OpenAPI") {
154+
@discriminates(with: "spec.type", aliases: [{ value: "grpc", type: "GrpcAPI" }]) {
155155
# ...
156156
}
157157

158-
type OpenAPI @implements(interface: "API") {
158+
type GrpcAPI @implements(interface: "API") {
159159
# ...
160160
}
161161
```
162162

163-
This means, when `spec.type` equals to `openapi`, the `API` interface will be resolved to `OpenAPI` type.
163+
This means, when `spec.type` equals to `grpc`, the `API` interface will be resolved to `GrpcAPI` type.
164164

165165
### `@resolve`
166166

src/__snapshots__/schema.graphql.snap

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
directive @discriminates(opaqueType: String, with: _DirectiveArgument_) on INTERFACE
1+
directive @discriminates(aliases: [DiscriminationAlias!], opaqueType: String, with: _DirectiveArgument_) on INTERFACE
22

3+
"""@deprecated Please use `@discriminates(aliases: [...])`"""
34
directive @discriminationAlias(type: String!, value: String!) repeatable on INTERFACE
45

56
directive @field(at: _DirectiveArgument_, default: _DirectiveArgument_) on FIELD_DEFINITION
@@ -14,6 +15,11 @@ interface Connection {
1415
pageInfo: PageInfo!
1516
}
1617

18+
input DiscriminationAlias {
19+
type: String!
20+
value: String!
21+
}
22+
1723
interface Edge {
1824
cursor: String!
1925
node: Node!

src/__snapshots__/types.ts.snap

+8
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ export type Connection = {
2525
pageInfo: PageInfo;
2626
};
2727

28+
export type DiscriminationAlias = {
29+
type: Scalars['String']['input'];
30+
value: Scalars['String']['input'];
31+
};
32+
2833
export type Edge = {
2934
cursor: Scalars['String']['output'];
3035
node: Node;
@@ -137,6 +142,7 @@ export type ResolversInterfaceTypes<RefType extends Record<string, unknown>> = {
137142
export type ResolversTypes = {
138143
Boolean: ResolverTypeWrapper<Scalars['Boolean']['output']>;
139144
Connection: ResolverTypeWrapper<ResolversInterfaceTypes<ResolversTypes>['Connection']>;
145+
DiscriminationAlias: DiscriminationAlias;
140146
Edge: ResolverTypeWrapper<ResolversInterfaceTypes<ResolversTypes>['Edge']>;
141147
ID: ResolverTypeWrapper<Scalars['ID']['output']>;
142148
Int: ResolverTypeWrapper<Scalars['Int']['output']>;
@@ -151,6 +157,7 @@ export type ResolversTypes = {
151157
export type ResolversParentTypes = {
152158
Boolean: Scalars['Boolean']['output'];
153159
Connection: ResolversInterfaceTypes<ResolversParentTypes>['Connection'];
160+
DiscriminationAlias: DiscriminationAlias;
154161
Edge: ResolversInterfaceTypes<ResolversParentTypes>['Edge'];
155162
ID: Scalars['ID']['output'];
156163
Int: Scalars['Int']['output'];
@@ -162,6 +169,7 @@ export type ResolversParentTypes = {
162169
};
163170

164171
export type DiscriminatesDirectiveArgs = {
172+
aliases?: Maybe<Array<DiscriminationAlias>>;
165173
opaqueType?: Maybe<Scalars['String']['input']>;
166174
with?: Maybe<Scalars['_DirectiveArgument_']['input']>;
167175
};

src/core/core.graphql

+9
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ directive @field(
55
directive @discriminates(
66
with: _DirectiveArgument_
77
opaqueType: String
8+
aliases: [DiscriminationAlias!]
89
) on INTERFACE
10+
"""
11+
@deprecated Please use `@discriminates(aliases: [...])`
12+
"""
913
directive @discriminationAlias(
1014
value: String!
1115
type: String!
@@ -15,6 +19,11 @@ directive @resolve(at: _DirectiveArgument_, nodeType: String, from: String) on F
1519

1620
scalar _DirectiveArgument_
1721

22+
input DiscriminationAlias {
23+
value: String!
24+
type: String!
25+
}
26+
1827
interface Node {
1928
id: ID!
2029
}

src/core/resolveDirectiveMapper.ts

+17-10
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
1-
import _ from "lodash";
2-
import { connectionFromArray, ConnectionArguments } from "graphql-relay";
31
import {
42
GraphQLInputObjectType,
5-
type GraphQLFieldConfig,
6-
type GraphQLInterfaceType,
73
GraphQLInt,
84
GraphQLString,
5+
type GraphQLFieldConfig,
6+
type GraphQLInterfaceType,
97
} from "graphql";
10-
import type {
11-
DirectiveMapperAPI,
12-
FieldResolver,
13-
ResolverContext,
14-
} from "../types.js";
8+
import { ConnectionArguments, connectionFromArray } from "graphql-relay";
9+
import _ from "lodash";
10+
import { HYDRAPHQL_EXTENSION } from "src/constants.js";
1511
import {
1612
createConnectionType,
1713
decodeId,
@@ -21,7 +17,11 @@ import {
2117
isNamedListType,
2218
unboxNamedType,
2319
} from "../helpers.js";
24-
import { HYDRAPHQL_EXTENSION } from "src/constants.js";
20+
import type {
21+
DirectiveMapperAPI,
22+
FieldResolver,
23+
ResolverContext,
24+
} from "../types.js";
2525

2626
export function resolveDirectiveMapper(
2727
fieldName: string,
@@ -97,6 +97,11 @@ export function resolveDirectiveMapper(
9797
}
9898
}
9999

100+
// FIXME: This doesn't work if a single ref is resolved to multiple nodes
101+
// We need to load all nodes
102+
// So here we might have a single connection or array of connections
103+
// TODO Throw an error if we have an array of refs and a single connection
104+
// TODO Throw an error if we have a single ref and an array of connections
100105
const ids = ((ref ?? []) as string[]).map((r) => ({
101106
id: encodeId({
102107
source,
@@ -108,6 +113,7 @@ export function resolveDirectiveMapper(
108113
}),
109114
}));
110115

116+
// FIXME: We need to apply connection
111117
return {
112118
...connectionFromArray(ids, args as ConnectionArguments),
113119
count: ids.length,
@@ -176,6 +182,7 @@ export function resolveDirectiveMapper(
176182
fieldResolver,
177183
},
178184
};
185+
// TODO Add test case of handling [Connection] type (array of connections)
179186
field.resolve = async ({ id }, args, context, info) => {
180187
if (directive.at === "id") return { id };
181188

src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export * from "./createLoader.js";
22
export * from "./createGraphQLApp.js";
33
export * from "./core/core.js";
44
export * from "./helpers.js";
5+
export * from "./loadSchema.js";
56
export { transformSchema } from "./transformSchema.js";
67
export type {
78
GraphQLContext,

src/loadSchema.ts

+16-6
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import { CodeFileLoader } from "@graphql-tools/code-file-loader";
22
import { GraphQLFileLoader } from "@graphql-tools/graphql-file-loader";
3-
import { loadTypedefs } from "@graphql-tools/load";
3+
import { loadTypedefs, loadTypedefsSync } from "@graphql-tools/load";
44
import {
55
getResolversFromSchema,
66
printSchemaWithDirectives,
7+
Source,
78
} from "@graphql-tools/utils";
89
import { createModule, gql } from "graphql-modules";
910

10-
export async function loadSchema(schema: string | string[]) {
11-
const sources = await loadTypedefs(schema, {
12-
sort: true,
13-
loaders: [new CodeFileLoader(), new GraphQLFileLoader()],
14-
});
11+
const loadTypeDefsOptions = {
12+
sort: true,
13+
loaders: [new CodeFileLoader(), new GraphQLFileLoader()],
14+
};
15+
16+
function sources2modules(sources: Source[]) {
1517
return sources.map((source, index) =>
1618
createModule({
1719
id: source.location ?? `unknown_${index}`,
@@ -24,3 +26,11 @@ export async function loadSchema(schema: string | string[]) {
2426
}),
2527
);
2628
}
29+
30+
export async function loadSchema(schema: string | string[]) {
31+
return sources2modules(await loadTypedefs(schema, loadTypeDefsOptions));
32+
}
33+
34+
export function loadSchemaSync(schema: string | string[]) {
35+
return sources2modules(loadTypedefsSync(schema, loadTypeDefsOptions));
36+
}

src/mapDirectives.test.ts

+47-37
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,14 @@ describe("mapDirectives", () => {
6262
const schema = transform(gql`
6363
interface Entity
6464
@implements(interface: "Node")
65-
@discriminates(with: "kind")
66-
@discriminationAlias(value: "component", type: "Component")
67-
@discriminationAlias(value: "template", type: "Template")
68-
@discriminationAlias(value: "location", type: "Location") {
65+
@discriminates(
66+
with: "kind"
67+
aliases: [
68+
{ value: "component", type: "Component" }
69+
{ value: "template", type: "Template" }
70+
{ value: "location", type: "Location" }
71+
]
72+
) {
6973
totalCount: Int!
7074
}
7175
@@ -390,14 +394,18 @@ describe("mapDirectives", () => {
390394
);
391395
});
392396

393-
void test(`should fail if @discriminationAlias has ambiguous types`, () => {
397+
void test(`should fail if discrimination aliases have ambiguous types`, () => {
394398
expect(() =>
395399
transform(gql`
396400
interface Entity
397401
@implements(interface: "Node")
398-
@discriminates(with: "kind")
399-
@discriminationAlias(value: "component", type: "EntityComponent")
400-
@discriminationAlias(value: "component", type: "Component") {
402+
@discriminates(
403+
with: "kind"
404+
aliases: [
405+
{ value: "component", type: "EntityComponent" }
406+
{ value: "component", type: "Component" }
407+
]
408+
) {
401409
name: String!
402410
}
403411
@@ -414,20 +422,6 @@ describe("mapDirectives", () => {
414422
);
415423
});
416424

417-
void test(`should fail if @discriminationAlias is used without @discriminates`, () => {
418-
expect(() =>
419-
transform(gql`
420-
interface Entity
421-
@implements(interface: "Node")
422-
@discriminationAlias(value: "component", type: "EntityComponent") {
423-
name: String!
424-
}
425-
`),
426-
).toThrow(
427-
`The "Entity" interface has @discriminationAlias directive but doesn't have @discriminates directive`,
428-
);
429-
});
430-
431425
void test(`should fail if interface has multiple implementations and @discriminates is not specified`, () => {
432426
expect(() =>
433427
transform(gql`
@@ -565,9 +559,11 @@ describe("mapDirectives", () => {
565559
expect(() =>
566560
transform(gql`
567561
interface Entity
568-
@discriminates(with: "kind")
569-
@implements(interface: "Node")
570-
@discriminationAlias(value: "component", type: "Component") {
562+
@discriminates(
563+
with: "kind"
564+
aliases: [{ value: "component", type: "Component" }]
565+
)
566+
@implements(interface: "Node") {
571567
name: String!
572568
}
573569
type Resource @implements(interface: "Entity") {
@@ -580,7 +576,7 @@ describe("mapDirectives", () => {
580576
}
581577
`),
582578
).toThrow(
583-
'Type(-s) "Component" in `interface Entity @discriminationAlias(value: ..., type: ...)` must implement "Entity" interface by using @implements directive',
579+
'Type(-s) "Component" in `interface Entity @discriminates(aliases: [...])` must implement "Entity" interface by using @implements directive',
584580
);
585581
});
586582

@@ -895,9 +891,13 @@ describe("mapDirectives", () => {
895891
id: "test",
896892
typeDefs: gql`
897893
interface Node
898-
@discriminates(with: "__source")
899-
@discriminationAlias(value: "Mock", type: "Entity")
900-
@discriminationAlias(value: "GraphQL", type: "GraphQLEntity")
894+
@discriminates(
895+
with: "__source"
896+
aliases: [
897+
{ value: "Mock", type: "Entity" }
898+
{ value: "GraphQL", type: "GraphQLEntity" }
899+
]
900+
)
901901
902902
type Entity @implements(interface: "Node") {
903903
parent: GraphQLEntity @resolve(at: "spec.parentId", from: "GraphQL")
@@ -965,9 +965,13 @@ describe("mapDirectives", () => {
965965
id: "test",
966966
typeDefs: gql`
967967
interface Node
968-
@discriminates(with: "__source")
969-
@discriminationAlias(value: "Mock", type: "Entity")
970-
@discriminationAlias(value: "Tasks", type: "TaskProperty")
968+
@discriminates(
969+
with: "__source"
970+
aliases: [
971+
{ value: "Mock", type: "Entity" }
972+
{ value: "Tasks", type: "TaskProperty" }
973+
]
974+
)
971975
972976
type Entity @implements(interface: "Node") {
973977
property(name: String!): TaskProperty
@@ -1051,9 +1055,13 @@ describe("mapDirectives", () => {
10511055
id: "test",
10521056
typeDefs: gql`
10531057
interface Node
1054-
@discriminates(with: "__source")
1055-
@discriminationAlias(value: "Mock", type: "Entity")
1056-
@discriminationAlias(value: "Tasks", type: "Task")
1058+
@discriminates(
1059+
with: "__source"
1060+
aliases: [
1061+
{ value: "Mock", type: "Entity" }
1062+
{ value: "Tasks", type: "Task" }
1063+
]
1064+
)
10571065
10581066
type Entity @implements(interface: "Node") {
10591067
task(taskId: ID!): Task @resolve(from: "Tasks")
@@ -1202,8 +1210,10 @@ describe("mapDirectives", () => {
12021210
typeDefs: gql`
12031211
interface Entity
12041212
@implements(interface: "Node")
1205-
@discriminates(with: "kind")
1206-
@discriminationAlias(value: "User", type: "Employee") {
1213+
@discriminates(
1214+
with: "kind"
1215+
aliases: [{ value: "User", type: "Employee" }]
1216+
) {
12071217
name: String! @field(at: "name")
12081218
}
12091219

0 commit comments

Comments
 (0)