Skip to content

D1

Use SQL tables, prepared statements, and transactional batches for application records.

  • Library Runtime.Workers
  • npm @cloudflare/workers-types 5.20260906.1

Database and schema

Create a D1 database, apply its schema or migrations, and attach it to the consuming Worker as a D1Database binding. Keep that database lifecycle separate from deploying a new Worker version. The account setup example creates a database through the management client.

Bind values as statement parameters. F# result types describe the expected row shape; they do not migrate the schema or validate arbitrary SQL results. The examples below demonstrate reads and a batch that commits related inserts together.

Guestbook

D1 is a SQL database. prepare and bind build a statement with ? placeholders. run executes the insert, and all returns the rows as Entry records. The daily D1 allowance on the free plan is 5 million rows read and 100,000 rows written.

open Fable.Core

module Workers = FSharp.CloudEdge.Runtime.Workers

type Entry = {| name: string; message: string |}

type Env =
    abstract DB: Workers.D1Database

let insertSql = "INSERT INTO entries (name, message) VALUES (?, ?)"
let latestSql = "SELECT name, message FROM entries ORDER BY id DESC LIMIT 20"

[<ExportDefault>]
let worker: Workers.ExportedHandler<Env, obj, obj, obj> =
    Workers.ExportedHandler.Create(fetch = fun request env _ ->
        async {
            match request.``method`` with
            | "POST" ->
                let! entry = request.json<Entry>() |> Async.AwaitPromise
                let insert = env.DB.prepare(insertSql).bind(entry.name, entry.message)
                let! result = insert.run() |> Async.AwaitPromise
                return Workers.Exports.Response.json {| id = result.meta.last_row_id |}
            | _ ->
                let! result = env.DB.prepare(latestSql).all<Entry>() |> Async.AwaitPromise
                return Workers.Exports.Response.json result.results
        }
        |> Async.StartAsPromise
        |> U2.Case1)

Needs a DB binding to a D1 database, with the entries table the queries use.

Emitted JavaScript
import { awaitPromise, startAsPromise } from "./fable_modules/fable-library-js.5.13.0/Async.js";
import { singleton } from "./fable_modules/fable-library-js.5.13.0/AsyncBuilder.js";

export const insertSql = "INSERT INTO entries (name, message) VALUES (?, ?)";

export const latestSql = "SELECT name, message FROM entries ORDER BY id DESC LIMIT 20";

export const worker = {
    fetch: (request, env, _arg) => startAsPromise(singleton.Delay(() => ((request.method === "POST") ? singleton.Bind(awaitPromise(request.json()), (_arg_1) => {
        const entry = _arg_1;
        const insert = env.DB.prepare(insertSql).bind(entry.name, entry.message);
        return singleton.Bind(awaitPromise(insert.run()), (_arg_2) => singleton.Return(globalThis.Response.json({
            id: _arg_2.meta.last_row_id,
        })));
    }) : singleton.Bind(awaitPromise(env.DB.prepare(latestSql).all()), (_arg_3) => singleton.Return(globalThis.Response.json(_arg_3.results)))))),
};

export default worker;

Order Checkout

batch sends an order and its items to D1 in one call. Cloudflare's D1 documentation defines batched statements as SQL transactions, so if one insert fails, D1 rolls back the order and all of its items. The item statement is prepared once and bound once per item.

open Fable.Core

module Workers = FSharp.CloudEdge.Runtime.Workers

type Order = {| email: string; items: {| sku: string; quantity: float |}[] |}

type Env =
    abstract DB: Workers.D1Database

let orderSql = "INSERT INTO orders (id, email) VALUES (?, ?)"
let itemSql = "INSERT INTO order_items (order_id, sku, quantity) VALUES (?, ?, ?)"

[<ExportDefault>]
let worker: Workers.ExportedHandler<Env, obj, obj, obj> =
    Workers.ExportedHandler.Create(fetch = fun request env _ ->
        async {
            let! order = request.json<Order>() |> Async.AwaitPromise
            let orderId = Workers.Exports.crypto.randomUUID()
            let addItem = env.DB.prepare itemSql
            let statements =
                [| env.DB.prepare(orderSql).bind(orderId, order.email)
                   for item in order.items do
                       addItem.bind(orderId, item.sku, item.quantity) |]
            let! _ = env.DB.batch statements |> Async.AwaitPromise
            return Workers.Exports.Response.json {| orderId = orderId |}
        }
        |> Async.StartAsPromise
        |> U2.Case1)

Needs the same DB binding, with orders and order_items tables.

Search over stored content

The Hybrid Search recipe combines D1 full-text search with a Vectorize index. D1 holds the keyword index and content records; Vectorize provides similarity search over embeddings.

Help verify the binding

Useful checks include row decoding, null values, statement parameters, batch rollback, and migrations. A compiling consumer does not establish hosted behavior. Record the package version, configuration, and observed result using the verification guide.

NuGet packages

Runtime.Workers 0.1.0.

See installation and release availability.

Edit this page