Skip to content

Commit 845f21b

Browse files
njlrxperiandri
andauthored
Relay Revamp (#484)
* 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>
1 parent 6f1dc93 commit 845f21b

File tree

31 files changed

+985
-5665
lines changed

31 files changed

+985
-5665
lines changed

FSharp.Data.GraphQL.sln

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "star-wars-api", "samples\st
4040
EndProject
4141
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "chat-app", "samples\chat-app\server\chat-app.fsproj", "{225B0790-C6B6-425C-9093-F359A4C635D3}"
4242
EndProject
43+
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "relay-book-store", "samples\relay-book-store\relay-book-store.fsproj", "{C24EB38E-326C-4770-BB20-9838694EE5E6}"
44+
EndProject
4345
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{BEFD8748-2467-45F9-A4AD-B450B12D5F78}"
4446
EndProject
4547
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Data.GraphQL.Shared", "src\FSharp.Data.GraphQL.Shared\FSharp.Data.GraphQL.Shared.fsproj", "{6768EA38-1335-4B8E-BC09-CCDED1F9AAF6}"
@@ -244,6 +246,30 @@ Global
244246
{554A6833-1E72-41B4-AAC1-C19371EC061B}.Release|x64.Build.0 = Release|Any CPU
245247
{554A6833-1E72-41B4-AAC1-C19371EC061B}.Release|x86.ActiveCfg = Release|Any CPU
246248
{554A6833-1E72-41B4-AAC1-C19371EC061B}.Release|x86.Build.0 = Release|Any CPU
249+
{0A83D100-DE1D-49A5-B6F7-CC78C7A15F80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
250+
{0A83D100-DE1D-49A5-B6F7-CC78C7A15F80}.Debug|Any CPU.Build.0 = Debug|Any CPU
251+
{0A83D100-DE1D-49A5-B6F7-CC78C7A15F80}.Debug|x64.ActiveCfg = Debug|Any CPU
252+
{0A83D100-DE1D-49A5-B6F7-CC78C7A15F80}.Debug|x64.Build.0 = Debug|Any CPU
253+
{0A83D100-DE1D-49A5-B6F7-CC78C7A15F80}.Debug|x86.ActiveCfg = Debug|Any CPU
254+
{0A83D100-DE1D-49A5-B6F7-CC78C7A15F80}.Debug|x86.Build.0 = Debug|Any CPU
255+
{0A83D100-DE1D-49A5-B6F7-CC78C7A15F80}.Release|Any CPU.ActiveCfg = Release|Any CPU
256+
{0A83D100-DE1D-49A5-B6F7-CC78C7A15F80}.Release|Any CPU.Build.0 = Release|Any CPU
257+
{0A83D100-DE1D-49A5-B6F7-CC78C7A15F80}.Release|x64.ActiveCfg = Release|Any CPU
258+
{0A83D100-DE1D-49A5-B6F7-CC78C7A15F80}.Release|x64.Build.0 = Release|Any CPU
259+
{0A83D100-DE1D-49A5-B6F7-CC78C7A15F80}.Release|x86.ActiveCfg = Release|Any CPU
260+
{0A83D100-DE1D-49A5-B6F7-CC78C7A15F80}.Release|x86.Build.0 = Release|Any CPU
261+
{7873CD7F-BCDD-47AE-9743-5D033DA84E3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
262+
{7873CD7F-BCDD-47AE-9743-5D033DA84E3E}.Debug|Any CPU.Build.0 = Debug|Any CPU
263+
{7873CD7F-BCDD-47AE-9743-5D033DA84E3E}.Debug|x64.ActiveCfg = Debug|Any CPU
264+
{7873CD7F-BCDD-47AE-9743-5D033DA84E3E}.Debug|x64.Build.0 = Debug|Any CPU
265+
{7873CD7F-BCDD-47AE-9743-5D033DA84E3E}.Debug|x86.ActiveCfg = Debug|Any CPU
266+
{7873CD7F-BCDD-47AE-9743-5D033DA84E3E}.Debug|x86.Build.0 = Debug|Any CPU
267+
{7873CD7F-BCDD-47AE-9743-5D033DA84E3E}.Release|Any CPU.ActiveCfg = Release|Any CPU
268+
{7873CD7F-BCDD-47AE-9743-5D033DA84E3E}.Release|Any CPU.Build.0 = Release|Any CPU
269+
{7873CD7F-BCDD-47AE-9743-5D033DA84E3E}.Release|x64.ActiveCfg = Release|Any CPU
270+
{7873CD7F-BCDD-47AE-9743-5D033DA84E3E}.Release|x64.Build.0 = Release|Any CPU
271+
{7873CD7F-BCDD-47AE-9743-5D033DA84E3E}.Release|x86.ActiveCfg = Release|Any CPU
272+
{7873CD7F-BCDD-47AE-9743-5D033DA84E3E}.Release|x86.Build.0 = Release|Any CPU
247273
{E011A3B2-3D96-48E3-AF5F-DA544FF5C5FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
248274
{E011A3B2-3D96-48E3-AF5F-DA544FF5C5FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
249275
{E011A3B2-3D96-48E3-AF5F-DA544FF5C5FE}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -316,30 +342,18 @@ Global
316342
{A6A162DF-9FBB-4C2A-913F-FD5FED35A09B}.Release|x64.Build.0 = Release|Any CPU
317343
{A6A162DF-9FBB-4C2A-913F-FD5FED35A09B}.Release|x86.ActiveCfg = Release|Any CPU
318344
{A6A162DF-9FBB-4C2A-913F-FD5FED35A09B}.Release|x86.Build.0 = Release|Any CPU
319-
{0A83D100-DE1D-49A5-B6F7-CC78C7A15F80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
320-
{0A83D100-DE1D-49A5-B6F7-CC78C7A15F80}.Debug|Any CPU.Build.0 = Debug|Any CPU
321-
{0A83D100-DE1D-49A5-B6F7-CC78C7A15F80}.Debug|x64.ActiveCfg = Debug|Any CPU
322-
{0A83D100-DE1D-49A5-B6F7-CC78C7A15F80}.Debug|x64.Build.0 = Debug|Any CPU
323-
{0A83D100-DE1D-49A5-B6F7-CC78C7A15F80}.Debug|x86.ActiveCfg = Debug|Any CPU
324-
{0A83D100-DE1D-49A5-B6F7-CC78C7A15F80}.Debug|x86.Build.0 = Debug|Any CPU
325-
{0A83D100-DE1D-49A5-B6F7-CC78C7A15F80}.Release|Any CPU.ActiveCfg = Release|Any CPU
326-
{0A83D100-DE1D-49A5-B6F7-CC78C7A15F80}.Release|Any CPU.Build.0 = Release|Any CPU
327-
{0A83D100-DE1D-49A5-B6F7-CC78C7A15F80}.Release|x64.ActiveCfg = Release|Any CPU
328-
{0A83D100-DE1D-49A5-B6F7-CC78C7A15F80}.Release|x64.Build.0 = Release|Any CPU
329-
{0A83D100-DE1D-49A5-B6F7-CC78C7A15F80}.Release|x86.ActiveCfg = Release|Any CPU
330-
{0A83D100-DE1D-49A5-B6F7-CC78C7A15F80}.Release|x86.Build.0 = Release|Any CPU
331-
{7873CD7F-BCDD-47AE-9743-5D033DA84E3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
332-
{7873CD7F-BCDD-47AE-9743-5D033DA84E3E}.Debug|Any CPU.Build.0 = Debug|Any CPU
333-
{7873CD7F-BCDD-47AE-9743-5D033DA84E3E}.Debug|x64.ActiveCfg = Debug|Any CPU
334-
{7873CD7F-BCDD-47AE-9743-5D033DA84E3E}.Debug|x64.Build.0 = Debug|Any CPU
335-
{7873CD7F-BCDD-47AE-9743-5D033DA84E3E}.Debug|x86.ActiveCfg = Debug|Any CPU
336-
{7873CD7F-BCDD-47AE-9743-5D033DA84E3E}.Debug|x86.Build.0 = Debug|Any CPU
337-
{7873CD7F-BCDD-47AE-9743-5D033DA84E3E}.Release|Any CPU.ActiveCfg = Release|Any CPU
338-
{7873CD7F-BCDD-47AE-9743-5D033DA84E3E}.Release|Any CPU.Build.0 = Release|Any CPU
339-
{7873CD7F-BCDD-47AE-9743-5D033DA84E3E}.Release|x64.ActiveCfg = Release|Any CPU
340-
{7873CD7F-BCDD-47AE-9743-5D033DA84E3E}.Release|x64.Build.0 = Release|Any CPU
341-
{7873CD7F-BCDD-47AE-9743-5D033DA84E3E}.Release|x86.ActiveCfg = Release|Any CPU
342-
{7873CD7F-BCDD-47AE-9743-5D033DA84E3E}.Release|x86.Build.0 = Release|Any CPU
345+
{C24EB38E-326C-4770-BB20-9838694EE5E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
346+
{C24EB38E-326C-4770-BB20-9838694EE5E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
347+
{C24EB38E-326C-4770-BB20-9838694EE5E6}.Debug|x64.ActiveCfg = Debug|Any CPU
348+
{C24EB38E-326C-4770-BB20-9838694EE5E6}.Debug|x64.Build.0 = Debug|Any CPU
349+
{C24EB38E-326C-4770-BB20-9838694EE5E6}.Debug|x86.ActiveCfg = Debug|Any CPU
350+
{C24EB38E-326C-4770-BB20-9838694EE5E6}.Debug|x86.Build.0 = Debug|Any CPU
351+
{C24EB38E-326C-4770-BB20-9838694EE5E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
352+
{C24EB38E-326C-4770-BB20-9838694EE5E6}.Release|Any CPU.Build.0 = Release|Any CPU
353+
{C24EB38E-326C-4770-BB20-9838694EE5E6}.Release|x64.ActiveCfg = Release|Any CPU
354+
{C24EB38E-326C-4770-BB20-9838694EE5E6}.Release|x64.Build.0 = Release|Any CPU
355+
{C24EB38E-326C-4770-BB20-9838694EE5E6}.Release|x86.ActiveCfg = Release|Any CPU
356+
{C24EB38E-326C-4770-BB20-9838694EE5E6}.Release|x86.Build.0 = Release|Any CPU
343357
EndGlobalSection
344358
GlobalSection(SolutionProperties) = preSolution
345359
HideSolutionNode = FALSE
@@ -352,6 +366,8 @@ Global
352366
{6768EA38-1335-4B8E-BC09-CCDED1F9AAF6} = {BEFD8748-2467-45F9-A4AD-B450B12D5F78}
353367
{474179D3-0090-49E9-88F8-2971C0966077} = {BEFD8748-2467-45F9-A4AD-B450B12D5F78}
354368
{554A6833-1E72-41B4-AAC1-C19371EC061B} = {BEFD8748-2467-45F9-A4AD-B450B12D5F78}
369+
{0A83D100-DE1D-49A5-B6F7-CC78C7A15F80} = {BEFD8748-2467-45F9-A4AD-B450B12D5F78}
370+
{7873CD7F-BCDD-47AE-9743-5D033DA84E3E} = {BEFD8748-2467-45F9-A4AD-B450B12D5F78}
355371
{E011A3B2-3D96-48E3-AF5F-DA544FF5C5FE} = {BEFD8748-2467-45F9-A4AD-B450B12D5F78}
356372
{8FB23F61-77CB-42C7-8EEC-B22D7C4E4067} = {BEFD8748-2467-45F9-A4AD-B450B12D5F78}
357373
{B075CD55-CEA4-4C30-A088-48319AADF070} = {BEFD8748-2467-45F9-A4AD-B450B12D5F78}
@@ -369,8 +385,7 @@ Global
369385
{A8F031E0-2BD5-4BAE-830A-60CBA76A047D} = {600D4BE2-FCE0-4684-AC6F-2DC829B395BA}
370386
{6EEA0E79-693F-4D4F-B55B-DB0C64EBDA45} = {600D4BE2-FCE0-4684-AC6F-2DC829B395BA}
371387
{7AA3516E-60F5-4969-878F-4E3DCF3E63A3} = {A8F031E0-2BD5-4BAE-830A-60CBA76A047D}
372-
{0A83D100-DE1D-49A5-B6F7-CC78C7A15F80} = {BEFD8748-2467-45F9-A4AD-B450B12D5F78}
373-
{7873CD7F-BCDD-47AE-9743-5D033DA84E3E} = {BEFD8748-2467-45F9-A4AD-B450B12D5F78}
388+
{C24EB38E-326C-4770-BB20-9838694EE5E6} = {B0C25450-74BF-40C2-9E02-09AADBAE2C2F}
374389
EndGlobalSection
375390
GlobalSection(ExtensibilityGlobals) = postSolution
376391
SolutionGuid = {C5B9895C-9DF8-4557-8D44-7D0C4C31F86E}

Packages.props

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
<PackageReference Update="System.Management" Version="6.*" />
3131
<PackageReference Update="System.Reactive" Version="6.*" NoWarn="NU1608" />
3232
<PackageReference Update="System.Text.Json" Version="$(SystemVersion)" />
33+
<PackageReference Update="System.Text.RegularExpressions" Version="4.3.1" />
3334
</ItemGroup>
3435
<ItemGroup Label="Build">
3536
<PackageReference Update="BlackFox.VsWhere" Version="1.1.0" />
@@ -67,8 +68,10 @@
6768
</ItemGroup>
6869
<ItemGroup Label="Tests and Samples">
6970
<PackageReference Update="CommandLineParser" Version="2.9.*" />
71+
<PackageReference Update="Donald" Version="10.1.0" />
7072
<PackageReference Update="EntityFramework" Version="1.*" />
7173
<PackageReference Update="FSharp.Data.TypeProviders" Version="1.*" />
74+
<PackageReference Update="FsToolkit.ErrorHandling" Version="4.15.3" />
7275
<PackageReference Update="Giraffe" Version="6.*" />
7376
<PackageReference Update="GraphQL.Server.Ui.Altair" Version="7.*" />
7477
<PackageReference Update="GraphQL.Server.Ui.GraphiQL" Version="7.*" />
@@ -77,11 +80,14 @@
7780
<PackageReference Update="HotChocolate.AspNetCore" Version="13.*" />
7881
<PackageReference Update="Iced" Version="1.17.*" />
7982
<PackageReference Update="Microsoft.CodeCoverage" Version="17.3.*" />
83+
<PackageReference Update="Microsoft.Data.Sqlite" Version="8.0.6" />
8084
<PackageReference Update="Microsoft.Diagnostics.NETCore.Client" Version="0.2.*" />
8185
<PackageReference Update="Microsoft.Diagnostics.Runtime" Version="2.2.*" />
8286
<PackageReference Update="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.*" />
8387
<PackageReference Update="Microsoft.NETCore.Platforms" Version="6.0.*" />
8488
<PackageReference Update="Newtonsoft.Json" Version="13.*" />
89+
<PackageReference Update="System.Data.Common" Version="4.3.0" />
90+
<PackageReference Update="Thoth.Json.Net" Version="12.0.0" />
8591
<PackageReference Update="Validus" Version="4.1.*" />
8692
</ItemGroup>
8793
</Project>

README.md

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ type Person =
2727
{ FirstName: string
2828
LastName: string }
2929
30-
// Define GraphQL type
30+
// Define GraphQL type
3131
let PersonType = Define.Object(
3232
name = "Person",
3333
fields = [
@@ -45,7 +45,7 @@ let executor = Executor(schema)
4545
// Retrieve person data
4646
let johnSnow = { FirstName = "John"; LastName = "Snow" }
4747
let reply = executor.AsyncExecute(Parser.parse "{ firstName, lastName }", johnSnow) |> Async.RunSynchronously
48-
// #> { data: { "firstName", "John", "lastName", "Snow" } }
48+
// #> { data: { "firstName", "John", "lastName", "Snow" } }
4949
```
5050

5151
It's type safe. Things like invalid fields or invalid return types will be checked at compile time.
@@ -79,14 +79,6 @@ Go to the [GraphiQL sample directory](https://github.com/bazingatechnologies/FSh
7979
}
8080
```
8181

82-
### Relay.js starter kit
83-
84-
A [second sample](https://github.com/bazingatechnologies/FSharp.Data.GraphQL/tree/dev/samples/relay-starter-kit) is a F#-backed version of of popular Relay Starter Kit - an example application using React.js + Relay with Relay-compatible server API.
85-
86-
To run it, build `FSharp.Data.GraphQL` and `FSharp.Data.GraphQL.Relay` projects using Debug settings. Then start server by running `server.fsx` script in your FSI - this will start a relay-compatible F# server on port 8083. Then build node.js frontend by getting all dependencies (`npm i`) and running it (`npm run serve | npm run dev`) - this will start webpack server running React application using Relay for managing application state. You can visit it on [http://localhost:8083/](http://localhost:8083/) .
87-
88-
In order to update client schema, visit [http://localhost:8083/](http://localhost:8083/) and copy-paste the response (which is the introspection query result from the current F# server) into *data/schema.json*.
89-
9082
## Stream features
9183

9284
The `stream` directive now has additional features, like batching (buffering) by interval and/or batch size. To make it work, a custom stream directive must be placed inside the `SchemaConfig.Directives` list, this custom directive containing two optional arguments called `interval` and `preferredBatchSize`:
@@ -192,7 +184,7 @@ type MyProvider = GraphQLProvider<"swapi_schema.json">
192184
From now on, you can start running queries and mutations:
193185

194186
```fsharp
195-
let operation =
187+
let operation =
196188
MyProvider.Operation<"""query q {
197189
hero (id: "1001") {
198190
name
@@ -393,7 +385,7 @@ And the value recovered by the filter in the query is usable in the `ResolveFiel
393385

394386
```fsharp
395387
Define.Field("friends", ListOf (Nullable CharacterType),
396-
resolve = fun ctx (d : Droid) ->
388+
resolve = fun ctx (d : Droid) ->
397389
ctx.Filter |> printfn "Droid friends filter: %A"
398390
d.Friends |> List.map getCharacter |> List.toSeq)
399391
```

samples/relay-book-store/DB.fs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
module FSharp.Data.GraphQL.Samples.RelayBookStore.DB
2+
3+
open System.Data
4+
open System.Data.Common
5+
open Donald
6+
7+
let fetchBooksTotalCount (db : IDbConnection) = async {
8+
let sql = "SELECT COUNT(id) AS n FROM books"
9+
10+
let! ct = Async.CancellationToken
11+
12+
let! count =
13+
db
14+
|> Db.newCommand sql
15+
|> Db.setCancellationToken ct
16+
|> Db.Async.querySingle (fun read -> read.ReadInt32 ("n"))
17+
|> Async.AwaitTask
18+
19+
return Option.get count
20+
}
21+
22+
let private readBook (read : DbDataReader) : Book = {
23+
ID = read.ReadString ("id")
24+
Title = read.ReadString ("title")
25+
Year = read.ReadInt32 ("year")
26+
}
27+
28+
let tryFetchBook (id : string) (db : IDbConnection) = async {
29+
let sql = "SELECT * FROM books WHERE id = @id LIMIT 1"
30+
31+
let parameters = [ "id", SqlType.String id ]
32+
33+
let! ct = Async.CancellationToken
34+
35+
let! maybeBook =
36+
db
37+
|> Db.newCommand sql
38+
|> Db.setParams parameters
39+
|> Db.setCancellationToken ct
40+
|> Db.Async.querySingle readBook
41+
|> Async.AwaitTask
42+
43+
return maybeBook
44+
}
45+
46+
let fetchBooksPage (maybeCursor : BookCursor voption) (isCursorInclusive : bool) (isForward : bool) (limit : int) (db : IDbConnection) =
47+
if limit < 0 then
48+
invalidArg (nameof limit) "must be non-negative"
49+
50+
async {
51+
let whereClause =
52+
match maybeCursor with
53+
| ValueSome _ ->
54+
if isForward then
55+
if isCursorInclusive then
56+
"WHERE title > @cursor_title OR (title = @cursor_title AND id >= @cursor_id)"
57+
else
58+
"WHERE title > @cursor_title OR (title = @cursor_title AND id > @cursor_id)"
59+
else if isCursorInclusive then
60+
"WHERE title < @cursor_title OR (title = @cursor_title AND id <= @cursor_id)"
61+
else
62+
"WHERE title < @cursor_title OR (title = @cursor_title AND id < @cursor_id)"
63+
| ValueNone -> ""
64+
65+
let orderByClause =
66+
if isForward then
67+
"ORDER BY title ASC, id ASC"
68+
else
69+
"ORDER BY title DESC, id DESC"
70+
71+
let sql =
72+
$"""SELECT *
73+
FROM books
74+
%s{whereClause}
75+
%s{orderByClause}
76+
LIMIT %i{limit}"""
77+
78+
let parameters = [
79+
match maybeCursor with
80+
| ValueSome cursor ->
81+
"cursor_id", SqlType.String cursor.ID
82+
"cursor_title", SqlType.String cursor.Title
83+
| ValueNone -> ()
84+
]
85+
86+
let! ct = Async.CancellationToken
87+
88+
let! records =
89+
db
90+
|> Db.newCommand sql
91+
|> Db.setParams parameters
92+
|> Db.setCancellationToken ct
93+
|> Db.Async.query readBook
94+
|> Async.AwaitTask
95+
96+
return records
97+
}

samples/relay-book-store/Domain.fs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
namespace FSharp.Data.GraphQL.Samples.RelayBookStore
2+
3+
type Book = { ID : string; Title : string; Year : int }
4+
5+
type BookCursor = { ID : string; Title : string }
6+
7+
[<RequireQualifiedAccess>]
8+
module BookCursor =
9+
10+
open FsToolkit.ErrorHandling
11+
open Thoth.Json.Net
12+
13+
let ofBook (x : Book) : BookCursor = { ID = x.ID; Title = x.Title }
14+
15+
let private encoder = fun x -> Encode.object [ "i", Encode.string x.ID; "t", Encode.string x.Title ]
16+
17+
let private decoder =
18+
Decode.object (fun get -> {
19+
ID = get.Required.Field "i" Decode.string
20+
Title = get.Required.Field "t" Decode.string
21+
})
22+
23+
let tryDecode (x : string) : BookCursor option = option {
24+
let! bytes = Base64.tryDecode x
25+
let! json = Utf8.tryDecode bytes
26+
27+
return! Decode.fromString decoder json |> Result.toOption
28+
}
29+
30+
let encode (x : BookCursor) : string =
31+
x
32+
|> encoder
33+
|> Encode.toString 0
34+
|> Utf8.encode
35+
|> Base64.encode
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#r "nuget: Microsoft.Data.Sqlite, 8.0.6"
2+
#r "nuget: Donald, 10.1.0"
3+
4+
open Microsoft.Data.Sqlite
5+
open Donald
6+
7+
let slugify (x : string) = x.Replace(' ', '-').Replace(''', '-').ToLowerInvariant ()
8+
9+
let books = [
10+
"Accelerando", 2005
11+
"Consider Phlebas", 1987
12+
"Dune", 1965
13+
"Ender's Game", 1985
14+
"Gateway", 1977
15+
"Interface", 1994
16+
"Jurrasic Park", 1990
17+
"Roadside Picnic", 1972
18+
"Stand on Zanzibar", 1968
19+
"The Sheep Look Up", 1972
20+
"The Mountain Trail and its Message", 1997
21+
"We", 1924
22+
]
23+
24+
let db = new SqliteConnection ("Data Source=app.db")
25+
26+
db
27+
|> Db.newCommand "CREATE TABLE books (id PRIMARY KEY, title, year); "
28+
|> Db.exec
29+
30+
db
31+
|> Db.newCommand "INSERT INTO books (id, title, year) VALUES (@id, @title, @year)"
32+
|> Db.execMany [
33+
for book in books do
34+
let title, year = book
35+
let id = slugify title
36+
[ "id", SqlType.String id; "title", SqlType.String title; "year", SqlType.Int year ]
37+
]

0 commit comments

Comments
 (0)