@effect-gql/core 0.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.
Files changed (145) hide show
  1. package/LICENSE +7 -0
  2. package/dist/analyzer-extension.d.ts +105 -0
  3. package/dist/analyzer-extension.d.ts.map +1 -0
  4. package/dist/analyzer-extension.js +137 -0
  5. package/dist/analyzer-extension.js.map +1 -0
  6. package/dist/builder/execute.d.ts +26 -0
  7. package/dist/builder/execute.d.ts.map +1 -0
  8. package/dist/builder/execute.js +104 -0
  9. package/dist/builder/execute.js.map +1 -0
  10. package/dist/builder/field-builders.d.ts +30 -0
  11. package/dist/builder/field-builders.d.ts.map +1 -0
  12. package/dist/builder/field-builders.js +200 -0
  13. package/dist/builder/field-builders.js.map +1 -0
  14. package/dist/builder/index.d.ts +7 -0
  15. package/dist/builder/index.d.ts.map +1 -0
  16. package/dist/builder/index.js +31 -0
  17. package/dist/builder/index.js.map +1 -0
  18. package/dist/builder/pipe-api.d.ts +231 -0
  19. package/dist/builder/pipe-api.d.ts.map +1 -0
  20. package/dist/builder/pipe-api.js +151 -0
  21. package/dist/builder/pipe-api.js.map +1 -0
  22. package/dist/builder/schema-builder.d.ts +301 -0
  23. package/dist/builder/schema-builder.d.ts.map +1 -0
  24. package/dist/builder/schema-builder.js +566 -0
  25. package/dist/builder/schema-builder.js.map +1 -0
  26. package/dist/builder/type-registry.d.ts +80 -0
  27. package/dist/builder/type-registry.d.ts.map +1 -0
  28. package/dist/builder/type-registry.js +505 -0
  29. package/dist/builder/type-registry.js.map +1 -0
  30. package/dist/builder/types.d.ts +283 -0
  31. package/dist/builder/types.d.ts.map +1 -0
  32. package/dist/builder/types.js +3 -0
  33. package/dist/builder/types.js.map +1 -0
  34. package/dist/cli/generate-schema.d.ts +29 -0
  35. package/dist/cli/generate-schema.d.ts.map +1 -0
  36. package/dist/cli/generate-schema.js +233 -0
  37. package/dist/cli/generate-schema.js.map +1 -0
  38. package/dist/cli/index.d.ts +19 -0
  39. package/dist/cli/index.d.ts.map +1 -0
  40. package/dist/cli/index.js +24 -0
  41. package/dist/cli/index.js.map +1 -0
  42. package/dist/context.d.ts +18 -0
  43. package/dist/context.d.ts.map +1 -0
  44. package/dist/context.js +11 -0
  45. package/dist/context.js.map +1 -0
  46. package/dist/error.d.ts +45 -0
  47. package/dist/error.d.ts.map +1 -0
  48. package/dist/error.js +29 -0
  49. package/dist/error.js.map +1 -0
  50. package/dist/extensions.d.ts +130 -0
  51. package/dist/extensions.d.ts.map +1 -0
  52. package/dist/extensions.js +78 -0
  53. package/dist/extensions.js.map +1 -0
  54. package/dist/index.d.ts +12 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +47 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/loader.d.ts +169 -0
  59. package/dist/loader.d.ts.map +1 -0
  60. package/dist/loader.js +237 -0
  61. package/dist/loader.js.map +1 -0
  62. package/dist/resolver-context.d.ts +154 -0
  63. package/dist/resolver-context.d.ts.map +1 -0
  64. package/dist/resolver-context.js +184 -0
  65. package/dist/resolver-context.js.map +1 -0
  66. package/dist/schema-mapping.d.ts +30 -0
  67. package/dist/schema-mapping.d.ts.map +1 -0
  68. package/dist/schema-mapping.js +280 -0
  69. package/dist/schema-mapping.js.map +1 -0
  70. package/dist/server/cache-control.d.ts +96 -0
  71. package/dist/server/cache-control.d.ts.map +1 -0
  72. package/dist/server/cache-control.js +308 -0
  73. package/dist/server/cache-control.js.map +1 -0
  74. package/dist/server/complexity.d.ts +165 -0
  75. package/dist/server/complexity.d.ts.map +1 -0
  76. package/dist/server/complexity.js +433 -0
  77. package/dist/server/complexity.js.map +1 -0
  78. package/dist/server/config.d.ts +66 -0
  79. package/dist/server/config.d.ts.map +1 -0
  80. package/dist/server/config.js +104 -0
  81. package/dist/server/config.js.map +1 -0
  82. package/dist/server/graphiql.d.ts +5 -0
  83. package/dist/server/graphiql.d.ts.map +1 -0
  84. package/dist/server/graphiql.js +43 -0
  85. package/dist/server/graphiql.js.map +1 -0
  86. package/dist/server/index.d.ts +18 -0
  87. package/dist/server/index.d.ts.map +1 -0
  88. package/dist/server/index.js +48 -0
  89. package/dist/server/index.js.map +1 -0
  90. package/dist/server/router.d.ts +79 -0
  91. package/dist/server/router.d.ts.map +1 -0
  92. package/dist/server/router.js +232 -0
  93. package/dist/server/router.js.map +1 -0
  94. package/dist/server/schema-builder-extensions.d.ts +42 -0
  95. package/dist/server/schema-builder-extensions.d.ts.map +1 -0
  96. package/dist/server/schema-builder-extensions.js +48 -0
  97. package/dist/server/schema-builder-extensions.js.map +1 -0
  98. package/dist/server/sse-adapter.d.ts +64 -0
  99. package/dist/server/sse-adapter.d.ts.map +1 -0
  100. package/dist/server/sse-adapter.js +227 -0
  101. package/dist/server/sse-adapter.js.map +1 -0
  102. package/dist/server/sse-types.d.ts +192 -0
  103. package/dist/server/sse-types.d.ts.map +1 -0
  104. package/dist/server/sse-types.js +63 -0
  105. package/dist/server/sse-types.js.map +1 -0
  106. package/dist/server/ws-adapter.d.ts +39 -0
  107. package/dist/server/ws-adapter.d.ts.map +1 -0
  108. package/dist/server/ws-adapter.js +247 -0
  109. package/dist/server/ws-adapter.js.map +1 -0
  110. package/dist/server/ws-types.d.ts +169 -0
  111. package/dist/server/ws-types.d.ts.map +1 -0
  112. package/dist/server/ws-types.js +11 -0
  113. package/dist/server/ws-types.js.map +1 -0
  114. package/dist/server/ws-utils.d.ts +42 -0
  115. package/dist/server/ws-utils.d.ts.map +1 -0
  116. package/dist/server/ws-utils.js +99 -0
  117. package/dist/server/ws-utils.js.map +1 -0
  118. package/package.json +61 -0
  119. package/src/analyzer-extension.ts +254 -0
  120. package/src/builder/execute.ts +153 -0
  121. package/src/builder/field-builders.ts +322 -0
  122. package/src/builder/index.ts +48 -0
  123. package/src/builder/pipe-api.ts +312 -0
  124. package/src/builder/schema-builder.ts +970 -0
  125. package/src/builder/type-registry.ts +670 -0
  126. package/src/builder/types.ts +305 -0
  127. package/src/context.ts +23 -0
  128. package/src/error.ts +32 -0
  129. package/src/extensions.ts +240 -0
  130. package/src/index.ts +32 -0
  131. package/src/loader.ts +363 -0
  132. package/src/resolver-context.ts +253 -0
  133. package/src/schema-mapping.ts +307 -0
  134. package/src/server/cache-control.ts +590 -0
  135. package/src/server/complexity.ts +774 -0
  136. package/src/server/config.ts +174 -0
  137. package/src/server/graphiql.ts +38 -0
  138. package/src/server/index.ts +96 -0
  139. package/src/server/router.ts +432 -0
  140. package/src/server/schema-builder-extensions.ts +51 -0
  141. package/src/server/sse-adapter.ts +327 -0
  142. package/src/server/sse-types.ts +234 -0
  143. package/src/server/ws-adapter.ts +355 -0
  144. package/src/server/ws-types.ts +192 -0
  145. package/src/server/ws-utils.ts +136 -0
@@ -0,0 +1,432 @@
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
+ }
@@ -0,0 +1,51 @@
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
+ }