@effect-gql/core 0.1.0 → 1.1.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/README.md +100 -0
- package/builder/index.cjs +1446 -0
- package/builder/index.cjs.map +1 -0
- package/builder/index.d.cts +260 -0
- package/{dist/builder/pipe-api.d.ts → builder/index.d.ts} +50 -21
- package/builder/index.js +1405 -0
- package/builder/index.js.map +1 -0
- package/index.cjs +3469 -0
- package/index.cjs.map +1 -0
- package/index.d.cts +529 -0
- package/index.d.ts +529 -0
- package/index.js +3292 -0
- package/index.js.map +1 -0
- package/package.json +19 -28
- package/schema-builder-DKvkzU_M.d.cts +965 -0
- package/schema-builder-DKvkzU_M.d.ts +965 -0
- package/server/index.cjs +1579 -0
- package/server/index.cjs.map +1 -0
- package/server/index.d.cts +682 -0
- package/server/index.d.ts +682 -0
- package/server/index.js +1548 -0
- package/server/index.js.map +1 -0
- package/dist/analyzer-extension.d.ts +0 -105
- package/dist/analyzer-extension.d.ts.map +0 -1
- package/dist/analyzer-extension.js +0 -137
- package/dist/analyzer-extension.js.map +0 -1
- package/dist/builder/execute.d.ts +0 -26
- package/dist/builder/execute.d.ts.map +0 -1
- package/dist/builder/execute.js +0 -104
- package/dist/builder/execute.js.map +0 -1
- package/dist/builder/field-builders.d.ts +0 -30
- package/dist/builder/field-builders.d.ts.map +0 -1
- package/dist/builder/field-builders.js +0 -200
- package/dist/builder/field-builders.js.map +0 -1
- package/dist/builder/index.d.ts +0 -7
- package/dist/builder/index.d.ts.map +0 -1
- package/dist/builder/index.js +0 -31
- package/dist/builder/index.js.map +0 -1
- package/dist/builder/pipe-api.d.ts.map +0 -1
- package/dist/builder/pipe-api.js +0 -151
- package/dist/builder/pipe-api.js.map +0 -1
- package/dist/builder/schema-builder.d.ts +0 -301
- package/dist/builder/schema-builder.d.ts.map +0 -1
- package/dist/builder/schema-builder.js +0 -566
- package/dist/builder/schema-builder.js.map +0 -1
- package/dist/builder/type-registry.d.ts +0 -80
- package/dist/builder/type-registry.d.ts.map +0 -1
- package/dist/builder/type-registry.js +0 -505
- package/dist/builder/type-registry.js.map +0 -1
- package/dist/builder/types.d.ts +0 -283
- package/dist/builder/types.d.ts.map +0 -1
- package/dist/builder/types.js +0 -3
- package/dist/builder/types.js.map +0 -1
- package/dist/cli/generate-schema.d.ts +0 -29
- package/dist/cli/generate-schema.d.ts.map +0 -1
- package/dist/cli/generate-schema.js +0 -233
- package/dist/cli/generate-schema.js.map +0 -1
- package/dist/cli/index.d.ts +0 -19
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -24
- package/dist/cli/index.js.map +0 -1
- package/dist/context.d.ts +0 -18
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -11
- package/dist/context.js.map +0 -1
- package/dist/error.d.ts +0 -45
- package/dist/error.d.ts.map +0 -1
- package/dist/error.js +0 -29
- package/dist/error.js.map +0 -1
- package/dist/extensions.d.ts +0 -130
- package/dist/extensions.d.ts.map +0 -1
- package/dist/extensions.js +0 -78
- package/dist/extensions.js.map +0 -1
- package/dist/index.d.ts +0 -12
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -47
- package/dist/index.js.map +0 -1
- package/dist/loader.d.ts +0 -169
- package/dist/loader.d.ts.map +0 -1
- package/dist/loader.js +0 -237
- package/dist/loader.js.map +0 -1
- package/dist/resolver-context.d.ts +0 -154
- package/dist/resolver-context.d.ts.map +0 -1
- package/dist/resolver-context.js +0 -184
- package/dist/resolver-context.js.map +0 -1
- package/dist/schema-mapping.d.ts +0 -30
- package/dist/schema-mapping.d.ts.map +0 -1
- package/dist/schema-mapping.js +0 -280
- package/dist/schema-mapping.js.map +0 -1
- package/dist/server/cache-control.d.ts +0 -96
- package/dist/server/cache-control.d.ts.map +0 -1
- package/dist/server/cache-control.js +0 -308
- package/dist/server/cache-control.js.map +0 -1
- package/dist/server/complexity.d.ts +0 -165
- package/dist/server/complexity.d.ts.map +0 -1
- package/dist/server/complexity.js +0 -433
- package/dist/server/complexity.js.map +0 -1
- package/dist/server/config.d.ts +0 -66
- package/dist/server/config.d.ts.map +0 -1
- package/dist/server/config.js +0 -104
- package/dist/server/config.js.map +0 -1
- package/dist/server/graphiql.d.ts +0 -5
- package/dist/server/graphiql.d.ts.map +0 -1
- package/dist/server/graphiql.js +0 -43
- package/dist/server/graphiql.js.map +0 -1
- package/dist/server/index.d.ts +0 -18
- package/dist/server/index.d.ts.map +0 -1
- package/dist/server/index.js +0 -48
- package/dist/server/index.js.map +0 -1
- package/dist/server/router.d.ts +0 -79
- package/dist/server/router.d.ts.map +0 -1
- package/dist/server/router.js +0 -232
- package/dist/server/router.js.map +0 -1
- package/dist/server/schema-builder-extensions.d.ts +0 -42
- package/dist/server/schema-builder-extensions.d.ts.map +0 -1
- package/dist/server/schema-builder-extensions.js +0 -48
- package/dist/server/schema-builder-extensions.js.map +0 -1
- package/dist/server/sse-adapter.d.ts +0 -64
- package/dist/server/sse-adapter.d.ts.map +0 -1
- package/dist/server/sse-adapter.js +0 -227
- package/dist/server/sse-adapter.js.map +0 -1
- package/dist/server/sse-types.d.ts +0 -192
- package/dist/server/sse-types.d.ts.map +0 -1
- package/dist/server/sse-types.js +0 -63
- package/dist/server/sse-types.js.map +0 -1
- package/dist/server/ws-adapter.d.ts +0 -39
- package/dist/server/ws-adapter.d.ts.map +0 -1
- package/dist/server/ws-adapter.js +0 -247
- package/dist/server/ws-adapter.js.map +0 -1
- package/dist/server/ws-types.d.ts +0 -169
- package/dist/server/ws-types.d.ts.map +0 -1
- package/dist/server/ws-types.js +0 -11
- package/dist/server/ws-types.js.map +0 -1
- package/dist/server/ws-utils.d.ts +0 -42
- package/dist/server/ws-utils.d.ts.map +0 -1
- package/dist/server/ws-utils.js +0 -99
- package/dist/server/ws-utils.js.map +0 -1
- package/src/analyzer-extension.ts +0 -254
- package/src/builder/execute.ts +0 -153
- package/src/builder/field-builders.ts +0 -322
- package/src/builder/index.ts +0 -48
- package/src/builder/pipe-api.ts +0 -312
- package/src/builder/schema-builder.ts +0 -970
- package/src/builder/type-registry.ts +0 -670
- package/src/builder/types.ts +0 -305
- package/src/context.ts +0 -23
- package/src/error.ts +0 -32
- package/src/extensions.ts +0 -240
- package/src/index.ts +0 -32
- package/src/loader.ts +0 -363
- package/src/resolver-context.ts +0 -253
- package/src/schema-mapping.ts +0 -307
- package/src/server/cache-control.ts +0 -590
- package/src/server/complexity.ts +0 -774
- package/src/server/config.ts +0 -174
- package/src/server/graphiql.ts +0 -38
- package/src/server/index.ts +0 -96
- package/src/server/router.ts +0 -432
- package/src/server/schema-builder-extensions.ts +0 -51
- package/src/server/sse-adapter.ts +0 -327
- package/src/server/sse-types.ts +0 -234
- package/src/server/ws-adapter.ts +0 -355
- package/src/server/ws-types.ts +0 -192
- package/src/server/ws-utils.ts +0 -136
package/src/server/router.ts
DELETED
|
@@ -1,432 +0,0 @@
|
|
|
1
|
-
import { HttpRouter, HttpServerRequest, HttpServerResponse } from "@effect/platform"
|
|
2
|
-
import { Cause, Context, Effect, Layer } from "effect"
|
|
3
|
-
import {
|
|
4
|
-
GraphQLSchema,
|
|
5
|
-
parse,
|
|
6
|
-
validate,
|
|
7
|
-
specifiedRules,
|
|
8
|
-
NoSchemaIntrospectionCustomRule,
|
|
9
|
-
execute as graphqlExecute,
|
|
10
|
-
Kind,
|
|
11
|
-
type DocumentNode,
|
|
12
|
-
type OperationDefinitionNode,
|
|
13
|
-
} from "graphql"
|
|
14
|
-
import type { GraphQLEffectContext } from "../builder/types"
|
|
15
|
-
import { graphiqlHtml } from "./graphiql"
|
|
16
|
-
import { normalizeConfig, type GraphQLRouterConfigInput } from "./config"
|
|
17
|
-
import {
|
|
18
|
-
validateComplexity,
|
|
19
|
-
ComplexityLimitExceededError,
|
|
20
|
-
type FieldComplexityMap,
|
|
21
|
-
} from "./complexity"
|
|
22
|
-
import { computeCachePolicy, toCacheControlHeader, type CacheHintMap } from "./cache-control"
|
|
23
|
-
import {
|
|
24
|
-
type GraphQLExtension,
|
|
25
|
-
ExtensionsService,
|
|
26
|
-
makeExtensionsService,
|
|
27
|
-
runParseHooks,
|
|
28
|
-
runValidateHooks,
|
|
29
|
-
runExecuteStartHooks,
|
|
30
|
-
runExecuteEndHooks,
|
|
31
|
-
} from "../extensions"
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Error handler function type for handling uncaught errors during GraphQL execution.
|
|
35
|
-
* Receives the error cause and should return an HTTP response.
|
|
36
|
-
*/
|
|
37
|
-
export type ErrorHandler = (
|
|
38
|
-
cause: Cause.Cause<unknown>
|
|
39
|
-
) => Effect.Effect<HttpServerResponse.HttpServerResponse, never, never>
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Default error handler that returns a 500 Internal Server Error.
|
|
43
|
-
* In non-production environments, it logs the full error for debugging.
|
|
44
|
-
*/
|
|
45
|
-
export const defaultErrorHandler: ErrorHandler = (cause) =>
|
|
46
|
-
(process.env.NODE_ENV !== "production"
|
|
47
|
-
? Effect.logError("GraphQL error", cause)
|
|
48
|
-
: Effect.void
|
|
49
|
-
).pipe(
|
|
50
|
-
Effect.andThen(
|
|
51
|
-
HttpServerResponse.json(
|
|
52
|
-
{
|
|
53
|
-
errors: [
|
|
54
|
-
{
|
|
55
|
-
message: "An error occurred processing your request",
|
|
56
|
-
},
|
|
57
|
-
],
|
|
58
|
-
},
|
|
59
|
-
{ status: 500 }
|
|
60
|
-
).pipe(Effect.orDie)
|
|
61
|
-
)
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Request body for GraphQL queries.
|
|
66
|
-
*/
|
|
67
|
-
interface GraphQLRequestBody {
|
|
68
|
-
query: string
|
|
69
|
-
variables?: Record<string, unknown>
|
|
70
|
-
operationName?: string
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Parse a GraphQL query string into a DocumentNode.
|
|
75
|
-
* Returns the document or an error response if parsing fails.
|
|
76
|
-
*/
|
|
77
|
-
const parseGraphQLQuery = (
|
|
78
|
-
query: string,
|
|
79
|
-
extensionsService: Context.Tag.Service<typeof ExtensionsService>
|
|
80
|
-
): Effect.Effect<
|
|
81
|
-
| { ok: true; document: DocumentNode }
|
|
82
|
-
| { ok: false; response: HttpServerResponse.HttpServerResponse },
|
|
83
|
-
never,
|
|
84
|
-
never
|
|
85
|
-
> =>
|
|
86
|
-
Effect.gen(function* () {
|
|
87
|
-
try {
|
|
88
|
-
const document = parse(query)
|
|
89
|
-
return { ok: true as const, document }
|
|
90
|
-
} catch (parseError) {
|
|
91
|
-
const extensionData = yield* extensionsService.get()
|
|
92
|
-
const response = yield* HttpServerResponse.json({
|
|
93
|
-
errors: [{ message: String(parseError) }],
|
|
94
|
-
extensions: Object.keys(extensionData).length > 0 ? extensionData : undefined,
|
|
95
|
-
}).pipe(Effect.orDie)
|
|
96
|
-
return { ok: false as const, response }
|
|
97
|
-
}
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Run complexity validation if configured.
|
|
102
|
-
* Logs warnings for analysis errors but doesn't block execution.
|
|
103
|
-
*/
|
|
104
|
-
const runComplexityValidation = (
|
|
105
|
-
body: GraphQLRequestBody,
|
|
106
|
-
schema: GraphQLSchema,
|
|
107
|
-
fieldComplexities: FieldComplexityMap,
|
|
108
|
-
complexityConfig: { maxDepth?: number; maxComplexity?: number } | undefined
|
|
109
|
-
): Effect.Effect<void, ComplexityLimitExceededError, never> => {
|
|
110
|
-
if (!complexityConfig) {
|
|
111
|
-
return Effect.void
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return validateComplexity(
|
|
115
|
-
body.query,
|
|
116
|
-
body.operationName,
|
|
117
|
-
body.variables,
|
|
118
|
-
schema,
|
|
119
|
-
fieldComplexities,
|
|
120
|
-
complexityConfig
|
|
121
|
-
).pipe(
|
|
122
|
-
Effect.catchTag("ComplexityLimitExceededError", (error) => Effect.fail(error)),
|
|
123
|
-
Effect.catchTag("ComplexityAnalysisError", (error) =>
|
|
124
|
-
Effect.logWarning("Complexity analysis failed", error)
|
|
125
|
-
)
|
|
126
|
-
)
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Execute a GraphQL query and handle async results.
|
|
131
|
-
*/
|
|
132
|
-
const executeGraphQLQuery = <R>(
|
|
133
|
-
schema: GraphQLSchema,
|
|
134
|
-
document: DocumentNode,
|
|
135
|
-
variables: Record<string, unknown> | undefined,
|
|
136
|
-
operationName: string | undefined,
|
|
137
|
-
runtime: import("effect").Runtime.Runtime<R>
|
|
138
|
-
): Effect.Effect<import("graphql").ExecutionResult, Error, never> =>
|
|
139
|
-
Effect.gen(function* () {
|
|
140
|
-
const executeResult = yield* Effect.try({
|
|
141
|
-
try: () =>
|
|
142
|
-
graphqlExecute({
|
|
143
|
-
schema,
|
|
144
|
-
document,
|
|
145
|
-
variableValues: variables,
|
|
146
|
-
operationName,
|
|
147
|
-
contextValue: { runtime } satisfies GraphQLEffectContext<R>,
|
|
148
|
-
}),
|
|
149
|
-
catch: (error) => new Error(String(error)),
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
// Await result if it's a promise
|
|
153
|
-
if (executeResult && typeof executeResult === "object" && "then" in executeResult) {
|
|
154
|
-
return yield* Effect.promise(
|
|
155
|
-
() => executeResult as Promise<import("graphql").ExecutionResult>
|
|
156
|
-
)
|
|
157
|
-
}
|
|
158
|
-
return executeResult as import("graphql").ExecutionResult
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Compute cache control header for the response if applicable.
|
|
163
|
-
*/
|
|
164
|
-
const computeCacheControlHeader = (
|
|
165
|
-
document: DocumentNode,
|
|
166
|
-
operationName: string | undefined,
|
|
167
|
-
schema: GraphQLSchema,
|
|
168
|
-
cacheHints: CacheHintMap,
|
|
169
|
-
cacheControlConfig:
|
|
170
|
-
| { enabled?: boolean; calculateHttpHeaders?: boolean; defaultMaxAge?: number }
|
|
171
|
-
| undefined
|
|
172
|
-
): Effect.Effect<string | undefined, never, never> =>
|
|
173
|
-
Effect.gen(function* () {
|
|
174
|
-
if (
|
|
175
|
-
cacheControlConfig?.enabled === false ||
|
|
176
|
-
cacheControlConfig?.calculateHttpHeaders === false
|
|
177
|
-
) {
|
|
178
|
-
return undefined
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Find the operation from the document
|
|
182
|
-
const operations = document.definitions.filter(
|
|
183
|
-
(d): d is OperationDefinitionNode => d.kind === Kind.OPERATION_DEFINITION
|
|
184
|
-
)
|
|
185
|
-
const operation = operationName
|
|
186
|
-
? operations.find((o) => o.name?.value === operationName)
|
|
187
|
-
: operations[0]
|
|
188
|
-
|
|
189
|
-
if (!operation || operation.operation === "mutation") {
|
|
190
|
-
// Mutations should not be cached
|
|
191
|
-
return undefined
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const cachePolicy = yield* computeCachePolicy({
|
|
195
|
-
document,
|
|
196
|
-
operation,
|
|
197
|
-
schema,
|
|
198
|
-
cacheHints,
|
|
199
|
-
config: cacheControlConfig ?? {},
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
return toCacheControlHeader(cachePolicy)
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Build the final GraphQL response with extensions merged in.
|
|
207
|
-
*/
|
|
208
|
-
const buildGraphQLResponse = (
|
|
209
|
-
result: import("graphql").ExecutionResult,
|
|
210
|
-
extensionData: Record<string, unknown>,
|
|
211
|
-
cacheControlHeader: string | undefined
|
|
212
|
-
): Effect.Effect<HttpServerResponse.HttpServerResponse, never, never> => {
|
|
213
|
-
const finalResult =
|
|
214
|
-
Object.keys(extensionData).length > 0
|
|
215
|
-
? {
|
|
216
|
-
...result,
|
|
217
|
-
extensions: {
|
|
218
|
-
...result.extensions,
|
|
219
|
-
...extensionData,
|
|
220
|
-
},
|
|
221
|
-
}
|
|
222
|
-
: result
|
|
223
|
-
|
|
224
|
-
const responseHeaders = cacheControlHeader ? { "cache-control": cacheControlHeader } : undefined
|
|
225
|
-
|
|
226
|
-
return HttpServerResponse.json(finalResult, { headers: responseHeaders }).pipe(Effect.orDie)
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Handle complexity limit exceeded error, returning appropriate response.
|
|
231
|
-
*/
|
|
232
|
-
const handleComplexityError = (
|
|
233
|
-
error: ComplexityLimitExceededError
|
|
234
|
-
): Effect.Effect<HttpServerResponse.HttpServerResponse, never, never> =>
|
|
235
|
-
HttpServerResponse.json(
|
|
236
|
-
{
|
|
237
|
-
errors: [
|
|
238
|
-
{
|
|
239
|
-
message: error.message,
|
|
240
|
-
extensions: {
|
|
241
|
-
code: "COMPLEXITY_LIMIT_EXCEEDED",
|
|
242
|
-
limitType: error.limitType,
|
|
243
|
-
limit: error.limit,
|
|
244
|
-
actual: error.actual,
|
|
245
|
-
},
|
|
246
|
-
},
|
|
247
|
-
],
|
|
248
|
-
},
|
|
249
|
-
{ status: 400 }
|
|
250
|
-
).pipe(Effect.orDie)
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Options for makeGraphQLRouter
|
|
254
|
-
*/
|
|
255
|
-
export interface MakeGraphQLRouterOptions extends GraphQLRouterConfigInput {
|
|
256
|
-
/**
|
|
257
|
-
* Field complexity definitions from the schema builder.
|
|
258
|
-
* If using toRouter(), this is automatically extracted from the builder.
|
|
259
|
-
* If using makeGraphQLRouter() directly, call builder.getFieldComplexities().
|
|
260
|
-
*/
|
|
261
|
-
readonly fieldComplexities?: FieldComplexityMap
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Cache hint definitions from the schema builder.
|
|
265
|
-
* If using toRouter(), this is automatically extracted from the builder.
|
|
266
|
-
* If using makeGraphQLRouter() directly, call builder.getCacheHints().
|
|
267
|
-
*/
|
|
268
|
-
readonly cacheHints?: CacheHintMap
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* GraphQL extensions for lifecycle hooks.
|
|
272
|
-
* If using toRouter(), this is automatically extracted from the builder.
|
|
273
|
-
* If using makeGraphQLRouter() directly, call builder.getExtensions().
|
|
274
|
-
*/
|
|
275
|
-
readonly extensions?: readonly GraphQLExtension<any>[]
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Custom error handler for uncaught errors during GraphQL execution.
|
|
279
|
-
* Receives the error cause and should return an HTTP response.
|
|
280
|
-
* Defaults to returning a 500 Internal Server Error with a generic message.
|
|
281
|
-
*/
|
|
282
|
-
readonly errorHandler?: ErrorHandler
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Create an HttpRouter configured for GraphQL
|
|
287
|
-
*
|
|
288
|
-
* The router handles:
|
|
289
|
-
* - POST requests to the GraphQL endpoint
|
|
290
|
-
* - GET requests to the GraphiQL UI (if enabled)
|
|
291
|
-
* - Query complexity validation (if configured)
|
|
292
|
-
* - Extension lifecycle hooks (onParse, onValidate, onExecuteStart, onExecuteEnd)
|
|
293
|
-
*
|
|
294
|
-
* @param schema - The GraphQL schema
|
|
295
|
-
* @param layer - Effect layer providing services required by resolvers
|
|
296
|
-
* @param options - Optional configuration for paths, GraphiQL, complexity, and extensions
|
|
297
|
-
* @returns An HttpRouter that can be composed with other routes
|
|
298
|
-
*
|
|
299
|
-
* @example
|
|
300
|
-
* ```typescript
|
|
301
|
-
* const router = makeGraphQLRouter(schema, Layer.empty, {
|
|
302
|
-
* path: "/graphql",
|
|
303
|
-
* graphiql: { path: "/graphiql" },
|
|
304
|
-
* complexity: { maxDepth: 10, maxComplexity: 1000 },
|
|
305
|
-
* fieldComplexities: builder.getFieldComplexities(),
|
|
306
|
-
* extensions: builder.getExtensions()
|
|
307
|
-
* })
|
|
308
|
-
*
|
|
309
|
-
* // Compose with other routes
|
|
310
|
-
* const app = HttpRouter.empty.pipe(
|
|
311
|
-
* HttpRouter.get("/health", HttpServerResponse.json({ status: "ok" })),
|
|
312
|
-
* HttpRouter.concat(router)
|
|
313
|
-
* )
|
|
314
|
-
* ```
|
|
315
|
-
*/
|
|
316
|
-
export const makeGraphQLRouter = <R>(
|
|
317
|
-
schema: GraphQLSchema,
|
|
318
|
-
layer: Layer.Layer<R>,
|
|
319
|
-
options: MakeGraphQLRouterOptions = {}
|
|
320
|
-
): HttpRouter.HttpRouter<never, never> => {
|
|
321
|
-
const resolvedConfig = normalizeConfig(options)
|
|
322
|
-
const fieldComplexities = options.fieldComplexities ?? new Map()
|
|
323
|
-
const cacheHints = options.cacheHints ?? new Map()
|
|
324
|
-
const extensions = options.extensions ?? []
|
|
325
|
-
const errorHandler = options.errorHandler ?? defaultErrorHandler
|
|
326
|
-
|
|
327
|
-
// GraphQL POST handler
|
|
328
|
-
const graphqlHandler = Effect.gen(function* () {
|
|
329
|
-
const extensionsService = yield* makeExtensionsService()
|
|
330
|
-
const runtime = yield* Effect.runtime<R>()
|
|
331
|
-
|
|
332
|
-
// Parse request body
|
|
333
|
-
const request = yield* HttpServerRequest.HttpServerRequest
|
|
334
|
-
const body = yield* request.json as Effect.Effect<GraphQLRequestBody>
|
|
335
|
-
|
|
336
|
-
// Phase 1: Parse
|
|
337
|
-
const parseResult = yield* parseGraphQLQuery(body.query, extensionsService)
|
|
338
|
-
if (!parseResult.ok) {
|
|
339
|
-
return parseResult.response
|
|
340
|
-
}
|
|
341
|
-
const document = parseResult.document
|
|
342
|
-
|
|
343
|
-
yield* runParseHooks(extensions, body.query, document).pipe(
|
|
344
|
-
Effect.provideService(ExtensionsService, extensionsService)
|
|
345
|
-
)
|
|
346
|
-
|
|
347
|
-
// Phase 2: Validate
|
|
348
|
-
const validationRules = resolvedConfig.introspection
|
|
349
|
-
? undefined
|
|
350
|
-
: [...specifiedRules, NoSchemaIntrospectionCustomRule]
|
|
351
|
-
const validationErrors = validate(schema, document, validationRules)
|
|
352
|
-
|
|
353
|
-
yield* runValidateHooks(extensions, document, validationErrors).pipe(
|
|
354
|
-
Effect.provideService(ExtensionsService, extensionsService)
|
|
355
|
-
)
|
|
356
|
-
|
|
357
|
-
if (validationErrors.length > 0) {
|
|
358
|
-
const extensionData = yield* extensionsService.get()
|
|
359
|
-
return yield* HttpServerResponse.json(
|
|
360
|
-
{
|
|
361
|
-
errors: validationErrors.map((e) => ({
|
|
362
|
-
message: e.message,
|
|
363
|
-
locations: e.locations,
|
|
364
|
-
path: e.path,
|
|
365
|
-
})),
|
|
366
|
-
extensions: Object.keys(extensionData).length > 0 ? extensionData : undefined,
|
|
367
|
-
},
|
|
368
|
-
{ status: 400 }
|
|
369
|
-
)
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// Complexity validation
|
|
373
|
-
yield* runComplexityValidation(body, schema, fieldComplexities, resolvedConfig.complexity)
|
|
374
|
-
|
|
375
|
-
// Phase 3: Execute
|
|
376
|
-
yield* runExecuteStartHooks(extensions, {
|
|
377
|
-
source: body.query,
|
|
378
|
-
document,
|
|
379
|
-
variableValues: body.variables,
|
|
380
|
-
operationName: body.operationName,
|
|
381
|
-
schema,
|
|
382
|
-
fieldComplexities,
|
|
383
|
-
}).pipe(Effect.provideService(ExtensionsService, extensionsService))
|
|
384
|
-
|
|
385
|
-
const result = yield* executeGraphQLQuery(
|
|
386
|
-
schema,
|
|
387
|
-
document,
|
|
388
|
-
body.variables,
|
|
389
|
-
body.operationName,
|
|
390
|
-
runtime
|
|
391
|
-
)
|
|
392
|
-
|
|
393
|
-
yield* runExecuteEndHooks(extensions, result).pipe(
|
|
394
|
-
Effect.provideService(ExtensionsService, extensionsService)
|
|
395
|
-
)
|
|
396
|
-
|
|
397
|
-
// Build response
|
|
398
|
-
const extensionData = yield* extensionsService.get()
|
|
399
|
-
const cacheControlHeader = yield* computeCacheControlHeader(
|
|
400
|
-
document,
|
|
401
|
-
body.operationName,
|
|
402
|
-
schema,
|
|
403
|
-
cacheHints,
|
|
404
|
-
resolvedConfig.cacheControl
|
|
405
|
-
)
|
|
406
|
-
|
|
407
|
-
return yield* buildGraphQLResponse(result, extensionData, cacheControlHeader)
|
|
408
|
-
}).pipe(
|
|
409
|
-
Effect.provide(layer),
|
|
410
|
-
Effect.catchAll((error) => {
|
|
411
|
-
if (error instanceof ComplexityLimitExceededError) {
|
|
412
|
-
return handleComplexityError(error)
|
|
413
|
-
}
|
|
414
|
-
return Effect.fail(error)
|
|
415
|
-
}),
|
|
416
|
-
Effect.catchAllCause(errorHandler)
|
|
417
|
-
)
|
|
418
|
-
|
|
419
|
-
// Build router
|
|
420
|
-
let router = HttpRouter.empty.pipe(
|
|
421
|
-
HttpRouter.post(resolvedConfig.path as HttpRouter.PathInput, graphqlHandler)
|
|
422
|
-
)
|
|
423
|
-
|
|
424
|
-
if (resolvedConfig.graphiql) {
|
|
425
|
-
const { path, endpoint } = resolvedConfig.graphiql
|
|
426
|
-
router = router.pipe(
|
|
427
|
-
HttpRouter.get(path as HttpRouter.PathInput, HttpServerResponse.html(graphiqlHtml(endpoint)))
|
|
428
|
-
)
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
return router
|
|
432
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { Layer } from "effect"
|
|
2
|
-
import { HttpRouter } from "@effect/platform"
|
|
3
|
-
import { GraphQLSchemaBuilder } from "../builder/schema-builder"
|
|
4
|
-
import { makeGraphQLRouter, type MakeGraphQLRouterOptions } from "./router"
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Convert a GraphQLSchemaBuilder to an HttpRouter.
|
|
8
|
-
*
|
|
9
|
-
* This bridges the GraphQL schema builder with the @effect/platform HTTP server.
|
|
10
|
-
* Field complexities and cache hints are automatically extracted from the builder.
|
|
11
|
-
*
|
|
12
|
-
* @param builder - The GraphQL schema builder
|
|
13
|
-
* @param layer - Effect layer providing services required by resolvers
|
|
14
|
-
* @param options - Optional configuration for paths, GraphiQL, complexity, and caching
|
|
15
|
-
* @returns An HttpRouter that can be composed with other routes
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* ```typescript
|
|
19
|
-
* import { GraphQLSchemaBuilder, query, toRouter } from "@effect-gql/core"
|
|
20
|
-
* import { Layer, Effect } from "effect"
|
|
21
|
-
* import * as S from "effect/Schema"
|
|
22
|
-
*
|
|
23
|
-
* const builder = GraphQLSchemaBuilder.empty.pipe(
|
|
24
|
-
* query("hello", { type: S.String, resolve: () => Effect.succeed("world") })
|
|
25
|
-
* )
|
|
26
|
-
*
|
|
27
|
-
* // Basic usage
|
|
28
|
-
* const router = toRouter(builder, Layer.empty, { graphiql: true })
|
|
29
|
-
*
|
|
30
|
-
* // With complexity limiting
|
|
31
|
-
* const routerWithLimits = toRouter(builder, Layer.empty, {
|
|
32
|
-
* graphiql: true,
|
|
33
|
-
* complexity: { maxDepth: 10, maxComplexity: 1000 }
|
|
34
|
-
* })
|
|
35
|
-
*
|
|
36
|
-
* // With cache control
|
|
37
|
-
* const routerWithCaching = toRouter(builder, Layer.empty, {
|
|
38
|
-
* cacheControl: { enabled: true, defaultMaxAge: 0 }
|
|
39
|
-
* })
|
|
40
|
-
* ```
|
|
41
|
-
*/
|
|
42
|
-
export const toRouter = <R, R2>(
|
|
43
|
-
builder: GraphQLSchemaBuilder<R>,
|
|
44
|
-
layer: Layer.Layer<R2>,
|
|
45
|
-
options?: Omit<MakeGraphQLRouterOptions, "fieldComplexities" | "cacheHints">
|
|
46
|
-
): HttpRouter.HttpRouter<never, never> => {
|
|
47
|
-
const schema = builder.buildSchema()
|
|
48
|
-
const fieldComplexities = builder.getFieldComplexities()
|
|
49
|
-
const cacheHints = builder.getCacheHints()
|
|
50
|
-
return makeGraphQLRouter(schema, layer, { ...options, fieldComplexities, cacheHints })
|
|
51
|
-
}
|