@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,322 @@
1
+ import { Effect, Runtime, Stream, Queue, Fiber, Option } from "effect"
2
+ import { GraphQLFieldConfig, GraphQLResolveInfo } from "graphql"
3
+ import type {
4
+ FieldRegistration,
5
+ SubscriptionFieldRegistration,
6
+ ObjectFieldRegistration,
7
+ DirectiveRegistration,
8
+ MiddlewareRegistration,
9
+ MiddlewareContext,
10
+ GraphQLEffectContext,
11
+ } from "./types"
12
+ import {
13
+ toGraphQLTypeWithRegistry,
14
+ toGraphQLArgsWithRegistry,
15
+ type TypeConversionContext,
16
+ type InputTypeLookupCache,
17
+ } from "./type-registry"
18
+
19
+ /**
20
+ * Context needed for building fields
21
+ */
22
+ export interface FieldBuilderContext extends TypeConversionContext {
23
+ directiveRegistrations: Map<string, DirectiveRegistration>
24
+ middlewares: readonly MiddlewareRegistration[]
25
+ inputTypeLookupCache?: InputTypeLookupCache
26
+ }
27
+
28
+ /**
29
+ * Apply directives to an Effect by wrapping it with directive transformers
30
+ */
31
+ function applyDirectives<A, E, R>(
32
+ effect: Effect.Effect<A, E, R>,
33
+ directives: readonly { name: string; args?: Record<string, unknown> }[] | undefined,
34
+ directiveRegistrations: Map<string, DirectiveRegistration>
35
+ ): Effect.Effect<A, E, any> {
36
+ if (!directives) return effect
37
+
38
+ let wrapped = effect
39
+ for (const directiveApp of directives) {
40
+ const directiveReg = directiveRegistrations.get(directiveApp.name)
41
+ if (directiveReg?.apply) {
42
+ wrapped = directiveReg.apply(directiveApp.args ?? {})(wrapped)
43
+ }
44
+ }
45
+ return wrapped
46
+ }
47
+
48
+ /**
49
+ * Apply middleware to an Effect by wrapping it with middleware transformers.
50
+ *
51
+ * Middleware executes in "onion" order - first registered middleware is the
52
+ * outermost layer, meaning it runs first before and last after the resolver.
53
+ *
54
+ * Each middleware can optionally specify a `match` predicate to filter which
55
+ * fields it applies to.
56
+ */
57
+ function applyMiddleware<A, E, R>(
58
+ effect: Effect.Effect<A, E, R>,
59
+ context: MiddlewareContext,
60
+ middlewares: readonly MiddlewareRegistration[]
61
+ ): Effect.Effect<A, E, any> {
62
+ if (middlewares.length === 0) return effect
63
+
64
+ let wrapped = effect
65
+
66
+ // Apply in reverse order so first registered is outermost
67
+ // (executes first before, last after)
68
+ for (let i = middlewares.length - 1; i >= 0; i--) {
69
+ const middleware = middlewares[i]
70
+
71
+ // Check if middleware should apply to this field
72
+ if (middleware.match && !middleware.match(context.info)) {
73
+ continue
74
+ }
75
+
76
+ wrapped = middleware.apply(wrapped, context)
77
+ }
78
+
79
+ return wrapped
80
+ }
81
+
82
+ /**
83
+ * Build a GraphQL field config from a field registration (for queries/mutations)
84
+ */
85
+ export function buildField(
86
+ config: FieldRegistration,
87
+ ctx: FieldBuilderContext
88
+ ): GraphQLFieldConfig<any, any> {
89
+ const fieldConfig: GraphQLFieldConfig<any, any> = {
90
+ type: toGraphQLTypeWithRegistry(config.type, ctx),
91
+ resolve: async (
92
+ _parent,
93
+ args,
94
+ context: GraphQLEffectContext<any>,
95
+ info: GraphQLResolveInfo
96
+ ) => {
97
+ // Apply directives first (per-field, explicit)
98
+ let effect = applyDirectives(
99
+ config.resolve(args),
100
+ config.directives,
101
+ ctx.directiveRegistrations
102
+ )
103
+
104
+ // Apply middleware (global/pattern-matched)
105
+ const middlewareContext: MiddlewareContext = { parent: _parent, args, info }
106
+ effect = applyMiddleware(effect, middlewareContext, ctx.middlewares)
107
+
108
+ return await Runtime.runPromise(context.runtime)(effect)
109
+ },
110
+ }
111
+
112
+ if (config.args) {
113
+ fieldConfig.args = toGraphQLArgsWithRegistry(
114
+ config.args,
115
+ ctx.enumRegistry,
116
+ ctx.inputRegistry,
117
+ ctx.inputs,
118
+ ctx.enums,
119
+ ctx.inputTypeLookupCache
120
+ )
121
+ }
122
+ if (config.description) {
123
+ fieldConfig.description = config.description
124
+ }
125
+
126
+ return fieldConfig
127
+ }
128
+
129
+ /**
130
+ * Build a GraphQL field config for an object field (has parent param)
131
+ */
132
+ export function buildObjectField(
133
+ config: ObjectFieldRegistration,
134
+ ctx: FieldBuilderContext
135
+ ): GraphQLFieldConfig<any, any> {
136
+ const fieldConfig: GraphQLFieldConfig<any, any> = {
137
+ type: toGraphQLTypeWithRegistry(config.type, ctx),
138
+ resolve: async (parent, args, context: GraphQLEffectContext<any>, info: GraphQLResolveInfo) => {
139
+ // Apply directives first (per-field, explicit)
140
+ let effect = applyDirectives(
141
+ config.resolve(parent, args),
142
+ config.directives,
143
+ ctx.directiveRegistrations
144
+ )
145
+
146
+ // Apply middleware (global/pattern-matched)
147
+ const middlewareContext: MiddlewareContext = { parent, args, info }
148
+ effect = applyMiddleware(effect, middlewareContext, ctx.middlewares)
149
+
150
+ return await Runtime.runPromise(context.runtime)(effect)
151
+ },
152
+ }
153
+
154
+ if (config.args) {
155
+ fieldConfig.args = toGraphQLArgsWithRegistry(
156
+ config.args,
157
+ ctx.enumRegistry,
158
+ ctx.inputRegistry,
159
+ ctx.inputs,
160
+ ctx.enums,
161
+ ctx.inputTypeLookupCache
162
+ )
163
+ }
164
+ if (config.description) {
165
+ fieldConfig.description = config.description
166
+ }
167
+
168
+ return fieldConfig
169
+ }
170
+
171
+ /**
172
+ * Build a GraphQL subscription field config.
173
+ *
174
+ * Subscriptions in GraphQL have a special structure:
175
+ * - `subscribe` returns an AsyncIterator that yields the "root value" for each event
176
+ * - `resolve` transforms each yielded value into the final result
177
+ *
178
+ * We convert Effect's Stream to an AsyncIterator using a Queue-based approach.
179
+ */
180
+ export function buildSubscriptionField(
181
+ config: SubscriptionFieldRegistration,
182
+ ctx: FieldBuilderContext
183
+ ): GraphQLFieldConfig<any, any> {
184
+ const fieldConfig: GraphQLFieldConfig<any, any> = {
185
+ type: toGraphQLTypeWithRegistry(config.type, ctx),
186
+
187
+ // The subscribe function returns an AsyncIterator
188
+ subscribe: async (
189
+ _parent,
190
+ args,
191
+ context: GraphQLEffectContext<any>,
192
+ info: GraphQLResolveInfo
193
+ ) => {
194
+ // Get the Stream from the subscribe Effect
195
+ let subscribeEffect = config.subscribe(args)
196
+
197
+ // Apply directives to the subscribe effect
198
+ subscribeEffect = applyDirectives(
199
+ subscribeEffect,
200
+ config.directives,
201
+ ctx.directiveRegistrations
202
+ ) as any
203
+
204
+ // Apply middleware to the subscribe effect
205
+ const middlewareContext: MiddlewareContext = { parent: _parent, args, info }
206
+ subscribeEffect = applyMiddleware(subscribeEffect, middlewareContext, ctx.middlewares) as any
207
+
208
+ const stream = await Runtime.runPromise(context.runtime)(subscribeEffect)
209
+
210
+ // Convert Stream to AsyncIterator using queue-based approach
211
+ return streamToAsyncIterator(stream, context.runtime)
212
+ },
213
+
214
+ // The resolve function transforms each yielded value
215
+ // If no custom resolve is provided, return the payload directly
216
+ resolve: config.resolve
217
+ ? async (value, args, context: GraphQLEffectContext<any>, info: GraphQLResolveInfo) => {
218
+ let effect = config.resolve!(value, args)
219
+
220
+ // Apply middleware to the resolve effect
221
+ const middlewareContext: MiddlewareContext = { parent: value, args, info }
222
+ effect = applyMiddleware(effect, middlewareContext, ctx.middlewares)
223
+
224
+ return await Runtime.runPromise(context.runtime)(effect)
225
+ }
226
+ : (value) => value,
227
+ }
228
+
229
+ if (config.args) {
230
+ fieldConfig.args = toGraphQLArgsWithRegistry(
231
+ config.args,
232
+ ctx.enumRegistry,
233
+ ctx.inputRegistry,
234
+ ctx.inputs,
235
+ ctx.enums,
236
+ ctx.inputTypeLookupCache
237
+ )
238
+ }
239
+ if (config.description) {
240
+ fieldConfig.description = config.description
241
+ }
242
+
243
+ return fieldConfig
244
+ }
245
+
246
+ /**
247
+ * Convert an Effect Stream to an AsyncIterator using a Queue-based approach.
248
+ *
249
+ * This is needed because Stream.toAsyncIterable() requires R = never,
250
+ * but our streams may have service requirements that need to be provided
251
+ * by the runtime context.
252
+ */
253
+ function streamToAsyncIterator<A, E, R>(
254
+ stream: Stream.Stream<A, E, R>,
255
+ runtime: Runtime.Runtime<R>
256
+ ): AsyncIterator<A> {
257
+ // Create the queue synchronously via runSync since unbounded queue creation is synchronous
258
+ let queue: Queue.Queue<Option.Option<A>>
259
+ let fiber: Fiber.RuntimeFiber<void, E>
260
+ let initialized = false
261
+ let done = false
262
+
263
+ const initialize = async () => {
264
+ if (initialized) return
265
+ initialized = true
266
+
267
+ queue = await Runtime.runPromise(runtime)(Queue.unbounded<Option.Option<A>>())
268
+
269
+ // Fork a fiber to run the stream and push values to the queue
270
+ fiber = Runtime.runFork(runtime)(
271
+ Effect.ensuring(
272
+ Stream.runForEach(stream, (value) => Queue.offer(queue, Option.some(value))),
273
+ // Signal completion by pushing None
274
+ Queue.offer(queue, Option.none())
275
+ )
276
+ )
277
+ }
278
+
279
+ return {
280
+ [Symbol.asyncIterator]() {
281
+ return this
282
+ },
283
+
284
+ async next(): Promise<IteratorResult<A>> {
285
+ await initialize()
286
+
287
+ if (done) {
288
+ return { done: true, value: undefined }
289
+ }
290
+
291
+ try {
292
+ const optionValue = await Runtime.runPromise(runtime)(Queue.take(queue))
293
+
294
+ if (Option.isNone(optionValue)) {
295
+ done = true
296
+ return { done: true, value: undefined }
297
+ }
298
+
299
+ return { done: false, value: optionValue.value }
300
+ } catch (error) {
301
+ done = true
302
+ throw error
303
+ }
304
+ },
305
+
306
+ async return(): Promise<IteratorResult<A>> {
307
+ // Cleanup - interrupt the fiber and shutdown the queue
308
+ done = true
309
+ if (initialized) {
310
+ try {
311
+ await Runtime.runPromise(runtime)(
312
+ Fiber.interrupt(fiber as unknown as Fiber.Fiber<any, any>)
313
+ )
314
+ await Runtime.runPromise(runtime)(Queue.shutdown(queue))
315
+ } catch {
316
+ // Ignore cleanup errors
317
+ }
318
+ }
319
+ return { done: true, value: undefined }
320
+ },
321
+ } as AsyncIterator<A>
322
+ }
@@ -0,0 +1,48 @@
1
+ // Re-export types
2
+ export type {
3
+ FieldRegistration,
4
+ TypeRegistration,
5
+ InterfaceRegistration,
6
+ EnumRegistration,
7
+ UnionRegistration,
8
+ InputTypeRegistration,
9
+ DirectiveApplication,
10
+ DirectiveRegistration,
11
+ MiddlewareContext,
12
+ MiddlewareRegistration,
13
+ SubscriptionFieldRegistration,
14
+ ObjectFieldRegistration,
15
+ GraphQLEffectContext,
16
+ TypeRegistries,
17
+ CacheHint,
18
+ CacheControlScope,
19
+ } from "./types"
20
+
21
+ // Re-export DirectiveLocation for convenience
22
+ export { DirectiveLocation } from "graphql"
23
+
24
+ // Re-export schema builder
25
+ export { GraphQLSchemaBuilder } from "./schema-builder"
26
+
27
+ // Re-export pipe-able API
28
+ export {
29
+ objectType,
30
+ interfaceType,
31
+ enumType,
32
+ unionType,
33
+ inputType,
34
+ directive,
35
+ middleware,
36
+ extension,
37
+ query,
38
+ mutation,
39
+ subscription,
40
+ field,
41
+ compose,
42
+ } from "./pipe-api"
43
+
44
+ // Re-export execute helper
45
+ export { execute } from "./execute"
46
+
47
+ // Re-export utilities that may be useful externally
48
+ export { getSchemaName } from "./type-registry"
@@ -0,0 +1,312 @@
1
+ import { Effect, Stream } from "effect"
2
+ import * as S from "effect/Schema"
3
+ import {
4
+ DirectiveLocation,
5
+ GraphQLResolveInfo,
6
+ DocumentNode,
7
+ ExecutionResult,
8
+ GraphQLError,
9
+ } from "graphql"
10
+ import type { DirectiveApplication, MiddlewareContext, CacheHint } from "./types"
11
+ import type { ExecutionArgs } from "../extensions"
12
+ import { GraphQLSchemaBuilder } from "./schema-builder"
13
+ import type { FieldComplexity } from "../server/complexity"
14
+
15
+ /**
16
+ * Add an object type to the schema builder (pipe-able)
17
+ * Name is optional if schema is TaggedStruct, TaggedClass, or Schema.Class
18
+ */
19
+ export const objectType =
20
+ <A, R2 = never>(config: {
21
+ name?: string
22
+ schema: S.Schema<A, any, any>
23
+ implements?: readonly string[]
24
+ directives?: readonly DirectiveApplication[]
25
+ /**
26
+ * Default cache control hint for all fields returning this type.
27
+ * Can be overridden by field-level cacheControl.
28
+ */
29
+ cacheControl?: CacheHint
30
+ fields?: Record<
31
+ string,
32
+ {
33
+ type: S.Schema<any, any, any>
34
+ args?: S.Schema<any, any, any>
35
+ description?: string
36
+ directives?: readonly DirectiveApplication[]
37
+ complexity?: FieldComplexity
38
+ cacheControl?: CacheHint
39
+ resolve: (parent: A, args: any) => Effect.Effect<any, any, any>
40
+ }
41
+ >
42
+ }) =>
43
+ <R>(builder: GraphQLSchemaBuilder<R>): GraphQLSchemaBuilder<R | R2> =>
44
+ builder.objectType(config)
45
+
46
+ /**
47
+ * Add an interface type to the schema builder (pipe-able)
48
+ * Name is optional if schema is TaggedStruct, TaggedClass, or Schema.Class
49
+ */
50
+ export const interfaceType =
51
+ (config: {
52
+ name?: string
53
+ schema: S.Schema<any, any, any>
54
+ resolveType?: (value: any) => string
55
+ directives?: readonly DirectiveApplication[]
56
+ }) =>
57
+ <R>(builder: GraphQLSchemaBuilder<R>): GraphQLSchemaBuilder<R> =>
58
+ builder.interfaceType(config)
59
+
60
+ /**
61
+ * Add an enum type to the schema builder (pipe-able)
62
+ */
63
+ export const enumType =
64
+ (config: {
65
+ name: string
66
+ values: readonly string[]
67
+ description?: string
68
+ directives?: readonly DirectiveApplication[]
69
+ }) =>
70
+ <R>(builder: GraphQLSchemaBuilder<R>): GraphQLSchemaBuilder<R> =>
71
+ builder.enumType(config)
72
+
73
+ /**
74
+ * Add a union type to the schema builder (pipe-able)
75
+ */
76
+ export const unionType =
77
+ (config: {
78
+ name: string
79
+ types: readonly string[]
80
+ resolveType?: (value: any) => string
81
+ directives?: readonly DirectiveApplication[]
82
+ }) =>
83
+ <R>(builder: GraphQLSchemaBuilder<R>): GraphQLSchemaBuilder<R> =>
84
+ builder.unionType(config)
85
+
86
+ /**
87
+ * Add an input type to the schema builder (pipe-able)
88
+ * Name is optional if schema is TaggedStruct, TaggedClass, or Schema.Class
89
+ */
90
+ export const inputType =
91
+ (config: {
92
+ name?: string
93
+ schema: S.Schema<any, any, any>
94
+ description?: string
95
+ directives?: readonly DirectiveApplication[]
96
+ }) =>
97
+ <R>(builder: GraphQLSchemaBuilder<R>): GraphQLSchemaBuilder<R> =>
98
+ builder.inputType(config)
99
+
100
+ /**
101
+ * Register a directive (pipe-able)
102
+ *
103
+ * @param config - Directive configuration
104
+ * @param config.name - The directive name (without @)
105
+ * @param config.description - Optional description
106
+ * @param config.locations - Array of DirectiveLocation values where this directive can be applied
107
+ * @param config.args - Optional Effect Schema for directive arguments
108
+ * @param config.apply - Optional function to transform resolver Effects (for executable directives)
109
+ */
110
+ export const directive =
111
+ <Args = void, R2 = never>(config: {
112
+ name: string
113
+ description?: string
114
+ locations: readonly DirectiveLocation[]
115
+ args?: S.Schema<Args, any, any>
116
+ apply?: (
117
+ args: Args
118
+ ) => <A, E, R3>(effect: Effect.Effect<A, E, R3>) => Effect.Effect<A, E, R2 | R3>
119
+ }) =>
120
+ <R>(builder: GraphQLSchemaBuilder<R>): GraphQLSchemaBuilder<R | R2> =>
121
+ builder.directive(config)
122
+
123
+ /**
124
+ * Register a middleware (pipe-able)
125
+ *
126
+ * Middleware wraps all resolvers (or those matching a pattern) and executes
127
+ * in an "onion" model - first registered middleware is the outermost layer.
128
+ *
129
+ * @param config.name - Middleware name (for debugging/logging)
130
+ * @param config.description - Optional description
131
+ * @param config.match - Optional predicate to filter which fields this applies to
132
+ * @param config.apply - Function that transforms the resolver Effect
133
+ *
134
+ * @example
135
+ * ```typescript
136
+ * GraphQLSchemaBuilder.empty.pipe(
137
+ * middleware({
138
+ * name: "logging",
139
+ * apply: (effect, ctx) => Effect.gen(function*() {
140
+ * yield* Effect.logInfo(`Resolving ${ctx.info.fieldName}`)
141
+ * return yield* effect
142
+ * })
143
+ * }),
144
+ * middleware({
145
+ * name: "adminOnly",
146
+ * match: (info) => info.fieldName.startsWith("admin"),
147
+ * apply: (effect) => Effect.gen(function*() {
148
+ * const auth = yield* AuthService
149
+ * yield* auth.requireAdmin()
150
+ * return yield* effect
151
+ * })
152
+ * })
153
+ * )
154
+ * ```
155
+ */
156
+ export const middleware =
157
+ <R2 = never>(config: {
158
+ name: string
159
+ description?: string
160
+ match?: (info: GraphQLResolveInfo) => boolean
161
+ apply: <A, E, R3>(
162
+ effect: Effect.Effect<A, E, R3>,
163
+ context: MiddlewareContext
164
+ ) => Effect.Effect<A, E, R2 | R3>
165
+ }) =>
166
+ <R>(builder: GraphQLSchemaBuilder<R>): GraphQLSchemaBuilder<R | R2> =>
167
+ builder.middleware(config)
168
+
169
+ /**
170
+ * Register an extension (pipe-able)
171
+ *
172
+ * Extensions provide lifecycle hooks that run at each phase of request processing
173
+ * (parse, validate, execute) and can contribute data to the response's extensions field.
174
+ *
175
+ * @param config.name - Extension name (for debugging/logging)
176
+ * @param config.description - Optional description
177
+ * @param config.onParse - Called after query parsing
178
+ * @param config.onValidate - Called after validation
179
+ * @param config.onExecuteStart - Called before execution begins
180
+ * @param config.onExecuteEnd - Called after execution completes
181
+ *
182
+ * @example
183
+ * ```typescript
184
+ * GraphQLSchemaBuilder.empty.pipe(
185
+ * extension({
186
+ * name: "tracing",
187
+ * onExecuteStart: () => Effect.gen(function*() {
188
+ * const ext = yield* ExtensionsService
189
+ * yield* ext.set("tracing", { startTime: Date.now() })
190
+ * }),
191
+ * onExecuteEnd: () => Effect.gen(function*() {
192
+ * const ext = yield* ExtensionsService
193
+ * yield* ext.merge("tracing", { endTime: Date.now() })
194
+ * }),
195
+ * })
196
+ * )
197
+ * ```
198
+ */
199
+ export const extension =
200
+ <R2 = never>(config: {
201
+ name: string
202
+ description?: string
203
+ onParse?: (source: string, document: DocumentNode) => Effect.Effect<void, never, R2>
204
+ onValidate?: (
205
+ document: DocumentNode,
206
+ errors: readonly GraphQLError[]
207
+ ) => Effect.Effect<void, never, R2>
208
+ onExecuteStart?: (args: ExecutionArgs) => Effect.Effect<void, never, R2>
209
+ onExecuteEnd?: (result: ExecutionResult) => Effect.Effect<void, never, R2>
210
+ }) =>
211
+ <R>(builder: GraphQLSchemaBuilder<R>): GraphQLSchemaBuilder<R | R2> =>
212
+ builder.extension(config)
213
+
214
+ /**
215
+ * Add a query field to the schema builder (pipe-able)
216
+ */
217
+ export const query =
218
+ <A, E, R2, Args = void>(
219
+ name: string,
220
+ config: {
221
+ type: S.Schema<A, any, any>
222
+ args?: S.Schema<Args, any, any>
223
+ description?: string
224
+ directives?: readonly DirectiveApplication[]
225
+ complexity?: FieldComplexity
226
+ cacheControl?: CacheHint
227
+ resolve: (args: Args) => Effect.Effect<A, E, R2>
228
+ }
229
+ ) =>
230
+ <R>(builder: GraphQLSchemaBuilder<R>): GraphQLSchemaBuilder<R | R2> =>
231
+ builder.query(name, config)
232
+
233
+ /**
234
+ * Add a mutation field to the schema builder (pipe-able)
235
+ */
236
+ export const mutation =
237
+ <A, E, R2, Args = void>(
238
+ name: string,
239
+ config: {
240
+ type: S.Schema<A, any, any>
241
+ args?: S.Schema<Args, any, any>
242
+ description?: string
243
+ directives?: readonly DirectiveApplication[]
244
+ complexity?: FieldComplexity
245
+ resolve: (args: Args) => Effect.Effect<A, E, R2>
246
+ }
247
+ ) =>
248
+ <R>(builder: GraphQLSchemaBuilder<R>): GraphQLSchemaBuilder<R | R2> =>
249
+ builder.mutation(name, config)
250
+
251
+ /**
252
+ * Add a subscription field to the schema builder (pipe-able)
253
+ *
254
+ * Subscriptions return a Stream that yields values over time.
255
+ *
256
+ * @example
257
+ * ```typescript
258
+ * GraphQLSchemaBuilder.empty.pipe(
259
+ * subscription("userCreated", {
260
+ * type: User,
261
+ * subscribe: Effect.gen(function*() {
262
+ * const pubsub = yield* PubSubService
263
+ * return pubsub.subscribe("USER_CREATED")
264
+ * }),
265
+ * })
266
+ * )
267
+ * ```
268
+ */
269
+ export const subscription =
270
+ <A, E, R2, Args = void>(
271
+ name: string,
272
+ config: {
273
+ type: S.Schema<A, any, any>
274
+ args?: S.Schema<Args, any, any>
275
+ description?: string
276
+ directives?: readonly DirectiveApplication[]
277
+ complexity?: FieldComplexity
278
+ cacheControl?: CacheHint
279
+ subscribe: (args: Args) => Effect.Effect<Stream.Stream<A, E, R2>, E, R2>
280
+ resolve?: (value: A, args: Args) => Effect.Effect<A, E, R2>
281
+ }
282
+ ) =>
283
+ <R>(builder: GraphQLSchemaBuilder<R>): GraphQLSchemaBuilder<R | R2> =>
284
+ builder.subscription(name, config)
285
+
286
+ /**
287
+ * Add a field to an existing object type (pipe-able)
288
+ */
289
+ export const field =
290
+ <Parent, A, E, R2, Args = void>(
291
+ typeName: string,
292
+ fieldName: string,
293
+ config: {
294
+ type: S.Schema<A, any, any>
295
+ args?: S.Schema<Args, any, any>
296
+ description?: string
297
+ directives?: readonly DirectiveApplication[]
298
+ complexity?: FieldComplexity
299
+ cacheControl?: CacheHint
300
+ resolve: (parent: Parent, args: Args) => Effect.Effect<A, E, R2>
301
+ }
302
+ ) =>
303
+ <R>(builder: GraphQLSchemaBuilder<R>): GraphQLSchemaBuilder<R | R2> =>
304
+ builder.field(typeName, fieldName, config)
305
+
306
+ /**
307
+ * Compose multiple schema operations (helper for arrays)
308
+ */
309
+ export const compose =
310
+ <R>(...operations: Array<(builder: GraphQLSchemaBuilder<any>) => GraphQLSchemaBuilder<any>>) =>
311
+ (builder: GraphQLSchemaBuilder<R>): GraphQLSchemaBuilder<any> =>
312
+ operations.reduce((b, op) => op(b), builder)