@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.
Files changed (164) hide show
  1. package/README.md +100 -0
  2. package/builder/index.cjs +1446 -0
  3. package/builder/index.cjs.map +1 -0
  4. package/builder/index.d.cts +260 -0
  5. package/{dist/builder/pipe-api.d.ts → builder/index.d.ts} +50 -21
  6. package/builder/index.js +1405 -0
  7. package/builder/index.js.map +1 -0
  8. package/index.cjs +3469 -0
  9. package/index.cjs.map +1 -0
  10. package/index.d.cts +529 -0
  11. package/index.d.ts +529 -0
  12. package/index.js +3292 -0
  13. package/index.js.map +1 -0
  14. package/package.json +19 -28
  15. package/schema-builder-DKvkzU_M.d.cts +965 -0
  16. package/schema-builder-DKvkzU_M.d.ts +965 -0
  17. package/server/index.cjs +1579 -0
  18. package/server/index.cjs.map +1 -0
  19. package/server/index.d.cts +682 -0
  20. package/server/index.d.ts +682 -0
  21. package/server/index.js +1548 -0
  22. package/server/index.js.map +1 -0
  23. package/dist/analyzer-extension.d.ts +0 -105
  24. package/dist/analyzer-extension.d.ts.map +0 -1
  25. package/dist/analyzer-extension.js +0 -137
  26. package/dist/analyzer-extension.js.map +0 -1
  27. package/dist/builder/execute.d.ts +0 -26
  28. package/dist/builder/execute.d.ts.map +0 -1
  29. package/dist/builder/execute.js +0 -104
  30. package/dist/builder/execute.js.map +0 -1
  31. package/dist/builder/field-builders.d.ts +0 -30
  32. package/dist/builder/field-builders.d.ts.map +0 -1
  33. package/dist/builder/field-builders.js +0 -200
  34. package/dist/builder/field-builders.js.map +0 -1
  35. package/dist/builder/index.d.ts +0 -7
  36. package/dist/builder/index.d.ts.map +0 -1
  37. package/dist/builder/index.js +0 -31
  38. package/dist/builder/index.js.map +0 -1
  39. package/dist/builder/pipe-api.d.ts.map +0 -1
  40. package/dist/builder/pipe-api.js +0 -151
  41. package/dist/builder/pipe-api.js.map +0 -1
  42. package/dist/builder/schema-builder.d.ts +0 -301
  43. package/dist/builder/schema-builder.d.ts.map +0 -1
  44. package/dist/builder/schema-builder.js +0 -566
  45. package/dist/builder/schema-builder.js.map +0 -1
  46. package/dist/builder/type-registry.d.ts +0 -80
  47. package/dist/builder/type-registry.d.ts.map +0 -1
  48. package/dist/builder/type-registry.js +0 -505
  49. package/dist/builder/type-registry.js.map +0 -1
  50. package/dist/builder/types.d.ts +0 -283
  51. package/dist/builder/types.d.ts.map +0 -1
  52. package/dist/builder/types.js +0 -3
  53. package/dist/builder/types.js.map +0 -1
  54. package/dist/cli/generate-schema.d.ts +0 -29
  55. package/dist/cli/generate-schema.d.ts.map +0 -1
  56. package/dist/cli/generate-schema.js +0 -233
  57. package/dist/cli/generate-schema.js.map +0 -1
  58. package/dist/cli/index.d.ts +0 -19
  59. package/dist/cli/index.d.ts.map +0 -1
  60. package/dist/cli/index.js +0 -24
  61. package/dist/cli/index.js.map +0 -1
  62. package/dist/context.d.ts +0 -18
  63. package/dist/context.d.ts.map +0 -1
  64. package/dist/context.js +0 -11
  65. package/dist/context.js.map +0 -1
  66. package/dist/error.d.ts +0 -45
  67. package/dist/error.d.ts.map +0 -1
  68. package/dist/error.js +0 -29
  69. package/dist/error.js.map +0 -1
  70. package/dist/extensions.d.ts +0 -130
  71. package/dist/extensions.d.ts.map +0 -1
  72. package/dist/extensions.js +0 -78
  73. package/dist/extensions.js.map +0 -1
  74. package/dist/index.d.ts +0 -12
  75. package/dist/index.d.ts.map +0 -1
  76. package/dist/index.js +0 -47
  77. package/dist/index.js.map +0 -1
  78. package/dist/loader.d.ts +0 -169
  79. package/dist/loader.d.ts.map +0 -1
  80. package/dist/loader.js +0 -237
  81. package/dist/loader.js.map +0 -1
  82. package/dist/resolver-context.d.ts +0 -154
  83. package/dist/resolver-context.d.ts.map +0 -1
  84. package/dist/resolver-context.js +0 -184
  85. package/dist/resolver-context.js.map +0 -1
  86. package/dist/schema-mapping.d.ts +0 -30
  87. package/dist/schema-mapping.d.ts.map +0 -1
  88. package/dist/schema-mapping.js +0 -280
  89. package/dist/schema-mapping.js.map +0 -1
  90. package/dist/server/cache-control.d.ts +0 -96
  91. package/dist/server/cache-control.d.ts.map +0 -1
  92. package/dist/server/cache-control.js +0 -308
  93. package/dist/server/cache-control.js.map +0 -1
  94. package/dist/server/complexity.d.ts +0 -165
  95. package/dist/server/complexity.d.ts.map +0 -1
  96. package/dist/server/complexity.js +0 -433
  97. package/dist/server/complexity.js.map +0 -1
  98. package/dist/server/config.d.ts +0 -66
  99. package/dist/server/config.d.ts.map +0 -1
  100. package/dist/server/config.js +0 -104
  101. package/dist/server/config.js.map +0 -1
  102. package/dist/server/graphiql.d.ts +0 -5
  103. package/dist/server/graphiql.d.ts.map +0 -1
  104. package/dist/server/graphiql.js +0 -43
  105. package/dist/server/graphiql.js.map +0 -1
  106. package/dist/server/index.d.ts +0 -18
  107. package/dist/server/index.d.ts.map +0 -1
  108. package/dist/server/index.js +0 -48
  109. package/dist/server/index.js.map +0 -1
  110. package/dist/server/router.d.ts +0 -79
  111. package/dist/server/router.d.ts.map +0 -1
  112. package/dist/server/router.js +0 -232
  113. package/dist/server/router.js.map +0 -1
  114. package/dist/server/schema-builder-extensions.d.ts +0 -42
  115. package/dist/server/schema-builder-extensions.d.ts.map +0 -1
  116. package/dist/server/schema-builder-extensions.js +0 -48
  117. package/dist/server/schema-builder-extensions.js.map +0 -1
  118. package/dist/server/sse-adapter.d.ts +0 -64
  119. package/dist/server/sse-adapter.d.ts.map +0 -1
  120. package/dist/server/sse-adapter.js +0 -227
  121. package/dist/server/sse-adapter.js.map +0 -1
  122. package/dist/server/sse-types.d.ts +0 -192
  123. package/dist/server/sse-types.d.ts.map +0 -1
  124. package/dist/server/sse-types.js +0 -63
  125. package/dist/server/sse-types.js.map +0 -1
  126. package/dist/server/ws-adapter.d.ts +0 -39
  127. package/dist/server/ws-adapter.d.ts.map +0 -1
  128. package/dist/server/ws-adapter.js +0 -247
  129. package/dist/server/ws-adapter.js.map +0 -1
  130. package/dist/server/ws-types.d.ts +0 -169
  131. package/dist/server/ws-types.d.ts.map +0 -1
  132. package/dist/server/ws-types.js +0 -11
  133. package/dist/server/ws-types.js.map +0 -1
  134. package/dist/server/ws-utils.d.ts +0 -42
  135. package/dist/server/ws-utils.d.ts.map +0 -1
  136. package/dist/server/ws-utils.js +0 -99
  137. package/dist/server/ws-utils.js.map +0 -1
  138. package/src/analyzer-extension.ts +0 -254
  139. package/src/builder/execute.ts +0 -153
  140. package/src/builder/field-builders.ts +0 -322
  141. package/src/builder/index.ts +0 -48
  142. package/src/builder/pipe-api.ts +0 -312
  143. package/src/builder/schema-builder.ts +0 -970
  144. package/src/builder/type-registry.ts +0 -670
  145. package/src/builder/types.ts +0 -305
  146. package/src/context.ts +0 -23
  147. package/src/error.ts +0 -32
  148. package/src/extensions.ts +0 -240
  149. package/src/index.ts +0 -32
  150. package/src/loader.ts +0 -363
  151. package/src/resolver-context.ts +0 -253
  152. package/src/schema-mapping.ts +0 -307
  153. package/src/server/cache-control.ts +0 -590
  154. package/src/server/complexity.ts +0 -774
  155. package/src/server/config.ts +0 -174
  156. package/src/server/graphiql.ts +0 -38
  157. package/src/server/index.ts +0 -96
  158. package/src/server/router.ts +0 -432
  159. package/src/server/schema-builder-extensions.ts +0 -51
  160. package/src/server/sse-adapter.ts +0 -327
  161. package/src/server/sse-types.ts +0 -234
  162. package/src/server/ws-adapter.ts +0 -355
  163. package/src/server/ws-types.ts +0 -192
  164. package/src/server/ws-utils.ts +0 -136
