graphqlite 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.
@@ -0,0 +1,238 @@
1
+ module GraphQLite
2
+ # Schema is the main entry point for defining GraphQL schemas
3
+ class Schema
4
+ attr_reader :query_type, :mutation_type, :subscription_type, :types, :directives
5
+
6
+ def initialize(&block)
7
+ @types = {}
8
+ @directives = {}
9
+ @query_type = nil
10
+ @mutation_type = nil
11
+ @subscription_type = nil
12
+ @type_resolver = TypeResolver.new(self)
13
+
14
+ # Register built-in scalars
15
+ register_type(Types::INT)
16
+ register_type(Types::FLOAT)
17
+ register_type(Types::STRING)
18
+ register_type(Types::BOOLEAN)
19
+ register_type(Types::ID)
20
+
21
+ # Build schema using DSL
22
+ instance_eval(&block) if block_given?
23
+
24
+ # Add introspection types
25
+ Introspection.add_introspection_types(self)
26
+ end
27
+
28
+ def query(&block)
29
+ @query_type = define_root_type('Query', &block)
30
+ end
31
+
32
+ def mutation(&block)
33
+ @mutation_type = define_root_type('Mutation', &block)
34
+ end
35
+
36
+ def subscription(&block)
37
+ @subscription_type = define_root_type('Subscription', &block)
38
+ end
39
+
40
+ def object(name, description: nil, interfaces: [], &block)
41
+ type = Types::ObjectType.new(name.to_s, description: description, interfaces: interfaces)
42
+ register_type(type)
43
+
44
+ builder = TypeBuilder.new(type, self)
45
+ builder.instance_eval(&block) if block_given?
46
+
47
+ type
48
+ end
49
+
50
+ def interface(name, description: nil, resolve_type: nil, &block)
51
+ type = Types::InterfaceType.new(name.to_s, description: description, resolve_type: resolve_type)
52
+ register_type(type)
53
+
54
+ builder = TypeBuilder.new(type, self)
55
+ builder.instance_eval(&block) if block_given?
56
+
57
+ type
58
+ end
59
+
60
+ def union(name, types: [], description: nil, resolve_type: nil)
61
+ type = Types::UnionType.new(name.to_s, types: types, description: description, resolve_type: resolve_type)
62
+ register_type(type)
63
+ type
64
+ end
65
+
66
+ def enum(name, values: {}, description: nil)
67
+ type = Types::EnumType.new(name.to_s, values: values, description: description)
68
+ register_type(type)
69
+ type
70
+ end
71
+
72
+ def input_object(name, description: nil, &block)
73
+ type = Types::InputObjectType.new(name.to_s, description: description)
74
+ register_type(type)
75
+
76
+ builder = TypeBuilder.new(type, self)
77
+ builder.instance_eval(&block) if block_given?
78
+
79
+ type
80
+ end
81
+
82
+ def scalar(name, description: nil, serialize: nil, parse_value: nil, parse_literal: nil)
83
+ type = Types::ScalarType.new(
84
+ name.to_s,
85
+ description: description,
86
+ serialize: serialize,
87
+ parse_value: parse_value,
88
+ parse_literal: parse_literal
89
+ )
90
+ register_type(type)
91
+ type
92
+ end
93
+
94
+ def execute(query_string, variables: {}, operation_name: nil, context: {})
95
+ # Parse the query
96
+ document = Parser.new(query_string).parse
97
+
98
+ # Validate the query
99
+ validator = Validator.new(self)
100
+ errors = validator.validate(document)
101
+ return { 'errors' => errors.map { |e| { 'message' => e.message } } } unless errors.empty?
102
+
103
+ # Execute the query
104
+ executor = Executor.new(self)
105
+ executor.execute(document, variables: variables, operation_name: operation_name, context: context)
106
+ rescue ParseError, ValidationError, ExecutionError => e
107
+ { 'errors' => [{ 'message' => e.message }] }
108
+ end
109
+
110
+ def get_type(name)
111
+ return nil if name.nil?
112
+ @types[name.to_s]
113
+ end
114
+
115
+ def register_type(type)
116
+ @types[type.name] = type
117
+ end
118
+
119
+ private
120
+
121
+ def define_root_type(name, &block)
122
+ type = Types::ObjectType.new(name)
123
+ register_type(type)
124
+
125
+ builder = TypeBuilder.new(type, self)
126
+ builder.instance_eval(&block) if block_given?
127
+
128
+ type
129
+ end
130
+
131
+ # Helper class for building types using DSL
132
+ class TypeBuilder
133
+ def initialize(type, schema)
134
+ @type = type
135
+ @schema = schema
136
+ @resolver = TypeResolver.new(schema)
137
+ end
138
+
139
+ def field(name, type = nil, description: nil, null: true, deprecation_reason: nil, &block)
140
+ # Resolve type reference
141
+ resolved_type = @resolver.resolve(type)
142
+ resolved_type = Types::NonNullType.new(resolved_type) unless null
143
+
144
+ if @type.is_a?(Types::ObjectType)
145
+ # Check if block expects a parameter (builder pattern) or not (resolver pattern)
146
+ if block && block.arity > 0
147
+ # Builder pattern: pass FieldBuilder to block
148
+ field_def = @type.field(name, resolved_type, description: description, deprecation_reason: deprecation_reason)
149
+ field_builder = FieldBuilder.new(field_def, @schema)
150
+ block.call(field_builder)
151
+ field_builder
152
+ else
153
+ # Resolver pattern: block is the resolver
154
+ field_def = @type.field(name, resolved_type, description: description, deprecation_reason: deprecation_reason, &block)
155
+ FieldBuilder.new(field_def, @schema)
156
+ end
157
+ elsif @type.is_a?(Types::InterfaceType)
158
+ @type.field(name, resolved_type, description: description, deprecation_reason: deprecation_reason)
159
+ elsif @type.is_a?(Types::InputObjectType)
160
+ default_value = null ? nil : block&.call
161
+ @type.field(name, resolved_type, description: description, default_value: default_value)
162
+ end
163
+ end
164
+ end
165
+
166
+ # Helper class for building fields with arguments
167
+ class FieldBuilder
168
+ attr_reader :field
169
+
170
+ def initialize(field, schema)
171
+ @field = field
172
+ @schema = schema
173
+ @resolver = TypeResolver.new(schema)
174
+ end
175
+
176
+ def argument(name, type, description: nil, default_value: nil)
177
+ resolved_type = @resolver.resolve(type)
178
+ @field.argument(name, resolved_type, description: description, default_value: default_value)
179
+ self
180
+ end
181
+
182
+ alias arg argument
183
+
184
+ def resolve(&block)
185
+ @field.instance_variable_set(:@resolve, block)
186
+ self
187
+ end
188
+ end
189
+
190
+ # Lazy type reference for forward references
191
+ class TypeReference
192
+ attr_reader :name, :schema
193
+
194
+ def initialize(name, schema)
195
+ @name = name
196
+ @schema = schema
197
+ end
198
+
199
+ def resolve
200
+ type = @schema.get_type(@name.to_s)
201
+ raise TypeError, "Unknown type: #{@name}" unless type
202
+ type
203
+ end
204
+ end
205
+
206
+ # Type resolver handles type references (symbols, strings, types)
207
+ class TypeResolver
208
+ def initialize(schema)
209
+ @schema = schema
210
+ end
211
+
212
+ def resolve(type_ref)
213
+ case type_ref
214
+ when Types::BaseType, Types::ListType, Types::NonNullType
215
+ type_ref
216
+ when String, Symbol
217
+ # Look up type by name - if it doesn't exist yet, return a lazy reference
218
+ type = @schema.get_type(type_ref.to_s)
219
+ return type if type
220
+ # Return lazy reference for forward references (mainly for introspection)
221
+ TypeReference.new(type_ref, @schema)
222
+ when Array
223
+ # Array notation for lists: [String] -> ListType
224
+ raise TypeError, "Array type must have exactly one element" unless type_ref.length == 1
225
+ Types::ListType.new(resolve(type_ref[0]))
226
+ when Class
227
+ # Ruby class reference - auto-convert to type name
228
+ type_name = type_ref.name.split('::').last
229
+ type = @schema.get_type(type_name)
230
+ return type if type
231
+ TypeReference.new(type_name, @schema)
232
+ else
233
+ raise TypeError, "Invalid type reference: #{type_ref.inspect}"
234
+ end
235
+ end
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,336 @@
1
+ module GraphQLite
2
+ module Types
3
+ # Base class for all types
4
+ class BaseType
5
+ attr_reader :name, :description
6
+
7
+ def initialize(name, description: nil)
8
+ @name = name
9
+ @description = description
10
+ end
11
+
12
+ def to_s
13
+ name
14
+ end
15
+
16
+ def non_null?
17
+ false
18
+ end
19
+
20
+ def list?
21
+ false
22
+ end
23
+ end
24
+
25
+ # Scalar types
26
+ class ScalarType < BaseType
27
+ attr_reader :serialize, :parse_value, :parse_literal
28
+
29
+ def initialize(name, description: nil, serialize: nil, parse_value: nil, parse_literal: nil)
30
+ super(name, description: description)
31
+ @serialize = serialize || ->(value) { value }
32
+ @parse_value = parse_value || ->(value) { value }
33
+ @parse_literal = parse_literal || ->(value) { value }
34
+ end
35
+
36
+ def kind
37
+ 'SCALAR'
38
+ end
39
+ end
40
+
41
+ # Object types
42
+ class ObjectType < BaseType
43
+ attr_reader :fields, :interfaces
44
+
45
+ def initialize(name, description: nil, interfaces: [])
46
+ super(name, description: description)
47
+ @fields = {}
48
+ @interfaces = interfaces
49
+ end
50
+
51
+ def field(name, type = nil, description: nil, deprecation_reason: nil, &block)
52
+ @fields[name.to_s] = Field.new(
53
+ name: name.to_s,
54
+ type: type,
55
+ description: description,
56
+ deprecation_reason: deprecation_reason,
57
+ resolve: block
58
+ )
59
+ end
60
+
61
+ def kind
62
+ 'OBJECT'
63
+ end
64
+ end
65
+
66
+ # Interface types
67
+ class InterfaceType < BaseType
68
+ attr_reader :fields, :resolve_type
69
+
70
+ def initialize(name, description: nil, resolve_type: nil)
71
+ super(name, description: description)
72
+ @fields = {}
73
+ @resolve_type = resolve_type
74
+ end
75
+
76
+ def field(name, type, description: nil, deprecation_reason: nil)
77
+ @fields[name.to_s] = Field.new(
78
+ name: name.to_s,
79
+ type: type,
80
+ description: description,
81
+ deprecation_reason: deprecation_reason
82
+ )
83
+ end
84
+
85
+ def kind
86
+ 'INTERFACE'
87
+ end
88
+ end
89
+
90
+ # Union types
91
+ class UnionType < BaseType
92
+ attr_reader :types, :resolve_type
93
+
94
+ def initialize(name, types: [], description: nil, resolve_type: nil)
95
+ super(name, description: description)
96
+ @types = types
97
+ @resolve_type = resolve_type
98
+ end
99
+
100
+ def kind
101
+ 'UNION'
102
+ end
103
+ end
104
+
105
+ # Enum types
106
+ class EnumType < BaseType
107
+ attr_reader :values
108
+
109
+ def initialize(name, values: {}, description: nil)
110
+ super(name, description: description)
111
+ @values = values.transform_values do |value|
112
+ value.is_a?(Hash) ? EnumValue.new(**value) : EnumValue.new(value: value)
113
+ end
114
+ end
115
+
116
+ def kind
117
+ 'ENUM'
118
+ end
119
+ end
120
+
121
+ EnumValue = Struct.new(:value, :description, :deprecation_reason, keyword_init: true)
122
+
123
+ # Input object types
124
+ class InputObjectType < BaseType
125
+ attr_reader :fields
126
+
127
+ def initialize(name, description: nil)
128
+ super(name, description: description)
129
+ @fields = {}
130
+ end
131
+
132
+ def field(name, type, description: nil, default_value: nil)
133
+ @fields[name.to_s] = InputField.new(
134
+ name: name.to_s,
135
+ type: type,
136
+ description: description,
137
+ default_value: default_value
138
+ )
139
+ end
140
+
141
+ def kind
142
+ 'INPUT_OBJECT'
143
+ end
144
+ end
145
+
146
+ # List type wrapper
147
+ class ListType
148
+ attr_reader :of_type
149
+
150
+ def initialize(of_type)
151
+ @of_type = of_type
152
+ end
153
+
154
+ def to_s
155
+ "[#{@of_type}]"
156
+ end
157
+
158
+ def list?
159
+ true
160
+ end
161
+
162
+ def non_null?
163
+ false
164
+ end
165
+
166
+ def kind
167
+ 'LIST'
168
+ end
169
+
170
+ # Resolve lazy type references
171
+ def resolve_types
172
+ @of_type = @of_type.resolve if @of_type.respond_to?(:resolve)
173
+ self
174
+ end
175
+ end
176
+
177
+ # Non-null type wrapper
178
+ class NonNullType
179
+ attr_reader :of_type
180
+
181
+ def initialize(of_type)
182
+ # Allow TypeReference as of_type, will be resolved later
183
+ raise TypeError, "Cannot wrap NonNullType in NonNullType" if of_type.is_a?(NonNullType)
184
+ @of_type = of_type
185
+ end
186
+
187
+ def to_s
188
+ "#{@of_type}!"
189
+ end
190
+
191
+ def non_null?
192
+ true
193
+ end
194
+
195
+ def list?
196
+ false
197
+ end
198
+
199
+ def kind
200
+ 'NON_NULL'
201
+ end
202
+
203
+ # Resolve lazy type references
204
+ def resolve_types
205
+ @of_type = @of_type.resolve if @of_type.respond_to?(:resolve)
206
+ self
207
+ end
208
+ end
209
+
210
+ # Field definition
211
+ class Field
212
+ attr_reader :name, :type, :description, :deprecation_reason, :arguments, :resolve
213
+
214
+ def initialize(name:, type:, description: nil, deprecation_reason: nil, resolve: nil)
215
+ @name = name
216
+ @type = type
217
+ @description = description
218
+ @deprecation_reason = deprecation_reason
219
+ @arguments = {}
220
+ @resolve = resolve
221
+ end
222
+
223
+ def argument(name, type, description: nil, default_value: nil)
224
+ @arguments[name.to_s] = Argument.new(
225
+ name: name.to_s,
226
+ type: type,
227
+ description: description,
228
+ default_value: default_value
229
+ )
230
+ end
231
+
232
+ def deprecated?
233
+ !@deprecation_reason.nil?
234
+ end
235
+ end
236
+
237
+ # Argument definition
238
+ class Argument
239
+ attr_reader :name, :type, :description, :default_value
240
+
241
+ def initialize(name:, type:, description: nil, default_value: nil)
242
+ @name = name
243
+ @type = type
244
+ @description = description
245
+ @default_value = default_value
246
+ end
247
+ end
248
+
249
+ # Input field definition
250
+ class InputField
251
+ attr_reader :name, :type, :description, :default_value
252
+
253
+ def initialize(name:, type:, description: nil, default_value: nil)
254
+ @name = name
255
+ @type = type
256
+ @description = description
257
+ @default_value = default_value
258
+ end
259
+ end
260
+
261
+ # Built-in scalar types
262
+ INT = ScalarType.new(
263
+ 'Int',
264
+ description: 'The `Int` scalar type represents non-fractional signed whole numeric values.',
265
+ serialize: ->(value) { value.to_i },
266
+ parse_value: ->(value) { value.to_i },
267
+ parse_literal: ->(value) { value.is_a?(Parser::IntValue) ? value.value.to_i : nil }
268
+ )
269
+
270
+ FLOAT = ScalarType.new(
271
+ 'Float',
272
+ description: 'The `Float` scalar type represents signed double-precision fractional values.',
273
+ serialize: ->(value) { value.to_f },
274
+ parse_value: ->(value) { value.to_f },
275
+ parse_literal: ->(value) {
276
+ case value
277
+ when Parser::FloatValue
278
+ value.value.to_f
279
+ when Parser::IntValue
280
+ value.value.to_f
281
+ end
282
+ }
283
+ )
284
+
285
+ STRING = ScalarType.new(
286
+ 'String',
287
+ description: 'The `String` scalar type represents textual data.',
288
+ serialize: ->(value) { value.to_s },
289
+ parse_value: ->(value) { value.to_s },
290
+ parse_literal: ->(value) { value.is_a?(Parser::StringValue) ? value.value : nil }
291
+ )
292
+
293
+ BOOLEAN = ScalarType.new(
294
+ 'Boolean',
295
+ description: 'The `Boolean` scalar type represents `true` or `false`.',
296
+ serialize: ->(value) { !!value },
297
+ parse_value: ->(value) { !!value },
298
+ parse_literal: ->(value) { value.is_a?(Parser::BooleanValue) ? value.value : nil }
299
+ )
300
+
301
+ ID = ScalarType.new(
302
+ 'ID',
303
+ description: 'The `ID` scalar type represents a unique identifier.',
304
+ serialize: ->(value) { value.to_s },
305
+ parse_value: ->(value) { value.to_s },
306
+ parse_literal: ->(value) {
307
+ case value
308
+ when Parser::StringValue, Parser::IntValue
309
+ value.value.to_s
310
+ end
311
+ }
312
+ )
313
+
314
+ # Helper methods to create type wrappers
315
+ def self.list(type)
316
+ ListType.new(type)
317
+ end
318
+
319
+ def self.non_null(type)
320
+ NonNullType.new(type)
321
+ end
322
+
323
+ # Convenience method for non-null types
324
+ class BaseType
325
+ def !
326
+ NonNullType.new(self)
327
+ end
328
+ end
329
+
330
+ class ListType
331
+ def !
332
+ NonNullType.new(self)
333
+ end
334
+ end
335
+ end
336
+ end