-
Notifications
You must be signed in to change notification settings - Fork 75
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Rework Relay types to enable async fetching * Add a relay sample that fetches data efficiently from SQLite * Remove Suave * Bump package version * Fix StarWars sample * Switch Relay to ValueOption * Fixed `System.Text.RegularExpressions` reference vulnerability --------- Co-authored-by: Andrii Chebukin <XperiAndri@Outlook.com>
- Loading branch information
1 parent
6f1dc93
commit 845f21b
Showing
31 changed files
with
985 additions
and
5,665 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
module FSharp.Data.GraphQL.Samples.RelayBookStore.DB | ||
|
||
open System.Data | ||
open System.Data.Common | ||
open Donald | ||
|
||
let fetchBooksTotalCount (db : IDbConnection) = async { | ||
let sql = "SELECT COUNT(id) AS n FROM books" | ||
|
||
let! ct = Async.CancellationToken | ||
|
||
let! count = | ||
db | ||
|> Db.newCommand sql | ||
|> Db.setCancellationToken ct | ||
|> Db.Async.querySingle (fun read -> read.ReadInt32 ("n")) | ||
|> Async.AwaitTask | ||
|
||
return Option.get count | ||
} | ||
|
||
let private readBook (read : DbDataReader) : Book = { | ||
ID = read.ReadString ("id") | ||
Title = read.ReadString ("title") | ||
Year = read.ReadInt32 ("year") | ||
} | ||
|
||
let tryFetchBook (id : string) (db : IDbConnection) = async { | ||
let sql = "SELECT * FROM books WHERE id = @id LIMIT 1" | ||
|
||
let parameters = [ "id", SqlType.String id ] | ||
|
||
let! ct = Async.CancellationToken | ||
|
||
let! maybeBook = | ||
db | ||
|> Db.newCommand sql | ||
|> Db.setParams parameters | ||
|> Db.setCancellationToken ct | ||
|> Db.Async.querySingle readBook | ||
|> Async.AwaitTask | ||
|
||
return maybeBook | ||
} | ||
|
||
let fetchBooksPage (maybeCursor : BookCursor voption) (isCursorInclusive : bool) (isForward : bool) (limit : int) (db : IDbConnection) = | ||
if limit < 0 then | ||
invalidArg (nameof limit) "must be non-negative" | ||
|
||
async { | ||
let whereClause = | ||
match maybeCursor with | ||
| ValueSome _ -> | ||
if isForward then | ||
if isCursorInclusive then | ||
"WHERE title > @cursor_title OR (title = @cursor_title AND id >= @cursor_id)" | ||
else | ||
"WHERE title > @cursor_title OR (title = @cursor_title AND id > @cursor_id)" | ||
else if isCursorInclusive then | ||
"WHERE title < @cursor_title OR (title = @cursor_title AND id <= @cursor_id)" | ||
else | ||
"WHERE title < @cursor_title OR (title = @cursor_title AND id < @cursor_id)" | ||
| ValueNone -> "" | ||
|
||
let orderByClause = | ||
if isForward then | ||
"ORDER BY title ASC, id ASC" | ||
else | ||
"ORDER BY title DESC, id DESC" | ||
|
||
let sql = | ||
$"""SELECT * | ||
FROM books | ||
%s{whereClause} | ||
%s{orderByClause} | ||
LIMIT %i{limit}""" | ||
|
||
let parameters = [ | ||
match maybeCursor with | ||
| ValueSome cursor -> | ||
"cursor_id", SqlType.String cursor.ID | ||
"cursor_title", SqlType.String cursor.Title | ||
| ValueNone -> () | ||
] | ||
|
||
let! ct = Async.CancellationToken | ||
|
||
let! records = | ||
db | ||
|> Db.newCommand sql | ||
|> Db.setParams parameters | ||
|> Db.setCancellationToken ct | ||
|> Db.Async.query readBook | ||
|> Async.AwaitTask | ||
|
||
return records | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
namespace FSharp.Data.GraphQL.Samples.RelayBookStore | ||
|
||
type Book = { ID : string; Title : string; Year : int } | ||
|
||
type BookCursor = { ID : string; Title : string } | ||
|
||
[<RequireQualifiedAccess>] | ||
module BookCursor = | ||
|
||
open FsToolkit.ErrorHandling | ||
open Thoth.Json.Net | ||
|
||
let ofBook (x : Book) : BookCursor = { ID = x.ID; Title = x.Title } | ||
|
||
let private encoder = fun x -> Encode.object [ "i", Encode.string x.ID; "t", Encode.string x.Title ] | ||
|
||
let private decoder = | ||
Decode.object (fun get -> { | ||
ID = get.Required.Field "i" Decode.string | ||
Title = get.Required.Field "t" Decode.string | ||
}) | ||
|
||
let tryDecode (x : string) : BookCursor option = option { | ||
let! bytes = Base64.tryDecode x | ||
let! json = Utf8.tryDecode bytes | ||
|
||
return! Decode.fromString decoder json |> Result.toOption | ||
} | ||
|
||
let encode (x : BookCursor) : string = | ||
x | ||
|> encoder | ||
|> Encode.toString 0 | ||
|> Utf8.encode | ||
|> Base64.encode |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
#r "nuget: Microsoft.Data.Sqlite, 8.0.6" | ||
#r "nuget: Donald, 10.1.0" | ||
|
||
open Microsoft.Data.Sqlite | ||
open Donald | ||
|
||
let slugify (x : string) = x.Replace(' ', '-').Replace(''', '-').ToLowerInvariant () | ||
|
||
let books = [ | ||
"Accelerando", 2005 | ||
"Consider Phlebas", 1987 | ||
"Dune", 1965 | ||
"Ender's Game", 1985 | ||
"Gateway", 1977 | ||
"Interface", 1994 | ||
"Jurrasic Park", 1990 | ||
"Roadside Picnic", 1972 | ||
"Stand on Zanzibar", 1968 | ||
"The Sheep Look Up", 1972 | ||
"The Mountain Trail and its Message", 1997 | ||
"We", 1924 | ||
] | ||
|
||
let db = new SqliteConnection ("Data Source=app.db") | ||
|
||
db | ||
|> Db.newCommand "CREATE TABLE books (id PRIMARY KEY, title, year); " | ||
|> Db.exec | ||
|
||
db | ||
|> Db.newCommand "INSERT INTO books (id, title, year) VALUES (@id, @title, @year)" | ||
|> Db.execMany [ | ||
for book in books do | ||
let title, year = book | ||
let id = slugify title | ||
[ "id", SqlType.String id; "title", SqlType.String title; "year", SqlType.Int year ] | ||
] |
Oops, something went wrong.