@@ -1,970 +0,0 @@
1
- import { Effect, Pipeable } from "effect"
2
- import * as S from "effect/Schema"
3
- import {
4
- GraphQLSchema,
5
- GraphQLObjectType,
6
- GraphQLInterfaceType,
7
- GraphQLEnumType,
8
- GraphQLUnionType,
9
- GraphQLInputObjectType,
10
- GraphQLFieldConfigMap,
11
- GraphQLDirective,
12
- DirectiveLocation,
13
- } from "graphql"
14
- import type {
15
- FieldRegistration,
16
- TypeRegistration,
17
- InterfaceRegistration,
18
- EnumRegistration,
19
- UnionRegistration,
20
- InputTypeRegistration,
21
- DirectiveRegistration,
22
- DirectiveApplication,
23
- SubscriptionFieldRegistration,
24
- ObjectFieldRegistration,
25
- MiddlewareRegistration,
26
- MiddlewareContext,
27
- CacheHint,
28
- } from "./types"
29
- import type { GraphQLExtension, ExecutionArgs } from "../extensions"
30
- import type {
31
- GraphQLResolveInfo,
32
- DocumentNode,
33
- ExecutionResult,
34
- GraphQLError as GQLError,
35
- } from "graphql"
36
- import type { FieldComplexity, FieldComplexityMap } from "../server/complexity"
37
- import type { CacheHintMap } from "../server/cache-control"
38
- import {
39
- getSchemaName,
40
- schemaToFields,
41
- schemaToInputFields,
42
- toGraphQLArgsWithRegistry,
43
- buildReverseLookups,
44
- buildInputTypeLookupCache,
45
- type TypeConversionContext,
46
- } from "./type-registry"
47
- import {
48
- buildField,
49
- buildObjectField,
50
- buildSubscriptionField,
51
- type FieldBuilderContext,
52
- } from "./field-builders"
53
-
54
- /**
55
- * Internal state for the builder
56
- */
57
- interface BuilderState {
58
- types: Map<string, TypeRegistration>
59
- interfaces: Map<string, InterfaceRegistration>
60
- enums: Map<string, EnumRegistration>
61
- unions: Map<string, UnionRegistration>
62
- inputs: Map<string, InputTypeRegistration>
63
- directives: Map<string, DirectiveRegistration>
64
- middlewares: readonly MiddlewareRegistration[] // Array to preserve registration order
65
- extensions: readonly GraphQLExtension<any>[] // Array to preserve registration order
66
- queries: Map<string, FieldRegistration>
67
- mutations: Map<string, FieldRegistration>
68
- subscriptions: Map<string, SubscriptionFieldRegistration>
69
- objectFields: Map<string, Map<string, ObjectFieldRegistration>>
70
- }
71
-
72
- /**
73
- * Create a new state with one map updated
74
- */
75
- function updateState<K extends keyof BuilderState>(
76
- state: BuilderState,
77
- key: K,
78
- value: BuilderState[K]
79
- ): BuilderState {
80
- return { ...state, [key]: value }
81
- }
82
-
83
- /**
84
- * GraphQL Schema Builder with type-safe service requirements (Layer-per-Request Pattern)
85
- *
86
- * The type parameter R accumulates all service requirements from resolvers.
87
- * Unlike the runtime-in-context approach, this pattern builds the schema without
88
- * executing any Effects. At request time, you provide a Layer with all required services.
89
- */
90
- export class GraphQLSchemaBuilder<R = never> implements Pipeable.Pipeable {
91
- private constructor(private readonly state: BuilderState) {}
92
-
93
- /**
94
- * Pipeable interface implementation - enables fluent .pipe() syntax
95
- */
96
- pipe<A>(this: A): A
97
- pipe<A, B>(this: A, ab: (a: A) => B): B
98
- pipe<A, B, C>(this: A, ab: (a: A) => B, bc: (b: B) => C): C
99
- pipe<A, B, C, D>(this: A, ab: (a: A) => B, bc: (b: B) => C, cd: (c: C) => D): D
100
- pipe<A, B, C, D, E>(
101
- this: A,
102
- ab: (a: A) => B,
103
- bc: (b: B) => C,
104
- cd: (c: C) => D,
105
- de: (d: D) => E
106
- ): E
107
- pipe<A, B, C, D, E, F>(
108
- this: A,
109
- ab: (a: A) => B,
110
- bc: (b: B) => C,
111
- cd: (c: C) => D,
112
- de: (d: D) => E,
113
- ef: (e: E) => F
114
- ): F
115
- pipe<A, B, C, D, E, F, G>(
116
- this: A,
117
- ab: (a: A) => B,
118
- bc: (b: B) => C,
119
- cd: (c: C) => D,
120
- de: (d: D) => E,
121
- ef: (e: E) => F,
122
- fg: (f: F) => G
123
- ): G
124
- pipe<A, B, C, D, E, F, G, H>(
125
- this: A,
126
- ab: (a: A) => B,
127
- bc: (b: B) => C,
128
- cd: (c: C) => D,
129
- de: (d: D) => E,
130
- ef: (e: E) => F,
131
- fg: (f: F) => G,
132
- gh: (g: G) => H
133
- ): H
134
- pipe<A, B, C, D, E, F, G, H, I>(
135
- this: A,
136
- ab: (a: A) => B,
137
- bc: (b: B) => C,
138
- cd: (c: C) => D,
139
- de: (d: D) => E,
140
- ef: (e: E) => F,
141
- fg: (f: F) => G,
142
- gh: (g: G) => H,
143
- hi: (h: H) => I
144
- ): I
145
- pipe() {
146
- return Pipeable.pipeArguments(this, arguments)
147
- }
148
-
149
- /**
150
- * Create an empty schema builder
151
- */
152
- static readonly empty = new GraphQLSchemaBuilder<never>({
153
- types: new Map(),
154
- interfaces: new Map(),
155
- enums: new Map(),
156
- unions: new Map(),
157
- inputs: new Map(),
158
- directives: new Map(),
159
- middlewares: [],
160
- extensions: [],
161
- queries: new Map(),
162
- mutations: new Map(),
163
- subscriptions: new Map(),
164
- objectFields: new Map(),
165
- })
166
-
167
- /**
168
- * Create a new builder with updated state
169
- */
170
- private with(newState: BuilderState): GraphQLSchemaBuilder<any> {
171
- return new GraphQLSchemaBuilder(newState)
172
- }
173
-
174
- // ============================================================================
175
- // Registration Methods
176
- // ============================================================================
177
-
178
- /**
179
- * Add a query field
180
- */
181
- query<A, E, R2, Args = void>(
182
- name: string,
183
- config: {
184
- type: S.Schema<A, any, any>
185
- args?: S.Schema<Args, any, any>
186
- description?: string
187
- directives?: readonly DirectiveApplication[]
188
- /**
189
- * Complexity cost of this field for query complexity limiting.
190
- * Can be a static number or a function that receives the resolved arguments.
191
- */
192
- complexity?: FieldComplexity
193
- /**
194
- * Cache control hint for this field.
195
- * Used to compute HTTP Cache-Control headers for the response.
196
- */
197
- cacheControl?: CacheHint
198
- resolve: (args: Args) => Effect.Effect<A, E, R2>
199
- }
200
- ): GraphQLSchemaBuilder<R | R2> {
201
- const newQueries = new Map(this.state.queries)
202
- newQueries.set(name, config)
203
- return this.with(updateState(this.state, "queries", newQueries))
204
- }
205
-
206
- /**
207
- * Add a mutation field
208
- */
209
- mutation<A, E, R2, Args = void>(
210
- name: string,
211
- config: {
212
- type: S.Schema<A, any, any>
213
- args?: S.Schema<Args, any, any>
214
- description?: string
215
- directives?: readonly DirectiveApplication[]
216
- /**
217
- * Complexity cost of this field for query complexity limiting.
218
- * Can be a static number or a function that receives the resolved arguments.
219
- */
220
- complexity?: FieldComplexity
221
- resolve: (args: Args) => Effect.Effect<A, E, R2>
222
- }
223
- ): GraphQLSchemaBuilder<R | R2> {
224
- const newMutations = new Map(this.state.mutations)
225
- newMutations.set(name, config)
226
- return this.with(updateState(this.state, "mutations", newMutations))
227
- }
228
-
229
- /**
230
- * Add a subscription field
231
- *
232
- * Subscriptions return a Stream that yields values over time.
233
- * The subscribe function returns an Effect that produces a Stream.
234
- *
235
- * @example
236
- * ```typescript
237
- * builder.subscription("userCreated", {
238
- * type: User,
239
- * subscribe: Effect.gen(function*() {
240
- * const pubsub = yield* PubSubService
241
- * return pubsub.subscribe("USER_CREATED")
242
- * }),
243
- * })
244
- * ```
245
- */
246
- subscription<A, E, R2, Args = void>(
247
- name: string,
248
- config: {
249
- type: S.Schema<A, any, any>
250
- args?: S.Schema<Args, any, any>
251
- description?: string
252
- directives?: readonly DirectiveApplication[]
253
- /**
254
- * Complexity cost of this subscription for query complexity limiting.
255
- * Can be a static number or a function that receives the resolved arguments.
256
- */
257
- complexity?: FieldComplexity
258
- /**
259
- * Cache control hint for this subscription.
260
- * Note: Subscriptions are typically not cached, but this can be used for initial response hints.
261
- */
262
- cacheControl?: CacheHint
263
- subscribe: (args: Args) => Effect.Effect<import("effect").Stream.Stream<A, E, R2>, E, R2>
264
- resolve?: (value: A, args: Args) => Effect.Effect<A, E, R2>
265
- }
266
- ): GraphQLSchemaBuilder<R | R2> {
267
- const newSubscriptions = new Map(this.state.subscriptions)
268
- newSubscriptions.set(name, config)
269
- return this.with(updateState(this.state, "subscriptions", newSubscriptions))
270
- }
271
-
272
- /**
273
- * Register an object type from a schema
274
- */
275
- objectType<A, R2 = never>(config: {
276
- name?: string
277
- schema: S.Schema<A, any, any>
278
- implements?: readonly string[]
279
- directives?: readonly DirectiveApplication[]
280
- /**
281
- * Default cache control hint for all fields returning this type.
282
- * Can be overridden by field-level cacheControl.
283
- */
284
- cacheControl?: CacheHint
285
- fields?: Record<
286
- string,
287
- {
288
- type: S.Schema<any, any, any>
289
- args?: S.Schema<any, any, any>
290
- description?: string
291
- directives?: readonly DirectiveApplication[]
292
- /**
293
- * Complexity cost of this field for query complexity limiting.
294
- */
295
- complexity?: FieldComplexity
296
- /**
297
- * Cache control hint for this field.
298
- */
299
- cacheControl?: CacheHint
300
- resolve: (parent: A, args: any) => Effect.Effect<any, any, any>
301
- }
302
- >
303
- }): GraphQLSchemaBuilder<R | R2> {
304
- const { schema, implements: implementsInterfaces, directives, cacheControl, fields } = config
305
- const name = config.name ?? getSchemaName(schema)
306
- if (!name) {
307
- throw new Error(
308
- "objectType requires a name. Either provide one explicitly or use a TaggedStruct/TaggedClass/Schema.Class"
309
- )
310
- }
311
-
312
- const newTypes = new Map(this.state.types)
313
- newTypes.set(name, { name, schema, implements: implementsInterfaces, directives, cacheControl })
314
-
315
- let newObjectFields = this.state.objectFields
316
- if (fields) {
317
- newObjectFields = new Map(this.state.objectFields)
318
- const typeFields = new Map<string, ObjectFieldRegistration>()
319
- for (const [fieldName, fieldConfig] of Object.entries(fields)) {
320
- typeFields.set(fieldName, fieldConfig as ObjectFieldRegistration)
321
- }
322
- newObjectFields.set(name, typeFields)
323
- }
324
-
325
- return this.with({
326
- ...this.state,
327
- types: newTypes,
328
- objectFields: newObjectFields,
329
- })
330
- }
331
-
332
- /**
333
- * Register an interface type from a schema
334
- */
335
- interfaceType(config: {
336
- name?: string
337
- schema: S.Schema<any, any, any>
338
- resolveType?: (value: any) => string
339
- directives?: readonly DirectiveApplication[]
340
- }): GraphQLSchemaBuilder<R> {
341
- const { schema, resolveType, directives } = config
342
- const name = config.name ?? getSchemaName(schema)
343
- if (!name) {
344
- throw new Error(
345
- "interfaceType requires a name. Either provide one explicitly or use a TaggedStruct/TaggedClass/Schema.Class"
346
- )
347
- }
348
-
349
- const newInterfaces = new Map(this.state.interfaces)
350
- newInterfaces.set(name, {
351
- name,
352
- schema,
353
- resolveType: resolveType ?? ((value: any) => value._tag),
354
- directives,
355
- })
356
-
357
- return this.with(updateState(this.state, "interfaces", newInterfaces))
358
- }
359
-
360
- /**
361
- * Register an enum type
362
- */
363
- enumType(config: {
364
- name: string
365
- values: readonly string[]
366
- description?: string
367
- directives?: readonly DirectiveApplication[]
368
- }): GraphQLSchemaBuilder<R> {
369
- const { name, values, description, directives } = config
370
- const newEnums = new Map(this.state.enums)
371
- newEnums.set(name, { name, values, description, directives })
372
- return this.with(updateState(this.state, "enums", newEnums))
373
- }
374
-
375
- /**
376
- * Register a union type
377
- */
378
- unionType(config: {
379
- name: string
380
- types: readonly string[]
381
- resolveType?: (value: any) => string
382
- directives?: readonly DirectiveApplication[]
383
- }): GraphQLSchemaBuilder<R> {
384
- const { name, types, resolveType, directives } = config
385
- const newUnions = new Map(this.state.unions)
386
- newUnions.set(name, {
387
- name,
388
- types,
389
- resolveType: resolveType ?? ((value: any) => value._tag),
390
- directives,
391
- })
392
- return this.with(updateState(this.state, "unions", newUnions))
393
- }
394
-
395
- /**
396
- * Register an input type
397
- */
398
- inputType(config: {
399
- name?: string
400
- schema: S.Schema<any, any, any>
401
- description?: string
402
- directives?: readonly DirectiveApplication[]
403
- }): GraphQLSchemaBuilder<R> {
404
- const { schema, description, directives } = config
405
- const name = config.name ?? getSchemaName(schema)
406
- if (!name) {
407
- throw new Error(
408
- "inputType requires a name. Either provide one explicitly or use a TaggedStruct/TaggedClass/Schema.Class"
409
- )
410
- }
411
-
412
- const newInputs = new Map(this.state.inputs)
413
- newInputs.set(name, { name, schema, description, directives })
414
- return this.with(updateState(this.state, "inputs", newInputs))
415
- }
416
-
417
- /**
418
- * Register a directive
419
- */
420
- directive<Args = void, R2 = never>(config: {
421
- name: string
422
- description?: string
423
- locations: readonly DirectiveLocation[]
424
- args?: S.Schema<Args, any, any>
425
- apply?: (
426
- args: Args
427
- ) => <A, E, R3>(effect: Effect.Effect<A, E, R3>) => Effect.Effect<A, E, R2 | R3>
428
- }): GraphQLSchemaBuilder<R | R2> {
429
- const newDirectives = new Map(this.state.directives)
430
- newDirectives.set(config.name, config as DirectiveRegistration)
431
- return this.with(updateState(this.state, "directives", newDirectives))
432
- }
433
-
434
- /**
435
- * Register a middleware
436
- *
437
- * Middleware wraps all resolvers (or those matching a pattern) and executes
438
- * in an "onion" model - first registered middleware is the outermost layer.
439
- *
440
- * @param config.name - Middleware name (for debugging/logging)
441
- * @param config.description - Optional description
442
- * @param config.match - Optional predicate to filter which fields this applies to
443
- * @param config.apply - Function that transforms the resolver Effect
444
- *
445
- * @example
446
- * ```typescript
447
- * builder.middleware({
448
- * name: "logging",
449
- * apply: (effect, ctx) => Effect.gen(function*() {
450
- * yield* Effect.logInfo(`Resolving ${ctx.info.fieldName}`)
451
- * const start = Date.now()
452
- * const result = yield* effect
453
- * yield* Effect.logInfo(`Resolved in ${Date.now() - start}ms`)
454
- * return result
455
- * })
456
- * })
457
- * ```
458
- */
459
- middleware<R2 = never>(config: {
460
- name: string
461
- description?: string
462
- match?: (info: GraphQLResolveInfo) => boolean
463
- apply: <A, E, R3>(
464
- effect: Effect.Effect<A, E, R3>,
465
- context: MiddlewareContext
466
- ) => Effect.Effect<A, E, R2 | R3>
467
- }): GraphQLSchemaBuilder<R | R2> {
468
- const newMiddlewares = [...this.state.middlewares, config as MiddlewareRegistration]
469
- return this.with({ ...this.state, middlewares: newMiddlewares })
470
- }
471
-
472
- /**
473
- * Register an extension
474
- *
475
- * Extensions provide lifecycle hooks that run at each phase of request processing
476
- * (parse, validate, execute) and can contribute data to the response's extensions field.
477
- *
478
- * @param config.name - Extension name (for debugging/logging)
479
- * @param config.description - Optional description
480
- * @param config.onParse - Called after query parsing
481
- * @param config.onValidate - Called after validation
482
- * @param config.onExecuteStart - Called before execution begins
483
- * @param config.onExecuteEnd - Called after execution completes
484
- *
485
- * @example
486
- * ```typescript
487
- * builder.extension({
488
- * name: "tracing",
489
- * onExecuteStart: () => Effect.gen(function*() {
490
- * const ext = yield* ExtensionsService
491
- * yield* ext.set("tracing", { startTime: Date.now() })
492
- * }),
493
- * onExecuteEnd: () => Effect.gen(function*() {
494
- * const ext = yield* ExtensionsService
495
- * yield* ext.merge("tracing", { endTime: Date.now() })
496
- * }),
497
- * })
498
- * ```
499
- */
500
- extension<R2 = never>(config: {
501
- name: string
502
- description?: string
503
- onParse?: (source: string, document: DocumentNode) => Effect.Effect<void, never, R2>
504
- onValidate?: (
505
- document: DocumentNode,
506
- errors: readonly GQLError[]
507
- ) => Effect.Effect<void, never, R2>
508
- onExecuteStart?: (args: ExecutionArgs) => Effect.Effect<void, never, R2>
509
- onExecuteEnd?: (result: ExecutionResult) => Effect.Effect<void, never, R2>
510
- }): GraphQLSchemaBuilder<R | R2> {
511
- const newExtensions = [...this.state.extensions, config as GraphQLExtension<R2>]
512
- return this.with({ ...this.state, extensions: newExtensions })
513
- }
514
-
515
- /**
516
- * Get the registered extensions for use by the execution layer
517
- */
518
- getExtensions(): readonly GraphQLExtension<any>[] {
519
- return this.state.extensions
520
- }
521
-
522
- /**
523
- * Add a computed/relational field to an object type
524
- */
525
- field<Parent, A, E, R2, Args = void>(
526
- typeName: string,
527
- fieldName: string,
528
- config: {
529
- type: S.Schema<A, any, any>
530
- args?: S.Schema<Args, any, any>
531
- description?: string
532
- directives?: readonly DirectiveApplication[]
533
- /**
534
- * Complexity cost of this field for query complexity limiting.
535
- * Can be a static number or a function that receives the resolved arguments.
536
- */
537
- complexity?: FieldComplexity
538
- /**
539
- * Cache control hint for this field.
540
- * Used to compute HTTP Cache-Control headers for the response.
541
- */
542
- cacheControl?: CacheHint
543
- resolve: (parent: Parent, args: Args) => Effect.Effect<A, E, R2>
544
- }
545
- ): GraphQLSchemaBuilder<R | R2> {
546
- const newObjectFields = new Map(this.state.objectFields)
547
- const typeFields = newObjectFields.get(typeName) || new Map()
548
- typeFields.set(fieldName, config)
549
- newObjectFields.set(typeName, typeFields)
550
- return this.with(updateState(this.state, "objectFields", newObjectFields))
551
- }
552
-
553
- // ============================================================================
554
- // Schema Building
555
- // ============================================================================
556
-
557
- /**
558
- * Get the field complexity map for use in complexity validation.
559
- * Maps "TypeName.fieldName" to the complexity value or function.
560
- */
561
- getFieldComplexities(): FieldComplexityMap {
562
- const complexities: FieldComplexityMap = new Map()
563
-
564
- // Query fields
565
- for (const [name, config] of this.state.queries) {
566
- if (config.complexity !== undefined) {
567
- complexities.set(`Query.${name}`, config.complexity)
568
- }
569
- }
570
-
571
- // Mutation fields
572
- for (const [name, config] of this.state.mutations) {
573
- if (config.complexity !== undefined) {
574
- complexities.set(`Mutation.${name}`, config.complexity)
575
- }
576
- }
577
-
578
- // Subscription fields
579
- for (const [name, config] of this.state.subscriptions) {
580
- if (config.complexity !== undefined) {
581
- complexities.set(`Subscription.${name}`, config.complexity)
582
- }
583
- }
584
-
585
- // Object type fields
586
- for (const [typeName, fields] of this.state.objectFields) {
587
- for (const [fieldName, config] of fields) {
588
- if (config.complexity !== undefined) {
589
- complexities.set(`${typeName}.${fieldName}`, config.complexity)
590
- }
591
- }
592
- }
593
-
594
- return complexities
595
- }
596
-
597
- /**
598
- * Get the cache hint map for use in cache control calculation.
599
- * Maps "TypeName.fieldName" to the cache hint for field-level hints,
600
- * or "TypeName" to the cache hint for type-level hints.
601
- */
602
- getCacheHints(): CacheHintMap {
603
- const hints: CacheHintMap = new Map()
604
-
605
- // Type-level hints
606
- for (const [typeName, typeReg] of this.state.types) {
607
- if (typeReg.cacheControl !== undefined) {
608
- hints.set(typeName, typeReg.cacheControl)
609
- }
610
- }
611
-
612
- // Query fields
613
- for (const [name, config] of this.state.queries) {
614
- if (config.cacheControl !== undefined) {
615
- hints.set(`Query.${name}`, config.cacheControl)
616
- }
617
- }
618
-
619
- // Subscription fields
620
- for (const [name, config] of this.state.subscriptions) {
621
- if (config.cacheControl !== undefined) {
622
- hints.set(`Subscription.${name}`, config.cacheControl)
623
- }
624
- }
625
-
626
- // Object type fields
627
- for (const [typeName, fields] of this.state.objectFields) {
628
- for (const [fieldName, config] of fields) {
629
- if (config.cacheControl !== undefined) {
630
- hints.set(`${typeName}.${fieldName}`, config.cacheControl)
631
- }
632
- }
633
- }
634
-
635
- return hints
636
- }
637
-
638
- /**
639
- * Build the GraphQL schema (no services required)
640
- */
641
- buildSchema(): GraphQLSchema {
642
- // Build all registries
643
- const directiveRegistry = this.buildDirectiveRegistry()
644
- const enumRegistry = this.buildEnumRegistry()
645
- const inputRegistry = this.buildInputRegistry(enumRegistry)
646
- const interfaceRegistry = this.buildInterfaceRegistry(enumRegistry)
647
- const { typeRegistry, unionRegistry } = this.buildTypeAndUnionRegistries(
648
- enumRegistry,
649
- interfaceRegistry
650
- )
651
-
652
- // Build field builder context
653
- const fieldCtx = this.createFieldBuilderContext(
654
- typeRegistry,
655
- interfaceRegistry,
656
- enumRegistry,
657
- unionRegistry,
658
- inputRegistry
659
- )
660
-
661
- // Build root type fields
662
- const queryFields = this.buildQueryFields(fieldCtx)
663
- const mutationFields = this.buildMutationFields(fieldCtx)
664
- const subscriptionFields = this.buildSubscriptionFields(fieldCtx)
665
-
666
- // Assemble schema
667
- return this.assembleSchema({
668
- directiveRegistry,
669
- enumRegistry,
670
- inputRegistry,
671
- interfaceRegistry,
672
- typeRegistry,
673
- unionRegistry,
674
- queryFields,
675
- mutationFields,
676
- subscriptionFields,
677
- })
678
- }
679
-
680
- private buildDirectiveRegistry(): Map<string, GraphQLDirective> {
681
- const registry = new Map<string, GraphQLDirective>()
682
-
683
- // Build cache once for O(1) lookups across all directives
684
- const cache = buildInputTypeLookupCache(this.state.inputs, this.state.enums)
685
-
686
- for (const [name, reg] of this.state.directives) {
687
- const graphqlDirective = new GraphQLDirective({
688
- name,
689
- description: reg.description,
690
- locations: [...reg.locations],
691
- args: reg.args
692
- ? toGraphQLArgsWithRegistry(
693
- reg.args,
694
- new Map(),
695
- new Map(),
696
- this.state.inputs,
697
- this.state.enums,
698
- cache
699
- )
700
- : undefined,
701
- })
702
- registry.set(name, graphqlDirective)
703
- }
704
-
705
- return registry
706
- }
707
-
708
- private buildEnumRegistry(): Map<string, GraphQLEnumType> {
709
- const registry = new Map<string, GraphQLEnumType>()
710
-
711
- for (const [name, reg] of this.state.enums) {
712
- const enumValues: Record<string, { value: string }> = {}
713
- for (const value of reg.values) {
714
- enumValues[value] = { value }
715
- }
716
- registry.set(
717
- name,
718
- new GraphQLEnumType({
719
- name,
720
- values: enumValues,
721
- description: reg.description,
722
- extensions: reg.directives ? { directives: reg.directives } : undefined,
723
- })
724
- )
725
- }
726
-
727
- return registry
728
- }
729
-
730
- private buildInputRegistry(
731
- enumRegistry: Map<string, GraphQLEnumType>
732
- ): Map<string, GraphQLInputObjectType> {
733
- const registry = new Map<string, GraphQLInputObjectType>()
734
-
735
- // Build cache once for O(1) lookups across all input types
736
- const cache = buildInputTypeLookupCache(this.state.inputs, this.state.enums)
737
-
738
- for (const [name, reg] of this.state.inputs) {
739
- const inputType = new GraphQLInputObjectType({
740
- name,
741
- description: reg.description,
742
- fields: () =>
743
- schemaToInputFields(
744
- reg.schema,
745
- enumRegistry,
746
- registry,
747
- this.state.inputs,
748
- this.state.enums,
749
- cache
750
- ),
751
- extensions: reg.directives ? { directives: reg.directives } : undefined,
752
- })
753
- registry.set(name, inputType)
754
- }
755
-
756
- return registry
757
- }
758
-
759
- private buildInterfaceRegistry(
760
- enumRegistry: Map<string, GraphQLEnumType>
761
- ): Map<string, GraphQLInterfaceType> {
762
- const registry = new Map<string, GraphQLInterfaceType>()
763
- // We need type and union registries for interface fields, but they're built later
764
- // Use empty maps for now - interfaces shouldn't reference object types directly
765
- const typeRegistry = new Map<string, GraphQLObjectType>()
766
- const unionRegistry = new Map<string, GraphQLUnionType>()
767
-
768
- // Create shared TypeConversionContext once for all interface field builders
769
- const sharedCtx: TypeConversionContext = {
770
- types: this.state.types,
771
- interfaces: this.state.interfaces,
772
- enums: this.state.enums,
773
- unions: this.state.unions,
774
- inputs: this.state.inputs,
775
- typeRegistry,
776
- interfaceRegistry: registry,
777
- enumRegistry,
778
- unionRegistry,
779
- inputRegistry: new Map(),
780
- }
781
- // Pre-build reverse lookup maps once
782
- buildReverseLookups(sharedCtx)
783
-
784
- for (const [name, reg] of this.state.interfaces) {
785
- const interfaceType = new GraphQLInterfaceType({
786
- name,
787
- fields: () => schemaToFields(reg.schema, sharedCtx),
788
- resolveType: reg.resolveType,
789
- extensions: reg.directives ? { directives: reg.directives } : undefined,
790
- })
791
- registry.set(name, interfaceType)
792
- }
793
-
794
- return registry
795
- }
796
-
797
- private buildTypeAndUnionRegistries(
798
- enumRegistry: Map<string, GraphQLEnumType>,
799
- interfaceRegistry: Map<string, GraphQLInterfaceType>
800
- ): {
801
- typeRegistry: Map<string, GraphQLObjectType>
802
- unionRegistry: Map<string, GraphQLUnionType>
803
- } {
804
- const typeRegistry = new Map<string, GraphQLObjectType>()
805
- const unionRegistry = new Map<string, GraphQLUnionType>()
806
-
807
- // Create shared TypeConversionContext once and reuse for all lazy field builders
808
- const sharedCtx: TypeConversionContext = {
809
- types: this.state.types,
810
- interfaces: this.state.interfaces,
811
- enums: this.state.enums,
812
- unions: this.state.unions,
813
- inputs: this.state.inputs,
814
- typeRegistry,
815
- interfaceRegistry,
816
- enumRegistry,
817
- unionRegistry,
818
- inputRegistry: new Map(),
819
- }
820
- // Pre-build reverse lookup maps once
821
- buildReverseLookups(sharedCtx)
822
-
823
- // Create shared FieldBuilderContext for additional fields
824
- const sharedFieldCtx = this.createFieldBuilderContext(
825
- typeRegistry,
826
- interfaceRegistry,
827
- enumRegistry,
828
- unionRegistry,
829
- new Map()
830
- )
831
-
832
- // Build object types with lazy field builders (allows circular references)
833
- for (const [typeName, typeReg] of this.state.types) {
834
- const implementedInterfaces =
835
- typeReg.implements?.map((name) => interfaceRegistry.get(name)!).filter(Boolean) ?? []
836
-
837
- const graphqlType = new GraphQLObjectType({
838
- name: typeName,
839
- fields: () => {
840
- const baseFields = schemaToFields(typeReg.schema, sharedCtx)
841
- const additionalFields = this.state.objectFields.get(typeName)
842
-
843
- if (additionalFields) {
844
- for (const [fieldName, fieldConfig] of additionalFields) {
845
- baseFields[fieldName] = buildObjectField(fieldConfig, sharedFieldCtx)
846
- }
847
- }
848
-
849
- return baseFields
850
- },
851
- interfaces: implementedInterfaces.length > 0 ? implementedInterfaces : undefined,
852
- extensions: typeReg.directives ? { directives: typeReg.directives } : undefined,
853
- })
854
- typeRegistry.set(typeName, graphqlType)
855
- }
856
-
857
- // Build union types (reference object types)
858
- for (const [name, reg] of this.state.unions) {
859
- const unionType = new GraphQLUnionType({
860
- name,
861
- types: () => reg.types.map((typeName) => typeRegistry.get(typeName)!).filter(Boolean),
862
- resolveType: reg.resolveType,
863
- extensions: reg.directives ? { directives: reg.directives } : undefined,
864
- })
865
- unionRegistry.set(name, unionType)
866
- }
867
-
868
- return { typeRegistry, unionRegistry }
869
- }
870
-
871
- private createFieldBuilderContext(
872
- typeRegistry: Map<string, GraphQLObjectType>,
873
- interfaceRegistry: Map<string, GraphQLInterfaceType>,
874
- enumRegistry: Map<string, GraphQLEnumType>,
875
- unionRegistry: Map<string, GraphQLUnionType>,
876
- inputRegistry: Map<string, GraphQLInputObjectType>
877
- ): FieldBuilderContext {
878
- // Build cache once for O(1) input type lookups across all fields
879
- const inputTypeLookupCache = buildInputTypeLookupCache(this.state.inputs, this.state.enums)
880
-
881
- return {
882
- types: this.state.types,
883
- interfaces: this.state.interfaces,
884
- enums: this.state.enums,
885
- unions: this.state.unions,
886
- inputs: this.state.inputs,
887
- typeRegistry,
888
- interfaceRegistry,
889
- enumRegistry,
890
- unionRegistry,
891
- inputRegistry,
892
- directiveRegistrations: this.state.directives,
893
- middlewares: this.state.middlewares,
894
- inputTypeLookupCache,
895
- }
896
- }
897
-
898
- private buildQueryFields(ctx: FieldBuilderContext): GraphQLFieldConfigMap<any, any> {
899
- const fields: GraphQLFieldConfigMap<any, any> = {}
900
- for (const [name, config] of this.state.queries) {
901
- fields[name] = buildField(config, ctx)
902
- }
903
- return fields
904
- }
905
-
906
- private buildMutationFields(ctx: FieldBuilderContext): GraphQLFieldConfigMap<any, any> {
907
- const fields: GraphQLFieldConfigMap<any, any> = {}
908
- for (const [name, config] of this.state.mutations) {
909
- fields[name] = buildField(config, ctx)
910
- }
911
- return fields
912
- }
913
-
914
- private buildSubscriptionFields(ctx: FieldBuilderContext): GraphQLFieldConfigMap<any, any> {
915
- const fields: GraphQLFieldConfigMap<any, any> = {}
916
- for (const [name, config] of this.state.subscriptions) {
917
- fields[name] = buildSubscriptionField(config, ctx)
918
- }
919
- return fields
920
- }
921
-
922
- private assembleSchema(registries: {
923
- directiveRegistry: Map<string, GraphQLDirective>
924
- enumRegistry: Map<string, GraphQLEnumType>
925
- inputRegistry: Map<string, GraphQLInputObjectType>
926
- interfaceRegistry: Map<string, GraphQLInterfaceType>
927
- typeRegistry: Map<string, GraphQLObjectType>
928
- unionRegistry: Map<string, GraphQLUnionType>
929
- queryFields: GraphQLFieldConfigMap<any, any>
930
- mutationFields: GraphQLFieldConfigMap<any, any>
931
- subscriptionFields: GraphQLFieldConfigMap<any, any>
932
- }): GraphQLSchema {
933
- const schemaConfig: any = {
934
- types: [
935
- ...Array.from(registries.enumRegistry.values()),
936
- ...Array.from(registries.inputRegistry.values()),
937
- ...Array.from(registries.interfaceRegistry.values()),
938
- ...Array.from(registries.typeRegistry.values()),
939
- ...Array.from(registries.unionRegistry.values()),
940
- ],
941
- directives:
942
- registries.directiveRegistry.size > 0
943
- ? [...Array.from(registries.directiveRegistry.values())]
944
- : undefined,
945
- }
946
-
947
- if (Object.keys(registries.queryFields).length > 0) {
948
- schemaConfig.query = new GraphQLObjectType({
949
- name: "Query",
950
- fields: registries.queryFields,
951
- })
952
- }
953
-
954
- if (Object.keys(registries.mutationFields).length > 0) {
955
- schemaConfig.mutation = new GraphQLObjectType({
956
- name: "Mutation",
957
- fields: registries.mutationFields,
958
- })
959
- }
960
-
961
- if (Object.keys(registries.subscriptionFields).length > 0) {
962
- schemaConfig.subscription = new GraphQLObjectType({
963
- name: "Subscription",
964
- fields: registries.subscriptionFields,
965
- })
966
- }
967
-
968
- return new GraphQLSchema(schemaConfig)
969
- }
970
- }