在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
开源软件名称(OpenSource Name):fsprojects/FSharp.Data.GraphQL开源软件地址(OpenSource Url):https://github.com/fsprojects/FSharp.Data.GraphQL开源编程语言(OpenSource Language):F# 99.2%开源软件介绍(OpenSource Introduction):FSharp.Data.GraphQLF# implementation of Facebook GraphQL query language specification. Quick startopen FSharp.Data.GraphQL
open FSharp.Data.GraphQL.Types
type Person =
{ FirstName: string
LastName: string }
// Define GraphQL type
let PersonType = Define.Object(
name = "Person",
fields = [
// Property resolver will be auto-generated
Define.AutoField("firstName", String)
// Asynchronous explicit member resolver
Define.AsyncField("lastName", String, resolve = fun context person -> async { return person.LastName })
])
// Include person as a root query of a schema
let schema = Schema(query = PersonType)
// Create an Exector for the schema
let executor = Executor(schema)
// Retrieve person data
let johnSnow = { FirstName = "John"; LastName = "Snow" }
let reply = executor.AsyncExecute(Parser.parse "{ firstName, lastName }", johnSnow) |> Async.RunSynchronously
// #> { data: { "firstName", "John", "lastName", "Snow" } } It's type safe. Things like invalid fields or invalid return types will be checked at compile time. DemosGraphiQL clientGo to the GraphiQL sample directory. In order to run it, build and run the Star Wars API sample project with Debug settings - this will create a Giraffe server compatible with the GraphQL spec, running on port 8086. Then what you need is to run node.js graphiql frontend. To do so, run {
hero(id:"1000") {
id,
name,
appearsIn,
homePlanet,
friends {
... on Human {
name
}
... on Droid {
name
}
}
}
} Relay.js starter kitA second sample is a F#-backed version of of popular Relay Starter Kit - an example application using React.js + Relay with Relay-compatible server API. To run it, build In order to update client schema, visit http://localhost:8083/ and copy-paste the response (which is the introspection query result from the current F# server) into data/schema.json. Stream featuresThe let customStreamDirective =
let args = [|
Define.Input(
"interval",
Nullable Int,
defaultValue = Some 2000,
description = "An optional argument used to buffer stream results. ")
Define.Input(
"preferredBatchSize",
Nullable Int,
defaultValue = None,
description = "An optional argument used to buffer stream results. ") |]
{ StreamDirective with Args = args }
let schemaConfig =
{ SchemaConfig.Default with
Directives = [
IncludeDirective
SkipDirective
DeferDirective
customStreamDirective
LiveDirective ] } This boilerplate code can be easily reduced with a built-in implementation: let streamOptions =
{ Interval = Some 2000; PreferredBatchSize = None }
let schemaConfig =
SchemaConfig.DefaultWithBufferedStream(streamOptions) Live queriesThe type ILiveFieldSubscription =
interface
abstract member Identity : obj -> obj
abstract member TypeName : string
abstract member FieldName : string
end
and ILiveFieldSubscription<'Object, 'Identity> =
interface
inherit ILiveFieldSubscription
abstract member Identity : 'Object -> 'Identity
end
and ILiveFieldSubscriptionProvider =
interface
abstract member HasSubscribers : string -> string -> bool
abstract member IsRegistered : string -> string -> bool
abstract member AsyncRegister : ILiveFieldSubscription -> Async<unit>
abstract member TryFind : string -> string -> ILiveFieldSubscription option
abstract member Add : obj -> string -> string -> IObservable<obj>
abstract member AsyncPublish<'T> : string -> string -> 'T -> Async<unit>
end To set a field as a live field, call the let schemaConfig = SchemaConfig.Default
let schema = Schema(root, config = schemaConfig)
let subscription =
{ Identity = fun (x : Human) -> x.Id
TypeName = "Hero"
FieldName = "name" }
schemaConfig.LiveFieldSubscriptionProvider.Register subscription With that, the field name of the hero is now able to go live, being updated to clients whenever it is queried with the let updatedHero = { hero with Name = "Han Solo - Test" }
schemaConfig.LiveFieldSubscriptionProvider.Publish "Hero" "name" updatedHero Client ProviderOur client library now has a completely redesigned type provider. To start using it, you will first need access to the introspection schema for the server you are trying to connect. This can be done with the provider in one of two ways:
type MyProvider = GraphQLProvider<"http://some.graphqlserver.development.org">
type MyProvider = GraphQLProvider<"swapi_schema.json"> From now on, you can start running queries and mutations: let operation =
MyProvider.Operation<"""query q {
hero (id: "1001") {
name
appearsIn
homePlanet
friends {
... on Human {
name
homePlanet
}
... on Droid {
name
primaryFunction
}
}
}
}""">()
// This is a instance of GraphQLProviderRuntimeContext.
// You can use it to provider a runtime URL to access your server,
// and optionally additional HTTP headers (auth headers, for example).
// If you use a local introspection file to parse the schema,
// The runtime context is mandatory.
let runtimeContext =
{ ServerUrl = "http://some.graphqlserver.production.org"
CustomHttpHeaders = None }
let result = operation.Run(runtimeContext)
// Query result objects have pretty-printing and structural equality.
printfn "Data: %A\n" result.Data
printfn "Errors: %A\n" result.Errors
printfn "Custom data: %A\n" result.CustomData
// Response from the server:
// Data: Some
// {Hero = Some
// {Name = Some "Darth Vader";
// AppearsIn = [|NewHope; Empire; Jedi|];
// HomePlanet = Some "Tatooine";
// Friends = [|Some {Name = Some "Wilhuff Tarkin";
// HomePlanet = <null>;}|];};}
// Errors: <null>
// Custom data: map [("documentId", 1221427401)] For more information about how to use the client provider, see the examples folder. MiddlewareYou can create and use middleware on top of the The query execution process through the use of the Executor involves three phases:
All the phases wrap the needed data to do the phase job inside a context object. They are expressed internally by functions: let internal compileSchema (ctx : SchemaCompileContext) : unit =
// ...
let internal planOperation (ctx: PlanningContext) : ExecutionPlan =
// ...
let internal executeOperation (ctx : ExecutionContext) : AsyncVal<GQLResponse> =
// ... That way, in the compile schema phase, the schema is modified and execution maps are generated inside the With that being said, a middleware can be used to intercept each phase and customize them as necessary. Each middleware must be implemented as a function with a specific signature, and wrapped inside an type SchemaCompileMiddleware =
SchemaCompileContext -> (SchemaCompileContext -> unit) -> unit
type OperationPlanningMiddleware =
PlanningContext -> (PlanningContext -> ExecutionPlan) -> ExecutionPlan
type OperationExecutionMiddleware =
ExecutionContext -> (ExecutionContext -> AsyncVal<GQLResponse>) -> AsyncVal<GQLResponse>
type IExecutorMiddleware =
abstract CompileSchema : SchemaCompileMiddleware option
abstract PlanOperation : OperationPlanningMiddleware option
abstract ExecuteOperationAsync : OperationExecutionMiddleware option Optionally, for ease of implementation, concrete class to derive from can be used, receiving only the optional sub-middleware functions in the constructor: type ExecutorMiddleware(?compile, ?plan, ?execute) =
interface IExecutorMiddleware with
member __.CompileSchema = compile
member __.PlanOperation = plan
member __.ExecuteOperationAsync = execute Each of the middleware functions act like an intercept function, with two parameters: the context of the phase, the function of the next middleware (or the actual phase itself, wich is the last to run), and the return value. Those functions can be passed as an argument to the constructor of the let middleware = [ ExecutorMiddleware(compileFn, planningFn, executionFn) ]
let executor = Executor(schema, middleware) A simple example of a practical middleware can be one that measures the time needed to plan a query. The results of which get returned as part of the let planningMiddleware (ctx : PlanningContext) (next : PlanningContext -> ExecutionPlan) =
let watch = Stopwatch()
watch.Start()
let result = next ctx
watch.Stop()
let metadata = result.Metadata.Add("planningTime", watch.ElapsedMilliseconds)
{ result with Metadata = metadata } Built-in middlewareThere are some built-in middleware inside QueryWeightMiddlewareThis middleware can be used to place weights on fields of the schema. Those weightened fields can now be used to protect the server from complex queries that could otherwise be used in DDOS attacks. When defining a field, we use the extension method let resolveFn (h : Human) =
h.Friends |> List.map getCharacter |> List.toSeq
let field =
Define.Field("friends", ListOf (Nullable CharacterType),
resolve = resolveFn).WithQueryWeight(0.5) Then we define the threshold middleware for the Executor. If we execute a query that ask for "friends of friends" in a recursive way, the executor will only accept nesting them 4 times before the query exceeds the weight threshold of 2.0: let middleware = [ Define.QueryWeightMiddleware(2.0) ] ObjectListFilterMiddlewareThis middleware can be used to automatically generate a filter for list fields inside an object of the schema. This filter can be passed as an argument for the field on the query, and recovered in the For example, we can create a middleware for filtering list fields of an let middleware = [ Define.ObjectListFilterMiddleware<Human, Character option>() ] The filter argument is an object that is mapped through a JSON definition inside an query TestQuery {
hero(id:"1000") {
id
name
appearsIn
homePlanet
friends (filter : { name_starts_with: "A" }) {
id
name
}
}
} This filter is mapped by the middleware inside an type FieldFilter<'Val> =
{ FieldName : string
Value : 'Val }
type ObjectListFilter =
| And of ObjectListFilter * ObjectListFilter
| Or of ObjectListFilter * ObjectListFilter
| Not of ObjectListFilter
| Equals of FieldFilter<System.IComparable>
| GreaterThan of FieldFilter<System.IComparable>
| LessThan of FieldFilter<System.IComparable>
| StartsWith of FieldFilter<string>
| EndsWith of FieldFilter<string>
| Contains of FieldFilter<string>
| FilterField of FieldFilter<ObjectListFilter> And the value recovered by the filter in the query is usable in the Define.Field("friends", ListOf (Nullable CharacterType),
resolve = fun ctx (d : Droid) ->
ctx.Filter |> printfn "Droid friends filter: %A"
d.Friends |> List.map getCharacter |> List.toSeq) By retrieving this filter from the field resolution context, it is possible to use client code to customize the query to run against a database, for example, and extend your GraphQL API features. LiveQueryMiddlewareThis middleware can be used to quickly allow your schema fields to be able to be queried with a /// A function that resolves an identity name for a schema object, based on a object definition of it.
type IdentityNameResolver = ObjectDef -> string For example, if all of our schema objects have an identity field named let schema = Schema(query = queryType)
let middleware = [ Define.LiveQueryMiddleware(fun _ -> "Id") ]
let executor = Executor(schema, middleware) The Using extensions to build your own middlewareYou can use extension methods provided by the As field definitions are immutable by default, generating copies of them with improved features can be hard work sometimes. This is where the extension methods can help: for example, if you need to add an argument to an already defined field inside the schema compile phase, you can use the method let field : FieldDef<'Val> = // Search for field inside ISchema
let arg : Define.Input("id", String)
let fieldWithArg = field.WithArgs([ arg ]) To see the complete list of extensions used to augment definitions, you can take a look at the |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论