@model-ts/graphql 0.0.0-stage → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +1280 -2
- package/dist/.tsbuildinfo +1 -0
- package/dist/Assembly.d.ts +24 -0
- package/dist/Assembly.d.ts.map +1 -0
- package/dist/Assembly.js +236 -0
- package/dist/Assembly.js.map +1 -0
- package/dist/Builder.d.ts +27 -0
- package/dist/Builder.d.ts.map +1 -0
- package/dist/Builder.js +40 -0
- package/dist/Builder.js.map +1 -0
- package/dist/Connections.d.ts +41 -0
- package/dist/Connections.d.ts.map +1 -0
- package/dist/Connections.js +243 -0
- package/dist/Connections.js.map +1 -0
- package/dist/Definitions.d.ts +139 -0
- package/dist/Definitions.d.ts.map +1 -0
- package/dist/Definitions.js +117 -0
- package/dist/Definitions.js.map +1 -0
- package/dist/Errors.d.ts +39 -0
- package/dist/Errors.d.ts.map +1 -0
- package/dist/Errors.js +103 -0
- package/dist/Errors.js.map +1 -0
- package/dist/Execution.d.ts +52 -0
- package/dist/Execution.d.ts.map +1 -0
- package/dist/Execution.js +174 -0
- package/dist/Execution.js.map +1 -0
- package/dist/FieldRuntime.d.ts +5 -0
- package/dist/FieldRuntime.d.ts.map +1 -0
- package/dist/FieldRuntime.js +27 -0
- package/dist/FieldRuntime.js.map +1 -0
- package/dist/FieldTypes.d.ts +232 -0
- package/dist/FieldTypes.d.ts.map +1 -0
- package/dist/FieldTypes.js +6 -0
- package/dist/FieldTypes.js.map +1 -0
- package/dist/Fields.d.ts +8 -0
- package/dist/Fields.d.ts.map +1 -0
- package/dist/Fields.js +72 -0
- package/dist/Fields.js.map +1 -0
- package/dist/Formatting.d.ts +25 -0
- package/dist/Formatting.d.ts.map +1 -0
- package/dist/Formatting.js +29 -0
- package/dist/Formatting.js.map +1 -0
- package/dist/GraphQL.d.ts +10 -0
- package/dist/GraphQL.d.ts.map +1 -0
- package/dist/GraphQL.js +5 -0
- package/dist/GraphQL.js.map +1 -0
- package/dist/InputTypes.d.ts +27 -0
- package/dist/InputTypes.d.ts.map +1 -0
- package/dist/InputTypes.js +203 -0
- package/dist/InputTypes.js.map +1 -0
- package/dist/Registry.d.ts +70 -0
- package/dist/Registry.d.ts.map +1 -0
- package/dist/Registry.js +3 -0
- package/dist/Registry.js.map +1 -0
- package/dist/Resolver.d.ts +30 -0
- package/dist/Resolver.d.ts.map +1 -0
- package/dist/Resolver.js +76 -0
- package/dist/Resolver.js.map +1 -0
- package/dist/Results.d.ts +20 -0
- package/dist/Results.d.ts.map +1 -0
- package/dist/Results.js +79 -0
- package/dist/Results.js.map +1 -0
- package/dist/ScalarBoundary.d.ts +9 -0
- package/dist/ScalarBoundary.d.ts.map +1 -0
- package/dist/ScalarBoundary.js +31 -0
- package/dist/ScalarBoundary.js.map +1 -0
- package/dist/Scalars.d.ts +9 -0
- package/dist/Scalars.d.ts.map +1 -0
- package/dist/Scalars.js +104 -0
- package/dist/Scalars.js.map +1 -0
- package/dist/SchemaTypes.d.ts +55 -0
- package/dist/SchemaTypes.d.ts.map +1 -0
- package/dist/SchemaTypes.js +47 -0
- package/dist/SchemaTypes.js.map +1 -0
- package/dist/Subscription.d.ts +6 -0
- package/dist/Subscription.d.ts.map +1 -0
- package/dist/Subscription.js +60 -0
- package/dist/Subscription.js.map +1 -0
- package/dist/Testing.d.ts +52 -0
- package/dist/Testing.d.ts.map +1 -0
- package/dist/Testing.js +74 -0
- package/dist/Testing.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/SubscriptionRule.d.ts +8 -0
- package/dist/internal/SubscriptionRule.d.ts.map +1 -0
- package/dist/internal/SubscriptionRule.js +73 -0
- package/dist/internal/SubscriptionRule.js.map +1 -0
- package/package.json +55 -4
- package/src/Assembly.ts +352 -0
- package/src/Builder.ts +110 -0
- package/src/Connections.ts +374 -0
- package/src/Definitions.ts +465 -0
- package/src/Errors.ts +188 -0
- package/src/Execution.ts +354 -0
- package/src/FieldRuntime.ts +32 -0
- package/src/FieldTypes.ts +667 -0
- package/src/Fields.ts +117 -0
- package/src/Formatting.ts +54 -0
- package/src/GraphQL.ts +50 -0
- package/src/InputTypes.ts +266 -0
- package/src/Registry.ts +157 -0
- package/src/Resolver.ts +176 -0
- package/src/Results.ts +128 -0
- package/src/ScalarBoundary.ts +30 -0
- package/src/Scalars.ts +143 -0
- package/src/SchemaTypes.ts +90 -0
- package/src/Subscription.ts +93 -0
- package/src/Testing.ts +144 -0
- package/src/index.ts +1 -0
- package/src/internal/SubscriptionRule.ts +90 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { Effect } from "effect"
|
|
2
|
+
import {
|
|
3
|
+
GraphQLBoolean,
|
|
4
|
+
GraphQLInt,
|
|
5
|
+
GraphQLString,
|
|
6
|
+
type GraphQLFieldConfigArgumentMap,
|
|
7
|
+
GraphQLList,
|
|
8
|
+
GraphQLNonNull,
|
|
9
|
+
GraphQLObjectType,
|
|
10
|
+
type GraphQLFieldConfigMap,
|
|
11
|
+
type GraphQLNamedType,
|
|
12
|
+
type GraphQLInterfaceType,
|
|
13
|
+
type GraphQLUnionType,
|
|
14
|
+
type GraphQLResolveInfo,
|
|
15
|
+
} from "graphql"
|
|
16
|
+
import { type ErrorPolicy, fieldError, protect } from "./Errors.ts"
|
|
17
|
+
import { type Field, type RequestContext } from "./FieldTypes.ts"
|
|
18
|
+
import {
|
|
19
|
+
type AnyResolve,
|
|
20
|
+
type AnyConfig,
|
|
21
|
+
config,
|
|
22
|
+
resolverField,
|
|
23
|
+
} from "./Resolver.ts"
|
|
24
|
+
import { invalid } from "./SchemaTypes.ts"
|
|
25
|
+
|
|
26
|
+
export interface PageInfo {
|
|
27
|
+
readonly hasNextPage: boolean
|
|
28
|
+
readonly hasPreviousPage: boolean
|
|
29
|
+
readonly startCursor: string | null
|
|
30
|
+
readonly endCursor: string | null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface Edge<A> {
|
|
34
|
+
readonly cursor: string
|
|
35
|
+
readonly node: A
|
|
36
|
+
}
|
|
37
|
+
/** Structurally identical to model-ts' PaginationResult, so paginate() results pass through. */
|
|
38
|
+
|
|
39
|
+
export interface Connection<A> {
|
|
40
|
+
readonly edges: ReadonlyArray<Edge<A>>
|
|
41
|
+
readonly pageInfo: PageInfo
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ConnectionArgs {
|
|
45
|
+
readonly first?: number | null
|
|
46
|
+
readonly after?: string | null
|
|
47
|
+
readonly last?: number | null
|
|
48
|
+
readonly before?: string | null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const encodeCursor = (offset: number) => btoa(`cursor:${offset}`)
|
|
52
|
+
|
|
53
|
+
export function decodeCursor(cursor: string): number {
|
|
54
|
+
try {
|
|
55
|
+
const match = /^cursor:(\d+)$/.exec(atob(cursor))
|
|
56
|
+
if (match && Number.isSafeInteger(Number(match[1]))) return Number(match[1])
|
|
57
|
+
} catch {
|
|
58
|
+
/* Reject invalid base64 using the same public error. */
|
|
59
|
+
}
|
|
60
|
+
throw fieldError("Invalid cursor", { code: "BAD_USER_INPUT" })
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** graphql-js has already validated the four built-in pagination arguments. */
|
|
64
|
+
export function paginationArgs(
|
|
65
|
+
raw: Readonly<Record<string, unknown>>,
|
|
66
|
+
): ConnectionArgs {
|
|
67
|
+
const pick = <K extends keyof ConnectionArgs>(key: K) =>
|
|
68
|
+
raw[key] == null ? {} : { [key]: raw[key] }
|
|
69
|
+
return {
|
|
70
|
+
...pick("first"),
|
|
71
|
+
...pick("after"),
|
|
72
|
+
...pick("last"),
|
|
73
|
+
...pick("before"),
|
|
74
|
+
} as ConnectionArgs
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function validatePagination(
|
|
78
|
+
args: ConnectionArgs,
|
|
79
|
+
coordinate: string,
|
|
80
|
+
max: number,
|
|
81
|
+
): void {
|
|
82
|
+
const reject = (message: string): never => {
|
|
83
|
+
throw fieldError(`${coordinate}: ${message}`, { code: "BAD_USER_INPUT" })
|
|
84
|
+
}
|
|
85
|
+
if (args.after != null && args.before != null)
|
|
86
|
+
reject('use "after" or "before", not both')
|
|
87
|
+
if (args.first != null && args.last != null)
|
|
88
|
+
reject('use "first" or "last", not both')
|
|
89
|
+
if (args.first != null && args.before != null)
|
|
90
|
+
reject('"before" cannot be combined with "first"')
|
|
91
|
+
if (args.last != null && args.after != null)
|
|
92
|
+
reject('"after" cannot be combined with "last"')
|
|
93
|
+
for (const key of ["first", "last"] as const) {
|
|
94
|
+
const size = args[key]
|
|
95
|
+
if (size != null && (size < 0 || size > max))
|
|
96
|
+
reject(`"${key}" must be between 0 and ${max}`)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Validation sees only caller arguments; a before-only request defaults backward. */
|
|
101
|
+
export function pageDefaults(
|
|
102
|
+
args: ConnectionArgs,
|
|
103
|
+
size: number,
|
|
104
|
+
): ConnectionArgs {
|
|
105
|
+
if (args.first != null || args.last != null) return args
|
|
106
|
+
return args.before != null
|
|
107
|
+
? { ...args, last: size }
|
|
108
|
+
: { ...args, first: size }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function slice<A>(
|
|
112
|
+
nodes: ReadonlyArray<A>,
|
|
113
|
+
args: ConnectionArgs,
|
|
114
|
+
): Connection<A> {
|
|
115
|
+
let start =
|
|
116
|
+
args.after == null
|
|
117
|
+
? 0
|
|
118
|
+
: Math.min(nodes.length, decodeCursor(args.after) + 1)
|
|
119
|
+
let end =
|
|
120
|
+
args.before == null
|
|
121
|
+
? nodes.length
|
|
122
|
+
: Math.min(nodes.length, decodeCursor(args.before))
|
|
123
|
+
if (args.first != null) end = Math.min(end, start + args.first)
|
|
124
|
+
if (args.last != null) start = Math.max(start, end - args.last)
|
|
125
|
+
const edges = nodes
|
|
126
|
+
.slice(start, Math.max(start, end))
|
|
127
|
+
.map((node, index) => ({ cursor: encodeCursor(start + index), node }))
|
|
128
|
+
return {
|
|
129
|
+
edges,
|
|
130
|
+
pageInfo: {
|
|
131
|
+
hasPreviousPage: start > 0,
|
|
132
|
+
hasNextPage: end < nodes.length,
|
|
133
|
+
startCursor: edges[0]?.cursor ?? null,
|
|
134
|
+
endCursor: edges.at(-1)?.cursor ?? null,
|
|
135
|
+
},
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export const connectionArgs: GraphQLFieldConfigArgumentMap = {
|
|
140
|
+
first: {
|
|
141
|
+
type: GraphQLInt,
|
|
142
|
+
description: "Returns the first n elements from the list.",
|
|
143
|
+
},
|
|
144
|
+
after: {
|
|
145
|
+
type: GraphQLString,
|
|
146
|
+
description:
|
|
147
|
+
"Returns the elements in the list that come after the specified cursor",
|
|
148
|
+
},
|
|
149
|
+
last: {
|
|
150
|
+
type: GraphQLInt,
|
|
151
|
+
description: "Returns the last n elements from the list.",
|
|
152
|
+
},
|
|
153
|
+
before: {
|
|
154
|
+
type: GraphQLString,
|
|
155
|
+
description:
|
|
156
|
+
"Returns the elements in the list that come before the specified cursor",
|
|
157
|
+
},
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export const connectionField = (
|
|
161
|
+
policy: ErrorPolicy,
|
|
162
|
+
maxPageSize: number,
|
|
163
|
+
defaultPageSize: number,
|
|
164
|
+
node: string,
|
|
165
|
+
input: AnyResolve | AnyConfig = {},
|
|
166
|
+
): Field<unknown> => {
|
|
167
|
+
const options = config(input)
|
|
168
|
+
const { resolve, nodes, totalCount } = options
|
|
169
|
+
for (const key of Object.keys(options.args ?? {}))
|
|
170
|
+
if (Object.hasOwn(connectionArgs, key))
|
|
171
|
+
invalid(`Reserved connection argument: ${key}`)
|
|
172
|
+
return resolverField(policy, options, {
|
|
173
|
+
type: (context, name) =>
|
|
174
|
+
context.connection(
|
|
175
|
+
node,
|
|
176
|
+
options.name,
|
|
177
|
+
name,
|
|
178
|
+
options.totalCount !== undefined,
|
|
179
|
+
options.edgeName,
|
|
180
|
+
),
|
|
181
|
+
args: options.args ?? {},
|
|
182
|
+
extraArgs: connectionArgs,
|
|
183
|
+
members: [],
|
|
184
|
+
call: (source, raw, decoded, coordinate, context) => {
|
|
185
|
+
const supplied = paginationArgs(raw)
|
|
186
|
+
validatePagination(supplied, coordinate, maxPageSize)
|
|
187
|
+
const page = nodes ? pageDefaults(supplied, defaultPageSize) : supplied
|
|
188
|
+
if (nodes) {
|
|
189
|
+
if (page.after != null) decodeCursor(page.after)
|
|
190
|
+
if (page.before != null) decodeCursor(page.before)
|
|
191
|
+
}
|
|
192
|
+
// Resolver and nodes overloads prove these shapes; this runtime path
|
|
193
|
+
// handles heterogeneous field values after their type parameters are erased.
|
|
194
|
+
const value = resolve
|
|
195
|
+
? resolve(source, { ...(decoded as object), ...page }, context)
|
|
196
|
+
: nodes
|
|
197
|
+
? nodes(source, decoded, context)
|
|
198
|
+
: Reflect.get(
|
|
199
|
+
Object(source),
|
|
200
|
+
coordinate.slice(coordinate.lastIndexOf(".") + 1),
|
|
201
|
+
)
|
|
202
|
+
if (!totalCount) return value
|
|
203
|
+
const finish = (result: unknown) => ({
|
|
204
|
+
...(resolve
|
|
205
|
+
? (result as object)
|
|
206
|
+
: slice(result as readonly unknown[], page)),
|
|
207
|
+
// graphql-js invokes function-valued fields only when selected, including
|
|
208
|
+
// fragments and directives. Keep the Effect bridge and policy at that boundary.
|
|
209
|
+
totalCount: (
|
|
210
|
+
_args: unknown,
|
|
211
|
+
request: RequestContext,
|
|
212
|
+
info: GraphQLResolveInfo,
|
|
213
|
+
) =>
|
|
214
|
+
request.run(
|
|
215
|
+
protect(() => totalCount(source, decoded, context), [], policy, {
|
|
216
|
+
coordinate: `${info.parentType.name}.${info.fieldName}`,
|
|
217
|
+
phase: "resolve",
|
|
218
|
+
}),
|
|
219
|
+
),
|
|
220
|
+
})
|
|
221
|
+
return Effect.isEffect(value) ? Effect.map(value, finish) : finish(value)
|
|
222
|
+
},
|
|
223
|
+
finish: (value, raw) =>
|
|
224
|
+
!nodes || options.totalCount
|
|
225
|
+
? value
|
|
226
|
+
: slice(
|
|
227
|
+
value as ReadonlyArray<unknown>,
|
|
228
|
+
pageDefaults(paginationArgs(raw), defaultPageSize),
|
|
229
|
+
),
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Derived names are reserved only when a field actually requests them. */
|
|
234
|
+
export function makeConnectionTypes(
|
|
235
|
+
named: Map<string, GraphQLNamedType>,
|
|
236
|
+
add: <T extends GraphQLNamedType>(type: T) => T,
|
|
237
|
+
nodeType: (
|
|
238
|
+
name: string,
|
|
239
|
+
coordinate: string,
|
|
240
|
+
) => GraphQLObjectType | GraphQLInterfaceType | GraphQLUnionType,
|
|
241
|
+
isNode: (name: string) => boolean,
|
|
242
|
+
) {
|
|
243
|
+
const generated = new Map<
|
|
244
|
+
string,
|
|
245
|
+
{ readonly type: GraphQLObjectType; readonly shape: string }
|
|
246
|
+
>()
|
|
247
|
+
const derivedObject = (
|
|
248
|
+
name: string,
|
|
249
|
+
shape: string,
|
|
250
|
+
fields: () => GraphQLFieldConfigMap<unknown, unknown>,
|
|
251
|
+
description?: string,
|
|
252
|
+
) => {
|
|
253
|
+
const existing = generated.get(name)
|
|
254
|
+
if (existing) {
|
|
255
|
+
if (existing.shape !== shape)
|
|
256
|
+
invalid(`Incompatible derived type ${name}; use a different edgeName`)
|
|
257
|
+
return existing.type
|
|
258
|
+
}
|
|
259
|
+
// add owns the global namespace: a real object may use a synthetic-looking
|
|
260
|
+
// name, but deriving that same name must report a collision.
|
|
261
|
+
const type = add(new GraphQLObjectType({ name, fields, description }))
|
|
262
|
+
generated.set(name, { type, shape })
|
|
263
|
+
return type
|
|
264
|
+
}
|
|
265
|
+
const pageInfo = () =>
|
|
266
|
+
derivedObject(
|
|
267
|
+
"PageInfo",
|
|
268
|
+
"pageInfo",
|
|
269
|
+
() => ({
|
|
270
|
+
hasNextPage: {
|
|
271
|
+
type: new GraphQLNonNull(GraphQLBoolean),
|
|
272
|
+
description:
|
|
273
|
+
"Used to indicate whether more edges exist following the set defined by the clients arguments.",
|
|
274
|
+
},
|
|
275
|
+
hasPreviousPage: {
|
|
276
|
+
type: new GraphQLNonNull(GraphQLBoolean),
|
|
277
|
+
description:
|
|
278
|
+
"Used to indicate whether more edges exist prior to the set defined by the clients arguments.",
|
|
279
|
+
},
|
|
280
|
+
startCursor: {
|
|
281
|
+
type: GraphQLString,
|
|
282
|
+
description:
|
|
283
|
+
"The cursor corresponding to the first nodes in edges. Null if the connection is empty.",
|
|
284
|
+
},
|
|
285
|
+
endCursor: {
|
|
286
|
+
type: GraphQLString,
|
|
287
|
+
description:
|
|
288
|
+
"The cursor corresponding to the last nodes in edges. Null if the connection is empty.",
|
|
289
|
+
},
|
|
290
|
+
}),
|
|
291
|
+
"PageInfo cursor, as defined in https://facebook.github.io/relay/graphql/connections.htm#sec-undefined.PageInfo",
|
|
292
|
+
)
|
|
293
|
+
const edgeNodes = new Map<string, string>()
|
|
294
|
+
const edgeType = (node: string, name = `${node}Edge`) => {
|
|
295
|
+
const owner = edgeNodes.get(name)
|
|
296
|
+
if (owner !== undefined && owner !== node)
|
|
297
|
+
invalid(
|
|
298
|
+
`Incompatible edge ${name}: nodes ${owner} and ${node}; use a different edgeName`,
|
|
299
|
+
)
|
|
300
|
+
edgeNodes.set(name, node)
|
|
301
|
+
return derivedObject(name, `edge:${node}`, () => ({
|
|
302
|
+
cursor: {
|
|
303
|
+
type: new GraphQLNonNull(GraphQLString),
|
|
304
|
+
description:
|
|
305
|
+
"https://facebook.github.io/relay/graphql/connections.htm#sec-Cursor",
|
|
306
|
+
},
|
|
307
|
+
node: {
|
|
308
|
+
type: new GraphQLNonNull(nodeType(node, `${name}.node`)),
|
|
309
|
+
description:
|
|
310
|
+
"https://facebook.github.io/relay/graphql/connections.htm#sec-Node",
|
|
311
|
+
},
|
|
312
|
+
}))
|
|
313
|
+
}
|
|
314
|
+
const connectionEdges = new Map<string, string>()
|
|
315
|
+
const connectionNodes = new Map<string, string>()
|
|
316
|
+
const countedConnections = new Map<string, boolean>()
|
|
317
|
+
const connectionType = (
|
|
318
|
+
node: string,
|
|
319
|
+
name: string,
|
|
320
|
+
counted = false,
|
|
321
|
+
edgeName = `${node}Edge`,
|
|
322
|
+
): GraphQLObjectType => {
|
|
323
|
+
const existing = named.get(name)
|
|
324
|
+
if (existing) {
|
|
325
|
+
if (
|
|
326
|
+
connectionNodes.get(name) !== node ||
|
|
327
|
+
countedConnections.get(name) !== counted ||
|
|
328
|
+
connectionEdges.get(name) !== edgeName
|
|
329
|
+
)
|
|
330
|
+
invalid(
|
|
331
|
+
`Duplicate GraphQL name: ${name}; already used for ${connectionNodes.get(name) ?? "another type"}`,
|
|
332
|
+
)
|
|
333
|
+
if (!(existing instanceof GraphQLObjectType))
|
|
334
|
+
return invalid(`Expected connection object: ${name}`)
|
|
335
|
+
return existing
|
|
336
|
+
}
|
|
337
|
+
connectionEdges.set(name, edgeName)
|
|
338
|
+
connectionNodes.set(name, node)
|
|
339
|
+
countedConnections.set(name, counted)
|
|
340
|
+
return add(
|
|
341
|
+
new GraphQLObjectType({
|
|
342
|
+
name,
|
|
343
|
+
fields: () => ({
|
|
344
|
+
edges: {
|
|
345
|
+
description:
|
|
346
|
+
"https://facebook.github.io/relay/graphql/connections.htm#sec-Edge-Types",
|
|
347
|
+
type: new GraphQLNonNull(
|
|
348
|
+
new GraphQLList(new GraphQLNonNull(edgeType(node, edgeName))),
|
|
349
|
+
),
|
|
350
|
+
},
|
|
351
|
+
pageInfo: {
|
|
352
|
+
type: new GraphQLNonNull(pageInfo()),
|
|
353
|
+
description:
|
|
354
|
+
"https://facebook.github.io/relay/graphql/connections.htm#sec-undefined.PageInfo",
|
|
355
|
+
},
|
|
356
|
+
...(counted
|
|
357
|
+
? { totalCount: { type: new GraphQLNonNull(GraphQLInt) } }
|
|
358
|
+
: {}),
|
|
359
|
+
}),
|
|
360
|
+
}),
|
|
361
|
+
)
|
|
362
|
+
}
|
|
363
|
+
function derived(name: string): GraphQLNamedType | undefined {
|
|
364
|
+
if (name === "PageInfo") return pageInfo()
|
|
365
|
+
for (const suffix of ["Edge", "Connection"] as const) {
|
|
366
|
+
const node = name.slice(0, -suffix.length)
|
|
367
|
+
if (!name.endsWith(suffix) || !isNode(node)) continue
|
|
368
|
+
return suffix === "Edge" ? edgeType(node) : connectionType(node, name)
|
|
369
|
+
}
|
|
370
|
+
return undefined
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return { derived, connectionType }
|
|
374
|
+
}
|