|  |  |  | @ -6,8 +6,14 @@ import ( | 
		
	
		
			
				|  |  |  |  | 	"fmt" | 
		
	
		
			
				|  |  |  |  | 	"reflect" | 
		
	
		
			
				|  |  |  |  | 	"runtime" | 
		
	
		
			
				|  |  |  |  | 	"strconv" | 
		
	
		
			
				|  |  |  |  | 	"sync" | 
		
	
		
			
				|  |  |  |  |   "time" | 
		
	
		
			
				|  |  |  |  |   "encoding" | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | 	"golang.org/x/exp/constraints" | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | 	"github.com/google/uuid" | 
		
	
		
			
				|  |  |  |  | 	"github.com/graphql-go/graphql" | 
		
	
		
			
				|  |  |  |  | 	"github.com/graphql-go/graphql/language/ast" | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
	
		
			
				
					|  |  |  | @ -69,6 +75,82 @@ type Context struct { | 
		
	
		
			
				|  |  |  |  |   nodeMap map[NodeID]*Node | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func (ctx *Context) GQLType(t reflect.Type) graphql.Type { | 
		
	
		
			
				|  |  |  |  |   ser, mapped := ctx.TypeTypes[t] | 
		
	
		
			
				|  |  |  |  |   if mapped { | 
		
	
		
			
				|  |  |  |  |     return ctx.TypeMap[ser].Type | 
		
	
		
			
				|  |  |  |  |   } else { | 
		
	
		
			
				|  |  |  |  |     switch t.Kind() { | 
		
	
		
			
				|  |  |  |  |     case reflect.Array: | 
		
	
		
			
				|  |  |  |  |       ser, mapped := ctx.TypeTypes[t.Elem()] | 
		
	
		
			
				|  |  |  |  |       if mapped { | 
		
	
		
			
				|  |  |  |  |         return graphql.NewList(ctx.TypeMap[ser].Type) | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |     case reflect.Slice: | 
		
	
		
			
				|  |  |  |  |       ser, mapped := ctx.TypeTypes[t.Elem()] | 
		
	
		
			
				|  |  |  |  |       if mapped { | 
		
	
		
			
				|  |  |  |  |         return graphql.NewList(ctx.TypeMap[ser].Type) | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |     case reflect.Map: | 
		
	
		
			
				|  |  |  |  |       ser, exists := ctx.TypeTypes[t] | 
		
	
		
			
				|  |  |  |  |       if exists { | 
		
	
		
			
				|  |  |  |  |         return ctx.TypeMap[ser].Type | 
		
	
		
			
				|  |  |  |  |       } else { | 
		
	
		
			
				|  |  |  |  |         err := RegisterMap(ctx, t) | 
		
	
		
			
				|  |  |  |  |         if err != nil { | 
		
	
		
			
				|  |  |  |  |           return nil | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |         return ctx.TypeMap[ctx.TypeTypes[t]].Type | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |     case reflect.Pointer: | 
		
	
		
			
				|  |  |  |  |       ser, mapped := ctx.TypeTypes[t.Elem()] | 
		
	
		
			
				|  |  |  |  |       if mapped { | 
		
	
		
			
				|  |  |  |  |         return ctx.TypeMap[ser].Type | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     return nil | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func RegisterMap(ctx *Context, t reflect.Type) error { | 
		
	
		
			
				|  |  |  |  |   key_type := ctx.GQLType(t.Key()) | 
		
	
		
			
				|  |  |  |  |   if key_type == nil { | 
		
	
		
			
				|  |  |  |  |     return nil | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   val_type := ctx.GQLType(t.Elem()) | 
		
	
		
			
				|  |  |  |  |   if val_type == nil { | 
		
	
		
			
				|  |  |  |  |     return nil | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   gql_pair := graphql.NewObject(graphql.ObjectConfig{ | 
		
	
		
			
				|  |  |  |  |     Name: t.String(), | 
		
	
		
			
				|  |  |  |  |     Fields: graphql.Fields{ | 
		
	
		
			
				|  |  |  |  |       "Key": &graphql.Field{ | 
		
	
		
			
				|  |  |  |  |         Type: key_type, | 
		
	
		
			
				|  |  |  |  |         Resolve: func(p graphql.ResolveParams) (interface{}, error) { | 
		
	
		
			
				|  |  |  |  |           return nil, fmt.Errorf("NOT_IMPLEMENTED") | 
		
	
		
			
				|  |  |  |  |         }, | 
		
	
		
			
				|  |  |  |  |       }, | 
		
	
		
			
				|  |  |  |  |       "Value": &graphql.Field{ | 
		
	
		
			
				|  |  |  |  |         Type: val_type, | 
		
	
		
			
				|  |  |  |  |         Resolve: func(p graphql.ResolveParams) (interface{}, error) { | 
		
	
		
			
				|  |  |  |  |           return nil, fmt.Errorf("NOT_IMPLEMENTED") | 
		
	
		
			
				|  |  |  |  |         }, | 
		
	
		
			
				|  |  |  |  |       }, | 
		
	
		
			
				|  |  |  |  |     }, | 
		
	
		
			
				|  |  |  |  |   }) | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   gql_map := graphql.NewList(gql_pair) | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   ctx.TypeTypes[t] = SerializeType(t) | 
		
	
		
			
				|  |  |  |  |   ctx.TypeMap[SerializeType(t)] = TypeInfo{ | 
		
	
		
			
				|  |  |  |  |     Type: gql_map, | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  |   | 
		
	
		
			
				|  |  |  |  |   return nil | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func BuildSchema(ctx *Context, query, mutation *graphql.Object) (graphql.Schema, error) { | 
		
	
		
			
				|  |  |  |  |   types := []graphql.Type{} | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
	
		
			
				
					|  |  |  | @ -138,13 +220,13 @@ func RegisterExtension[E any, T interface { *E; Extension}](ctx *Context, data i | 
		
	
		
			
				|  |  |  |  |     if tagged_gv { | 
		
	
		
			
				|  |  |  |  |       fields[gv_tag] = field.Index | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |       type_ser, type_mapped := ctx.TypeTypes[field.Type] | 
		
	
		
			
				|  |  |  |  |       if type_mapped == false { | 
		
	
		
			
				|  |  |  |  |       gql_type := ctx.GQLType(field.Type) | 
		
	
		
			
				|  |  |  |  |       if gql_type == nil { | 
		
	
		
			
				|  |  |  |  |         return fmt.Errorf("Extension %s has field %s of unregistered type %s", reflect_type, gv_tag, field.Type) | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |       gql_interface.AddFieldConfig(gv_tag, &graphql.Field{ | 
		
	
		
			
				|  |  |  |  |         Type: ctx.TypeMap[type_ser].Type, | 
		
	
		
			
				|  |  |  |  |         Type: gql_type, | 
		
	
		
			
				|  |  |  |  |       }) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
	
		
			
				
					|  |  |  | @ -217,12 +299,12 @@ func RegisterObject[T any](ctx *Context) error { | 
		
	
		
			
				|  |  |  |  |   for _, field := range(reflect.VisibleFields(reflect_type)) { | 
		
	
		
			
				|  |  |  |  |     gv_tag, tagged_gv := field.Tag.Lookup("gv") | 
		
	
		
			
				|  |  |  |  |     if tagged_gv { | 
		
	
		
			
				|  |  |  |  |       field_type, mapped := ctx.TypeTypes[field.Type] | 
		
	
		
			
				|  |  |  |  |       if mapped == false { | 
		
	
		
			
				|  |  |  |  |         return fmt.Errorf("Object %+v has field %s of unknown type %+v", reflect_type, gv_tag, field_type) | 
		
	
		
			
				|  |  |  |  |       gql_type := ctx.GQLType(field.Type) | 
		
	
		
			
				|  |  |  |  |       if gql_type == nil { | 
		
	
		
			
				|  |  |  |  |         return fmt.Errorf("Object %+v has field %s of unknown type %+v", reflect_type, gv_tag, field.Type) | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |       gql.AddFieldConfig(gv_tag, &graphql.Field{ | 
		
	
		
			
				|  |  |  |  |         Type: ctx.TypeMap[field_type].Type, | 
		
	
		
			
				|  |  |  |  |         Type: gql_type, | 
		
	
		
			
				|  |  |  |  |         Resolve: func(p graphql.ResolveParams) (interface{}, error) { | 
		
	
		
			
				|  |  |  |  |           val, ok := p.Source.(T) | 
		
	
		
			
				|  |  |  |  |           if ok == false { | 
		
	
	
		
			
				
					|  |  |  | @ -248,9 +330,100 @@ func RegisterObject[T any](ctx *Context) error { | 
		
	
		
			
				|  |  |  |  |   return nil | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func RegisterScalar[T any](ctx *Context, to_json func(interface{})interface{}, from_json func(interface{})interface{}, from_ast func(ast.Value)interface{}) error { | 
		
	
		
			
				|  |  |  |  |   reflect_type := reflect.TypeFor[T]() | 
		
	
		
			
				|  |  |  |  |   serialized_type := SerializedTypeFor[T]() | 
		
	
		
			
				|  |  |  |  | func identity(value interface{}) interface{} { | 
		
	
		
			
				|  |  |  |  |   return value | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func stringify(value interface{}) interface{} { | 
		
	
		
			
				|  |  |  |  |   v, ok := value.(encoding.TextMarshaler) | 
		
	
		
			
				|  |  |  |  |   if ok { | 
		
	
		
			
				|  |  |  |  |     b, err := v.MarshalText() | 
		
	
		
			
				|  |  |  |  |     if err != nil { | 
		
	
		
			
				|  |  |  |  |       return nil | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     return string(b) | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  |   return nil | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func unstringify[T any, E interface { *T; encoding.TextUnmarshaler }](value interface{}) interface{} { | 
		
	
		
			
				|  |  |  |  |   str, ok := value.(string) | 
		
	
		
			
				|  |  |  |  |   if ok == false { | 
		
	
		
			
				|  |  |  |  |     return nil | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   var tmp E | 
		
	
		
			
				|  |  |  |  |   err := tmp.UnmarshalText([]byte(str)) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     return nil | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   return *tmp | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func unstringifyAST[T any, E interface { *T; encoding.TextUnmarshaler}](value ast.Value)interface{} { | 
		
	
		
			
				|  |  |  |  |   str, ok := value.(*ast.StringValue) | 
		
	
		
			
				|  |  |  |  |   if ok == false { | 
		
	
		
			
				|  |  |  |  |     return nil | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   var tmp E | 
		
	
		
			
				|  |  |  |  |   err := tmp.UnmarshalText([]byte(str.Value)) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     return nil | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   return *tmp | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func coerce[T any](value interface{}) interface{} { | 
		
	
		
			
				|  |  |  |  |   t := reflect.TypeFor[T]() | 
		
	
		
			
				|  |  |  |  |   if reflect.TypeOf(value).ConvertibleTo(t) { | 
		
	
		
			
				|  |  |  |  |     return value.(T) | 
		
	
		
			
				|  |  |  |  |   } else { | 
		
	
		
			
				|  |  |  |  |     return nil | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func astString[T ~string](value ast.Value) interface{} { | 
		
	
		
			
				|  |  |  |  |   str, ok := value.(*ast.StringValue) | 
		
	
		
			
				|  |  |  |  |   if ok == false { | 
		
	
		
			
				|  |  |  |  |     return nil | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   return T(str.Value) | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func astInt[T constraints.Integer](value ast.Value) interface{} { | 
		
	
		
			
				|  |  |  |  |   switch value := value.(type) { | 
		
	
		
			
				|  |  |  |  |   case *ast.BooleanValue: | 
		
	
		
			
				|  |  |  |  |     if value.Value { | 
		
	
		
			
				|  |  |  |  |       return T(1) | 
		
	
		
			
				|  |  |  |  |     } else { | 
		
	
		
			
				|  |  |  |  |       return T(0) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |   case *ast.StringValue: | 
		
	
		
			
				|  |  |  |  |     i, err := strconv.Atoi(value.Value) | 
		
	
		
			
				|  |  |  |  |     if err != nil { | 
		
	
		
			
				|  |  |  |  |       return nil | 
		
	
		
			
				|  |  |  |  |     } else { | 
		
	
		
			
				|  |  |  |  |       return T(i) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |   case *ast.IntValue: | 
		
	
		
			
				|  |  |  |  |     i, err := strconv.Atoi(value.Value) | 
		
	
		
			
				|  |  |  |  |     if err != nil { | 
		
	
		
			
				|  |  |  |  |       return nil | 
		
	
		
			
				|  |  |  |  |     } else { | 
		
	
		
			
				|  |  |  |  |       return T(i) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |   default: | 
		
	
		
			
				|  |  |  |  |     return nil | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func RegisterScalar[S any](ctx *Context, to_json func(interface{})interface{}, from_json func(interface{})interface{}, from_ast func(ast.Value)interface{}) error { | 
		
	
		
			
				|  |  |  |  |   reflect_type := reflect.TypeFor[S]() | 
		
	
		
			
				|  |  |  |  |   serialized_type := SerializedTypeFor[S]() | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   _, exists := ctx.TypeTypes[reflect_type] | 
		
	
		
			
				|  |  |  |  |   if exists { | 
		
	
	
		
			
				
					|  |  |  | @ -379,6 +552,61 @@ func NewContext(db * badger.DB, log Logger) (*Context, error) { | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   var err error | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   err = RegisterScalar[int](ctx, identity, coerce[int], astInt[int])  | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     return nil, err | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   err = RegisterScalar[uint8](ctx, identity, coerce[uint8], astInt[uint8]) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     return nil, err | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   err = RegisterScalar[time.Time](ctx, stringify, unstringify[time.Time], unstringifyAST[time.Time]) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     return nil, err | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  |    | 
		
	
		
			
				|  |  |  |  |   err = RegisterScalar[string](ctx, identity, coerce[string], astString[string]) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     return nil, err | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   err = RegisterScalar[EventState](ctx, identity, coerce[EventState], astString[EventState]) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     return nil, err | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   err = RegisterScalar[ReqState](ctx, identity, coerce[ReqState], astInt[ReqState])  | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     return nil, err | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   err = RegisterScalar[uuid.UUID](ctx, stringify, unstringify[uuid.UUID], unstringifyAST[uuid.UUID])  | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     return nil, err | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   err = RegisterScalar[NodeID](ctx, stringify, unstringify[NodeID], unstringifyAST[NodeID])  | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     return nil, err | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   err = RegisterScalar[WaitReason](ctx, identity, coerce[WaitReason], astString[WaitReason]) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     return nil, err | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   err = RegisterObject[WaitInfo](ctx) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     return nil, err | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   err = RegisterMap(ctx, reflect.TypeFor[WaitMap]()) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     return nil, err | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   err = RegisterExtension[ListenerExt](ctx, nil) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     return nil, err | 
		
	
	
		
			
				
					|  |  |  | 
 |