@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.
- package/LICENSE +7 -0
- package/dist/analyzer-extension.d.ts +105 -0
- package/dist/analyzer-extension.d.ts.map +1 -0
- package/dist/analyzer-extension.js +137 -0
- package/dist/analyzer-extension.js.map +1 -0
- package/dist/builder/execute.d.ts +26 -0
- package/dist/builder/execute.d.ts.map +1 -0
- package/dist/builder/execute.js +104 -0
- package/dist/builder/execute.js.map +1 -0
- package/dist/builder/field-builders.d.ts +30 -0
- package/dist/builder/field-builders.d.ts.map +1 -0
- package/dist/builder/field-builders.js +200 -0
- package/dist/builder/field-builders.js.map +1 -0
- package/dist/builder/index.d.ts +7 -0
- package/dist/builder/index.d.ts.map +1 -0
- package/dist/builder/index.js +31 -0
- package/dist/builder/index.js.map +1 -0
- package/dist/builder/pipe-api.d.ts +231 -0
- package/dist/builder/pipe-api.d.ts.map +1 -0
- package/dist/builder/pipe-api.js +151 -0
- package/dist/builder/pipe-api.js.map +1 -0
- package/dist/builder/schema-builder.d.ts +301 -0
- package/dist/builder/schema-builder.d.ts.map +1 -0
- package/dist/builder/schema-builder.js +566 -0
- package/dist/builder/schema-builder.js.map +1 -0
- package/dist/builder/type-registry.d.ts +80 -0
- package/dist/builder/type-registry.d.ts.map +1 -0
- package/dist/builder/type-registry.js +505 -0
- package/dist/builder/type-registry.js.map +1 -0
- package/dist/builder/types.d.ts +283 -0
- package/dist/builder/types.d.ts.map +1 -0
- package/dist/builder/types.js +3 -0
- package/dist/builder/types.js.map +1 -0
- package/dist/cli/generate-schema.d.ts +29 -0
- package/dist/cli/generate-schema.d.ts.map +1 -0
- package/dist/cli/generate-schema.js +233 -0
- package/dist/cli/generate-schema.js.map +1 -0
- package/dist/cli/index.d.ts +19 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +24 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/context.d.ts +18 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +11 -0
- package/dist/context.js.map +1 -0
- package/dist/error.d.ts +45 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +29 -0
- package/dist/error.js.map +1 -0
- package/dist/extensions.d.ts +130 -0
- package/dist/extensions.d.ts.map +1 -0
- package/dist/extensions.js +78 -0
- package/dist/extensions.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +47 -0
- package/dist/index.js.map +1 -0
- package/dist/loader.d.ts +169 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +237 -0
- package/dist/loader.js.map +1 -0
- package/dist/resolver-context.d.ts +154 -0
- package/dist/resolver-context.d.ts.map +1 -0
- package/dist/resolver-context.js +184 -0
- package/dist/resolver-context.js.map +1 -0
- package/dist/schema-mapping.d.ts +30 -0
- package/dist/schema-mapping.d.ts.map +1 -0
- package/dist/schema-mapping.js +280 -0
- package/dist/schema-mapping.js.map +1 -0
- package/dist/server/cache-control.d.ts +96 -0
- package/dist/server/cache-control.d.ts.map +1 -0
- package/dist/server/cache-control.js +308 -0
- package/dist/server/cache-control.js.map +1 -0
- package/dist/server/complexity.d.ts +165 -0
- package/dist/server/complexity.d.ts.map +1 -0
- package/dist/server/complexity.js +433 -0
- package/dist/server/complexity.js.map +1 -0
- package/dist/server/config.d.ts +66 -0
- package/dist/server/config.d.ts.map +1 -0
- package/dist/server/config.js +104 -0
- package/dist/server/config.js.map +1 -0
- package/dist/server/graphiql.d.ts +5 -0
- package/dist/server/graphiql.d.ts.map +1 -0
- package/dist/server/graphiql.js +43 -0
- package/dist/server/graphiql.js.map +1 -0
- package/dist/server/index.d.ts +18 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +48 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/router.d.ts +79 -0
- package/dist/server/router.d.ts.map +1 -0
- package/dist/server/router.js +232 -0
- package/dist/server/router.js.map +1 -0
- package/dist/server/schema-builder-extensions.d.ts +42 -0
- package/dist/server/schema-builder-extensions.d.ts.map +1 -0
- package/dist/server/schema-builder-extensions.js +48 -0
- package/dist/server/schema-builder-extensions.js.map +1 -0
- package/dist/server/sse-adapter.d.ts +64 -0
- package/dist/server/sse-adapter.d.ts.map +1 -0
- package/dist/server/sse-adapter.js +227 -0
- package/dist/server/sse-adapter.js.map +1 -0
- package/dist/server/sse-types.d.ts +192 -0
- package/dist/server/sse-types.d.ts.map +1 -0
- package/dist/server/sse-types.js +63 -0
- package/dist/server/sse-types.js.map +1 -0
- package/dist/server/ws-adapter.d.ts +39 -0
- package/dist/server/ws-adapter.d.ts.map +1 -0
- package/dist/server/ws-adapter.js +247 -0
- package/dist/server/ws-adapter.js.map +1 -0
- package/dist/server/ws-types.d.ts +169 -0
- package/dist/server/ws-types.d.ts.map +1 -0
- package/dist/server/ws-types.js +11 -0
- package/dist/server/ws-types.js.map +1 -0
- package/dist/server/ws-utils.d.ts +42 -0
- package/dist/server/ws-utils.d.ts.map +1 -0
- package/dist/server/ws-utils.js +99 -0
- package/dist/server/ws-utils.js.map +1 -0
- package/package.json +61 -0
- package/src/analyzer-extension.ts +254 -0
- package/src/builder/execute.ts +153 -0
- package/src/builder/field-builders.ts +322 -0
- package/src/builder/index.ts +48 -0
- package/src/builder/pipe-api.ts +312 -0
- package/src/builder/schema-builder.ts +970 -0
- package/src/builder/type-registry.ts +670 -0
- package/src/builder/types.ts +305 -0
- package/src/context.ts +23 -0
- package/src/error.ts +32 -0
- package/src/extensions.ts +240 -0
- package/src/index.ts +32 -0
- package/src/loader.ts +363 -0
- package/src/resolver-context.ts +253 -0
- package/src/schema-mapping.ts +307 -0
- package/src/server/cache-control.ts +590 -0
- package/src/server/complexity.ts +774 -0
- package/src/server/config.ts +174 -0
- package/src/server/graphiql.ts +38 -0
- package/src/server/index.ts +96 -0
- package/src/server/router.ts +432 -0
- package/src/server/schema-builder-extensions.ts +51 -0
- package/src/server/sse-adapter.ts +327 -0
- package/src/server/sse-types.ts +234 -0
- package/src/server/ws-adapter.ts +355 -0
- package/src/server/ws-types.ts +192 -0
- package/src/server/ws-utils.ts +136 -0
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
import * as S from "effect/Schema"
|
|
2
|
+
import * as AST from "effect/SchemaAST"
|
|
3
|
+
import {
|
|
4
|
+
GraphQLObjectType,
|
|
5
|
+
GraphQLInterfaceType,
|
|
6
|
+
GraphQLEnumType,
|
|
7
|
+
GraphQLUnionType,
|
|
8
|
+
GraphQLInputObjectType,
|
|
9
|
+
GraphQLList,
|
|
10
|
+
GraphQLNonNull,
|
|
11
|
+
GraphQLFieldConfigMap,
|
|
12
|
+
GraphQLInputFieldConfigMap,
|
|
13
|
+
} from "graphql"
|
|
14
|
+
import { toGraphQLType, toGraphQLArgs, toGraphQLInputType } from "../schema-mapping"
|
|
15
|
+
import type {
|
|
16
|
+
TypeRegistration,
|
|
17
|
+
InterfaceRegistration,
|
|
18
|
+
EnumRegistration,
|
|
19
|
+
UnionRegistration,
|
|
20
|
+
InputTypeRegistration,
|
|
21
|
+
} from "./types"
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Extract type name from a schema if it has one.
|
|
25
|
+
* Supports:
|
|
26
|
+
* - S.TaggedStruct("Name", {...}) - extracts from _tag literal
|
|
27
|
+
* - S.TaggedClass()("Name", {...}) - extracts from identifier annotation
|
|
28
|
+
* - S.Class<T>("Name")({...}) - extracts from identifier annotation
|
|
29
|
+
*/
|
|
30
|
+
export function getSchemaName(schema: S.Schema<any, any, any>): string | undefined {
|
|
31
|
+
const ast = schema.ast
|
|
32
|
+
|
|
33
|
+
// Handle Transformation (Schema.Class, TaggedClass)
|
|
34
|
+
if (ast._tag === "Transformation") {
|
|
35
|
+
const identifier = AST.getIdentifierAnnotation((ast as any).to)
|
|
36
|
+
if (identifier._tag === "Some") {
|
|
37
|
+
return identifier.value
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Handle TypeLiteral (TaggedStruct)
|
|
42
|
+
if (ast._tag === "TypeLiteral") {
|
|
43
|
+
const tagProp = (ast as any).propertySignatures.find((p: any) => String(p.name) === "_tag")
|
|
44
|
+
if (tagProp && tagProp.type._tag === "Literal" && typeof tagProp.type.literal === "string") {
|
|
45
|
+
return tagProp.type.literal
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return undefined
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Context needed for type conversion operations
|
|
54
|
+
*/
|
|
55
|
+
export interface TypeConversionContext {
|
|
56
|
+
types: Map<string, TypeRegistration>
|
|
57
|
+
interfaces: Map<string, InterfaceRegistration>
|
|
58
|
+
enums: Map<string, EnumRegistration>
|
|
59
|
+
unions: Map<string, UnionRegistration>
|
|
60
|
+
inputs: Map<string, InputTypeRegistration>
|
|
61
|
+
typeRegistry: Map<string, GraphQLObjectType>
|
|
62
|
+
interfaceRegistry: Map<string, GraphQLInterfaceType>
|
|
63
|
+
enumRegistry: Map<string, GraphQLEnumType>
|
|
64
|
+
unionRegistry: Map<string, GraphQLUnionType>
|
|
65
|
+
inputRegistry: Map<string, GraphQLInputObjectType>
|
|
66
|
+
// Reverse lookup caches for O(1) type resolution
|
|
67
|
+
schemaToTypeName?: Map<S.Schema<any, any, any>, string>
|
|
68
|
+
astToTypeName?: Map<AST.AST, string>
|
|
69
|
+
schemaToInterfaceName?: Map<S.Schema<any, any, any>, string>
|
|
70
|
+
astToInterfaceName?: Map<AST.AST, string>
|
|
71
|
+
schemaToInputName?: Map<S.Schema<any, any, any>, string>
|
|
72
|
+
astToInputName?: Map<AST.AST, string>
|
|
73
|
+
// Cached sorted values for enum/union matching
|
|
74
|
+
enumSortedValues?: Map<string, readonly string[]>
|
|
75
|
+
unionSortedTypes?: Map<string, readonly string[]>
|
|
76
|
+
// Reverse lookup: literal value -> enum name (for single literal O(1) lookup)
|
|
77
|
+
literalToEnumName?: Map<string, string>
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Build reverse lookup maps from registration maps for O(1) type resolution
|
|
82
|
+
*/
|
|
83
|
+
export function buildReverseLookups(ctx: TypeConversionContext): void {
|
|
84
|
+
// Build schema/AST -> type name lookups
|
|
85
|
+
if (!ctx.schemaToTypeName) {
|
|
86
|
+
ctx.schemaToTypeName = new Map()
|
|
87
|
+
ctx.astToTypeName = new Map()
|
|
88
|
+
for (const [typeName, typeReg] of ctx.types) {
|
|
89
|
+
ctx.schemaToTypeName.set(typeReg.schema, typeName)
|
|
90
|
+
ctx.astToTypeName.set(typeReg.schema.ast, typeName)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Build schema/AST -> interface name lookups
|
|
95
|
+
if (!ctx.schemaToInterfaceName) {
|
|
96
|
+
ctx.schemaToInterfaceName = new Map()
|
|
97
|
+
ctx.astToInterfaceName = new Map()
|
|
98
|
+
for (const [interfaceName, interfaceReg] of ctx.interfaces) {
|
|
99
|
+
ctx.schemaToInterfaceName.set(interfaceReg.schema, interfaceName)
|
|
100
|
+
ctx.astToInterfaceName.set(interfaceReg.schema.ast, interfaceName)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Build schema/AST -> input name lookups
|
|
105
|
+
if (!ctx.schemaToInputName) {
|
|
106
|
+
ctx.schemaToInputName = new Map()
|
|
107
|
+
ctx.astToInputName = new Map()
|
|
108
|
+
for (const [inputName, inputReg] of ctx.inputs) {
|
|
109
|
+
ctx.schemaToInputName.set(inputReg.schema, inputName)
|
|
110
|
+
ctx.astToInputName.set(inputReg.schema.ast, inputName)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Build cached sorted enum values and literal -> enum lookup
|
|
115
|
+
if (!ctx.enumSortedValues) {
|
|
116
|
+
ctx.enumSortedValues = new Map()
|
|
117
|
+
ctx.literalToEnumName = new Map()
|
|
118
|
+
for (const [enumName, enumReg] of ctx.enums) {
|
|
119
|
+
ctx.enumSortedValues.set(enumName, [...enumReg.values].sort())
|
|
120
|
+
// Build literal -> enum reverse lookup for O(1) single literal lookups
|
|
121
|
+
for (const value of enumReg.values) {
|
|
122
|
+
ctx.literalToEnumName.set(value, enumName)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Build cached sorted union types
|
|
128
|
+
if (!ctx.unionSortedTypes) {
|
|
129
|
+
ctx.unionSortedTypes = new Map()
|
|
130
|
+
for (const [unionName, unionReg] of ctx.unions) {
|
|
131
|
+
ctx.unionSortedTypes.set(unionName, [...unionReg.types].sort())
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// GraphQLNonNull wrapper cache for memoization
|
|
137
|
+
const nonNullCache = new WeakMap<any, GraphQLNonNull<any>>()
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get or create a GraphQLNonNull wrapper (memoized)
|
|
141
|
+
*/
|
|
142
|
+
export function getNonNull<T extends import("graphql").GraphQLNullableType>(
|
|
143
|
+
type: T
|
|
144
|
+
): GraphQLNonNull<T> {
|
|
145
|
+
let cached = nonNullCache.get(type)
|
|
146
|
+
if (!cached) {
|
|
147
|
+
cached = new GraphQLNonNull(type)
|
|
148
|
+
nonNullCache.set(type, cached)
|
|
149
|
+
}
|
|
150
|
+
return cached
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Convert schema to GraphQL type, checking registry first for registered types
|
|
155
|
+
*/
|
|
156
|
+
export function toGraphQLTypeWithRegistry(
|
|
157
|
+
schema: S.Schema<any, any, any>,
|
|
158
|
+
ctx: TypeConversionContext
|
|
159
|
+
): any {
|
|
160
|
+
// Ensure reverse lookup maps are built
|
|
161
|
+
buildReverseLookups(ctx)
|
|
162
|
+
|
|
163
|
+
const ast = schema.ast
|
|
164
|
+
|
|
165
|
+
// Check registered object types first
|
|
166
|
+
const registeredType = findRegisteredType(schema, ast, ctx)
|
|
167
|
+
if (registeredType) return registeredType
|
|
168
|
+
|
|
169
|
+
// Check registered interfaces
|
|
170
|
+
const registeredInterface = findRegisteredInterface(schema, ast, ctx)
|
|
171
|
+
if (registeredInterface) return registeredInterface
|
|
172
|
+
|
|
173
|
+
// Handle transformations (like S.Array, S.optional, etc)
|
|
174
|
+
if (ast._tag === "Transformation") {
|
|
175
|
+
return handleTransformationAST(ast, ctx)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Handle unions (enum literals or object type unions)
|
|
179
|
+
if (ast._tag === "Union") {
|
|
180
|
+
return handleUnionAST(ast, ctx)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Check single literal for enum match
|
|
184
|
+
if (ast._tag === "Literal") {
|
|
185
|
+
const enumType = findEnumForLiteral(ast, ctx)
|
|
186
|
+
if (enumType) return enumType
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Handle tuple types (readonly arrays)
|
|
190
|
+
if (ast._tag === "TupleType") {
|
|
191
|
+
return handleTupleTypeAST(ast, ctx)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Handle Suspend (recursive/self-referential schemas)
|
|
195
|
+
if (ast._tag === "Suspend") {
|
|
196
|
+
const innerAst = (ast as any).f()
|
|
197
|
+
return toGraphQLTypeWithRegistry(S.make(innerAst), ctx)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Fall back to default conversion
|
|
201
|
+
return toGraphQLType(schema)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Find a registered object type matching this schema (O(1) with reverse lookup)
|
|
206
|
+
*/
|
|
207
|
+
function findRegisteredType(
|
|
208
|
+
schema: S.Schema<any, any, any>,
|
|
209
|
+
ast: AST.AST,
|
|
210
|
+
ctx: TypeConversionContext
|
|
211
|
+
): GraphQLObjectType | undefined {
|
|
212
|
+
// Use reverse lookup maps for O(1) lookup
|
|
213
|
+
const typeName = ctx.schemaToTypeName?.get(schema) ?? ctx.astToTypeName?.get(ast)
|
|
214
|
+
if (typeName) {
|
|
215
|
+
return ctx.typeRegistry.get(typeName)
|
|
216
|
+
}
|
|
217
|
+
return undefined
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Find a registered interface matching this schema (O(1) with reverse lookup)
|
|
222
|
+
*/
|
|
223
|
+
function findRegisteredInterface(
|
|
224
|
+
schema: S.Schema<any, any, any>,
|
|
225
|
+
ast: AST.AST,
|
|
226
|
+
ctx: TypeConversionContext
|
|
227
|
+
): GraphQLInterfaceType | undefined {
|
|
228
|
+
// Use reverse lookup maps for O(1) lookup
|
|
229
|
+
const interfaceName = ctx.schemaToInterfaceName?.get(schema) ?? ctx.astToInterfaceName?.get(ast)
|
|
230
|
+
if (interfaceName) {
|
|
231
|
+
return ctx.interfaceRegistry.get(interfaceName)
|
|
232
|
+
}
|
|
233
|
+
return undefined
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Handle Transformation AST nodes (arrays, optional, Schema.Class, etc.)
|
|
238
|
+
*/
|
|
239
|
+
function handleTransformationAST(ast: any, ctx: TypeConversionContext): any {
|
|
240
|
+
const toAst = ast.to
|
|
241
|
+
|
|
242
|
+
// Check if it's an array (readonly array on the to side)
|
|
243
|
+
if (toAst._tag === "TupleType") {
|
|
244
|
+
if (toAst.rest && toAst.rest.length > 0) {
|
|
245
|
+
const elementSchema = S.make(toAst.rest[0].type)
|
|
246
|
+
const elementType = toGraphQLTypeWithRegistry(elementSchema, ctx)
|
|
247
|
+
return new GraphQLList(elementType)
|
|
248
|
+
} else if (toAst.elements.length > 0) {
|
|
249
|
+
const elementSchema = S.make(toAst.elements[0].type)
|
|
250
|
+
const elementType = toGraphQLTypeWithRegistry(elementSchema, ctx)
|
|
251
|
+
return new GraphQLList(elementType)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Other transformations - recurse on the "to" side
|
|
256
|
+
return toGraphQLTypeWithRegistry(S.make(ast.to), ctx)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Handle Union AST nodes (literal enums or object type unions)
|
|
261
|
+
*/
|
|
262
|
+
function handleUnionAST(ast: any, ctx: TypeConversionContext): any {
|
|
263
|
+
const allLiterals = ast.types.every((t: any) => t._tag === "Literal")
|
|
264
|
+
|
|
265
|
+
if (allLiterals) {
|
|
266
|
+
// This might be an enum
|
|
267
|
+
const enumType = findEnumForLiteralUnion(ast.types, ctx)
|
|
268
|
+
if (enumType) return enumType
|
|
269
|
+
} else {
|
|
270
|
+
// This is a Union of object types - check if it matches a registered union
|
|
271
|
+
const unionType = findRegisteredUnion(ast.types, ctx)
|
|
272
|
+
if (unionType) return unionType
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Fallback: use first type
|
|
276
|
+
if (ast.types.length > 0) {
|
|
277
|
+
return toGraphQLTypeWithRegistry(S.make(ast.types[0]), ctx)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return toGraphQLType(S.make(ast))
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Find a registered enum matching a union of literals (uses cached sorted values)
|
|
285
|
+
*/
|
|
286
|
+
function findEnumForLiteralUnion(
|
|
287
|
+
types: any[],
|
|
288
|
+
ctx: TypeConversionContext
|
|
289
|
+
): GraphQLEnumType | undefined {
|
|
290
|
+
const literalValues = types.map((t: any) => String(t.literal)).sort()
|
|
291
|
+
|
|
292
|
+
for (const [enumName] of ctx.enums) {
|
|
293
|
+
// Use cached sorted values instead of sorting on every comparison
|
|
294
|
+
const enumValues = ctx.enumSortedValues?.get(enumName)
|
|
295
|
+
if (
|
|
296
|
+
enumValues &&
|
|
297
|
+
literalValues.length === enumValues.length &&
|
|
298
|
+
literalValues.every((v: string, i: number) => v === enumValues[i])
|
|
299
|
+
) {
|
|
300
|
+
return ctx.enumRegistry.get(enumName)
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return undefined
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Find a registered union matching an object type union (uses cached sorted types)
|
|
308
|
+
*/
|
|
309
|
+
function findRegisteredUnion(
|
|
310
|
+
types: any[],
|
|
311
|
+
ctx: TypeConversionContext
|
|
312
|
+
): GraphQLUnionType | undefined {
|
|
313
|
+
// Collect _tag values from each union member
|
|
314
|
+
const memberTags: string[] = []
|
|
315
|
+
for (const memberAst of types) {
|
|
316
|
+
if (memberAst._tag === "TypeLiteral") {
|
|
317
|
+
const tagProp = memberAst.propertySignatures.find((p: any) => String(p.name) === "_tag")
|
|
318
|
+
if (tagProp && tagProp.type._tag === "Literal") {
|
|
319
|
+
memberTags.push(String(tagProp.type.literal))
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Check if any registered union has matching types
|
|
325
|
+
if (memberTags.length === types.length) {
|
|
326
|
+
const sortedTags = memberTags.sort()
|
|
327
|
+
for (const [unionName] of ctx.unions) {
|
|
328
|
+
// Use cached sorted types instead of sorting on every comparison
|
|
329
|
+
const unionTypes = ctx.unionSortedTypes?.get(unionName)
|
|
330
|
+
if (
|
|
331
|
+
unionTypes &&
|
|
332
|
+
sortedTags.length === unionTypes.length &&
|
|
333
|
+
sortedTags.every((tag, i) => tag === unionTypes[i])
|
|
334
|
+
) {
|
|
335
|
+
return ctx.unionRegistry.get(unionName)
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return undefined
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Find a registered enum containing a single literal value (O(1) with reverse lookup)
|
|
344
|
+
*/
|
|
345
|
+
function findEnumForLiteral(ast: any, ctx: TypeConversionContext): GraphQLEnumType | undefined {
|
|
346
|
+
const literalValue = String(ast.literal)
|
|
347
|
+
// Use reverse lookup map for O(1) lookup instead of O(N×M) iteration
|
|
348
|
+
const enumName = ctx.literalToEnumName?.get(literalValue)
|
|
349
|
+
if (enumName) {
|
|
350
|
+
return ctx.enumRegistry.get(enumName)
|
|
351
|
+
}
|
|
352
|
+
return undefined
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Handle TupleType AST nodes (arrays)
|
|
357
|
+
*/
|
|
358
|
+
function handleTupleTypeAST(ast: any, ctx: TypeConversionContext): any {
|
|
359
|
+
if (ast.rest && ast.rest.length > 0) {
|
|
360
|
+
const elementSchema = S.make(ast.rest[0].type)
|
|
361
|
+
const elementType = toGraphQLTypeWithRegistry(elementSchema, ctx)
|
|
362
|
+
return new GraphQLList(elementType)
|
|
363
|
+
} else if (ast.elements && ast.elements.length > 0) {
|
|
364
|
+
const elementSchema = S.make(ast.elements[0].type)
|
|
365
|
+
const elementType = toGraphQLTypeWithRegistry(elementSchema, ctx)
|
|
366
|
+
return new GraphQLList(elementType)
|
|
367
|
+
}
|
|
368
|
+
return toGraphQLType(S.make(ast))
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Convert a schema to GraphQL fields
|
|
373
|
+
*/
|
|
374
|
+
export function schemaToFields(
|
|
375
|
+
schema: S.Schema<any, any, any>,
|
|
376
|
+
ctx: TypeConversionContext
|
|
377
|
+
): GraphQLFieldConfigMap<any, any> {
|
|
378
|
+
let ast = schema.ast
|
|
379
|
+
|
|
380
|
+
// Handle Transformation (Schema.Class, TaggedClass)
|
|
381
|
+
if (ast._tag === "Transformation") {
|
|
382
|
+
ast = (ast as any).to
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Handle Declaration (Schema.Class wraps TypeLiteral in Declaration)
|
|
386
|
+
if (ast._tag === "Declaration") {
|
|
387
|
+
const typeParams = (ast as any).typeParameters
|
|
388
|
+
if (typeParams && typeParams.length > 0 && typeParams[0]._tag === "TypeLiteral") {
|
|
389
|
+
ast = typeParams[0]
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (ast._tag === "TypeLiteral") {
|
|
394
|
+
const fields: GraphQLFieldConfigMap<any, any> = {}
|
|
395
|
+
|
|
396
|
+
for (const field of (ast as any).propertySignatures) {
|
|
397
|
+
const fieldName = String(field.name)
|
|
398
|
+
const fieldSchema = S.make(field.type)
|
|
399
|
+
let fieldType = toGraphQLTypeWithRegistry(fieldSchema, ctx)
|
|
400
|
+
|
|
401
|
+
// Make non-optional fields non-null (memoized)
|
|
402
|
+
if (!field.isOptional) {
|
|
403
|
+
fieldType = getNonNull(fieldType)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
fields[fieldName] = { type: fieldType }
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return fields
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return {}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Convert a schema to GraphQL input fields
|
|
417
|
+
*/
|
|
418
|
+
export function schemaToInputFields(
|
|
419
|
+
schema: S.Schema<any, any, any>,
|
|
420
|
+
enumRegistry: Map<string, GraphQLEnumType>,
|
|
421
|
+
inputRegistry: Map<string, GraphQLInputObjectType>,
|
|
422
|
+
inputs: Map<string, InputTypeRegistration>,
|
|
423
|
+
enums: Map<string, EnumRegistration>,
|
|
424
|
+
cache?: InputTypeLookupCache
|
|
425
|
+
): GraphQLInputFieldConfigMap {
|
|
426
|
+
const ast = schema.ast
|
|
427
|
+
|
|
428
|
+
if (ast._tag === "TypeLiteral") {
|
|
429
|
+
const fields: GraphQLInputFieldConfigMap = {}
|
|
430
|
+
|
|
431
|
+
for (const field of ast.propertySignatures) {
|
|
432
|
+
const fieldName = String(field.name)
|
|
433
|
+
const fieldSchema = S.make(field.type)
|
|
434
|
+
let fieldType = toGraphQLInputTypeWithRegistry(
|
|
435
|
+
fieldSchema,
|
|
436
|
+
enumRegistry,
|
|
437
|
+
inputRegistry,
|
|
438
|
+
inputs,
|
|
439
|
+
enums,
|
|
440
|
+
cache
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
// Make non-optional fields non-null (memoized)
|
|
444
|
+
if (!field.isOptional) {
|
|
445
|
+
fieldType = getNonNull(fieldType)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
fields[fieldName] = { type: fieldType }
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return fields
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return {}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Optional cache for input type lookups to enable O(1) resolution
|
|
459
|
+
*/
|
|
460
|
+
export interface InputTypeLookupCache {
|
|
461
|
+
schemaToInputName?: Map<S.Schema<any, any, any>, string>
|
|
462
|
+
astToInputName?: Map<AST.AST, string>
|
|
463
|
+
literalToEnumName?: Map<string, string>
|
|
464
|
+
enumSortedValues?: Map<string, readonly string[]>
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Build lookup caches for input type resolution
|
|
469
|
+
*/
|
|
470
|
+
export function buildInputTypeLookupCache(
|
|
471
|
+
inputs: Map<string, InputTypeRegistration>,
|
|
472
|
+
enums: Map<string, EnumRegistration>
|
|
473
|
+
): InputTypeLookupCache {
|
|
474
|
+
const cache: InputTypeLookupCache = {
|
|
475
|
+
schemaToInputName: new Map(),
|
|
476
|
+
astToInputName: new Map(),
|
|
477
|
+
literalToEnumName: new Map(),
|
|
478
|
+
enumSortedValues: new Map(),
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Build input type reverse lookups
|
|
482
|
+
for (const [inputName, inputReg] of inputs) {
|
|
483
|
+
cache.schemaToInputName!.set(inputReg.schema, inputName)
|
|
484
|
+
cache.astToInputName!.set(inputReg.schema.ast, inputName)
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Build enum lookups
|
|
488
|
+
for (const [enumName, enumReg] of enums) {
|
|
489
|
+
cache.enumSortedValues!.set(enumName, [...enumReg.values].sort())
|
|
490
|
+
for (const value of enumReg.values) {
|
|
491
|
+
cache.literalToEnumName!.set(value, enumName)
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
return cache
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Convert a schema to GraphQL input type, checking enum and input registries.
|
|
500
|
+
* Uses O(1) reverse lookups when cache is provided.
|
|
501
|
+
*/
|
|
502
|
+
export function toGraphQLInputTypeWithRegistry(
|
|
503
|
+
schema: S.Schema<any, any, any>,
|
|
504
|
+
enumRegistry: Map<string, GraphQLEnumType>,
|
|
505
|
+
inputRegistry: Map<string, GraphQLInputObjectType>,
|
|
506
|
+
inputs: Map<string, InputTypeRegistration>,
|
|
507
|
+
enums: Map<string, EnumRegistration>,
|
|
508
|
+
cache?: InputTypeLookupCache
|
|
509
|
+
): any {
|
|
510
|
+
const ast = schema.ast
|
|
511
|
+
|
|
512
|
+
// Handle transformations (like S.optional wrapping)
|
|
513
|
+
if (ast._tag === "Transformation") {
|
|
514
|
+
const toAst = (ast as any).to
|
|
515
|
+
return toGraphQLInputTypeWithRegistry(
|
|
516
|
+
S.make(toAst),
|
|
517
|
+
enumRegistry,
|
|
518
|
+
inputRegistry,
|
|
519
|
+
inputs,
|
|
520
|
+
enums,
|
|
521
|
+
cache
|
|
522
|
+
)
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Check if this schema matches a registered input type (O(1) with cache)
|
|
526
|
+
if (cache?.schemaToInputName || cache?.astToInputName) {
|
|
527
|
+
const inputName = cache.schemaToInputName?.get(schema) ?? cache.astToInputName?.get(ast)
|
|
528
|
+
if (inputName) {
|
|
529
|
+
const result = inputRegistry.get(inputName)
|
|
530
|
+
if (result) return result
|
|
531
|
+
}
|
|
532
|
+
} else {
|
|
533
|
+
// Fallback to linear scan if no cache
|
|
534
|
+
for (const [inputName, inputReg] of inputs) {
|
|
535
|
+
if (inputReg.schema.ast === ast || inputReg.schema === schema) {
|
|
536
|
+
const result = inputRegistry.get(inputName)
|
|
537
|
+
if (result) return result
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Check if this schema matches a registered enum
|
|
543
|
+
if (ast._tag === "Union") {
|
|
544
|
+
const unionAst = ast as any
|
|
545
|
+
|
|
546
|
+
// Handle S.optional which creates Union(LiteralUnion, UndefinedKeyword)
|
|
547
|
+
const nonUndefinedTypes = unionAst.types.filter((t: any) => t._tag !== "UndefinedKeyword")
|
|
548
|
+
if (nonUndefinedTypes.length === 1 && nonUndefinedTypes[0]._tag === "Union") {
|
|
549
|
+
return toGraphQLInputTypeWithRegistry(
|
|
550
|
+
S.make(nonUndefinedTypes[0]),
|
|
551
|
+
enumRegistry,
|
|
552
|
+
inputRegistry,
|
|
553
|
+
inputs,
|
|
554
|
+
enums,
|
|
555
|
+
cache
|
|
556
|
+
)
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Check for nested input type inside optional
|
|
560
|
+
if (nonUndefinedTypes.length === 1 && nonUndefinedTypes[0]._tag === "TypeLiteral") {
|
|
561
|
+
return toGraphQLInputTypeWithRegistry(
|
|
562
|
+
S.make(nonUndefinedTypes[0]),
|
|
563
|
+
enumRegistry,
|
|
564
|
+
inputRegistry,
|
|
565
|
+
inputs,
|
|
566
|
+
enums,
|
|
567
|
+
cache
|
|
568
|
+
)
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const allLiterals = unionAst.types.every((t: any) => t._tag === "Literal")
|
|
572
|
+
|
|
573
|
+
if (allLiterals) {
|
|
574
|
+
const literalValues = unionAst.types.map((t: any) => String(t.literal)).sort()
|
|
575
|
+
|
|
576
|
+
// Use cached sorted values if available
|
|
577
|
+
for (const [enumName] of enums) {
|
|
578
|
+
const enumValues =
|
|
579
|
+
cache?.enumSortedValues?.get(enumName) ?? [...enums.get(enumName)!.values].sort()
|
|
580
|
+
if (
|
|
581
|
+
literalValues.length === enumValues.length &&
|
|
582
|
+
literalValues.every((v: string, i: number) => v === enumValues[i])
|
|
583
|
+
) {
|
|
584
|
+
const result = enumRegistry.get(enumName)
|
|
585
|
+
if (result) return result
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Check single literal (O(1) with cache)
|
|
592
|
+
if (ast._tag === "Literal") {
|
|
593
|
+
const literalValue = String((ast as any).literal)
|
|
594
|
+
if (cache?.literalToEnumName) {
|
|
595
|
+
const enumName = cache.literalToEnumName.get(literalValue)
|
|
596
|
+
if (enumName) {
|
|
597
|
+
const result = enumRegistry.get(enumName)
|
|
598
|
+
if (result) return result
|
|
599
|
+
}
|
|
600
|
+
} else {
|
|
601
|
+
// Fallback to linear scan if no cache
|
|
602
|
+
for (const [enumName, enumReg] of enums) {
|
|
603
|
+
if (enumReg.values.includes(literalValue)) {
|
|
604
|
+
const result = enumRegistry.get(enumName)
|
|
605
|
+
if (result) return result
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// Handle Suspend (recursive/self-referential schemas)
|
|
612
|
+
if (ast._tag === "Suspend") {
|
|
613
|
+
const innerAst = (ast as any).f()
|
|
614
|
+
return toGraphQLInputTypeWithRegistry(
|
|
615
|
+
S.make(innerAst),
|
|
616
|
+
enumRegistry,
|
|
617
|
+
inputRegistry,
|
|
618
|
+
inputs,
|
|
619
|
+
enums,
|
|
620
|
+
cache
|
|
621
|
+
)
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Fall back to default toGraphQLInputType
|
|
625
|
+
return toGraphQLInputType(schema)
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Convert a schema to GraphQL arguments with registry support.
|
|
630
|
+
* Uses O(1) reverse lookups when cache is provided.
|
|
631
|
+
*/
|
|
632
|
+
export function toGraphQLArgsWithRegistry(
|
|
633
|
+
schema: S.Schema<any, any, any>,
|
|
634
|
+
enumRegistry: Map<string, GraphQLEnumType>,
|
|
635
|
+
inputRegistry: Map<string, GraphQLInputObjectType>,
|
|
636
|
+
inputs: Map<string, InputTypeRegistration>,
|
|
637
|
+
enums: Map<string, EnumRegistration>,
|
|
638
|
+
cache?: InputTypeLookupCache
|
|
639
|
+
): any {
|
|
640
|
+
const ast = schema.ast
|
|
641
|
+
|
|
642
|
+
if (ast._tag === "TypeLiteral") {
|
|
643
|
+
const args: Record<string, { type: any }> = {}
|
|
644
|
+
|
|
645
|
+
for (const field of (ast as any).propertySignatures) {
|
|
646
|
+
const fieldName = String(field.name)
|
|
647
|
+
const fieldSchema = S.make(field.type)
|
|
648
|
+
let fieldType = toGraphQLInputTypeWithRegistry(
|
|
649
|
+
fieldSchema,
|
|
650
|
+
enumRegistry,
|
|
651
|
+
inputRegistry,
|
|
652
|
+
inputs,
|
|
653
|
+
enums,
|
|
654
|
+
cache
|
|
655
|
+
)
|
|
656
|
+
|
|
657
|
+
// Make non-optional fields non-null (memoized)
|
|
658
|
+
if (!field.isOptional) {
|
|
659
|
+
fieldType = getNonNull(fieldType)
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
args[fieldName] = { type: fieldType }
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
return args
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// Fall back to default toGraphQLArgs
|
|
669
|
+
return toGraphQLArgs(schema)
|
|
670
|
+
}
|