WebDyne::Cloudflare::D1
Future-returning Cloudflare D1 facade.
Synopsis
my $db_or=WebDyne::Cloudflare::D1->new(
scope => $self->r()->{'scope'}, binding => 'DB',
);
my $row_hr=$db_or->prepare('SELECT name FROM things WHERE id = ?1')
->bind($id)->first()->get();
Interface
new(scope => $scope_hr, binding => 'DB')validates the request capability.binding()returns the selected binding name.prepare($sql)returns an immutable Statement.run($sql, @params),all($sql, @params), andfirst($sql, @params)are Future-returning convenience methods.batch($statements_ar)executes a non-empty array of prepared statements from this database object as one atomic D1 batch. It returns a Future of ordered result hashes (results,meta,success), one per statement.blob($bytes)creates an explicit Blob. Function, class-method and object-method calls are supported; supply exactly one payload.
SQL parameters accept scalars, undef (SQL NULL), JSON booleans and explicit
BLOB wrappers. Unflagged non-ASCII text is decoded strictly as UTF-8.
Returned BLOB columns become Perl bytes. Rows may safely contain columns named
type and base64; envelopes are interpreted only at column-value positions.
Zero, empty strings and NULL remain distinct.
Host failures fail the Future with a D1::Error. Missing capabilities and invalid constructor arguments throw immediately. Internal encoding/host-call helpers are not supported API. No implicit retries or automatic SQL interpolation is provided.
Atomic batches
my $insert_or=$db_or->prepare('INSERT INTO things(name) VALUES (?1)');
my $results_ar=$db_or->batch([
$insert_or->bind('first'),
$insert_or->bind('second'),
$db_or->prepare('SELECT name FROM things ORDER BY name'),
])->get();
my $rows_ar=$results_ar->[2]{'results'};
D1 executes statements sequentially and rolls back the entire batch when one fails. The Future fails with a structured D1 error; partial results are not returned. Parameters and BLOB columns follow the ordinary query encoding. Statements remain reusable and binding does not change the original statement.
Supply all statements before execution; a batch cannot pause for Perl code to inspect a result or bind that result into a later statement. It is not an interactive transaction API. Statements from another database object (even one using the same binding) are rejected, as are empty batches and non-statement entries. Validation errors fail the Future before the host call. The adapter does not split or retry batches; Cloudflare's query and resource limits apply.
Text inputs, including nested metadata keys and values, are normalized from unflagged UTF-8 without changing caller data. Invalid UTF-8, cyclic containers and duplicate normalized keys are rejected before calling the host. See the usage guide.
Sessions and read replication
with_session($constraint_or_bookmark) returns a Future of a
Session. Omit the argument for first-unconstrained, or pass
first-primary or a non-empty bookmark from a previous session. Explicit
undef, empty strings, references and extra arguments are rejected.
first-unconstrainedallows the first read to use a replica, potentially behind the primary.first-primarysends the first query to the primary; later reads may use replicas while retaining sequential consistency.- A bookmark starts the session at least as up-to-date as that bookmark.
my $session_or=await $db_or->with_session('first-primary');
await $session_or->run('UPDATE things SET name = ?1 WHERE id = ?2', $name, $id);
my $row_hr=await $session_or->first('SELECT name FROM things WHERE id = ?1', $id);
my $bookmark=await $session_or->get_bookmark();
Enable read replication separately in the Cloudflare D1 database settings. Existing database objects keep their primary-only behavior. Sessions also work when replication is disabled; writes always go to the primary. A session does not guarantee the latest primary data on every read.
For HTTP continuity, read a per-database bookmark from an application header
such as x-d1-bookmark, use it in with_session(), then return the new bookmark
in the response header before sending the response. For example, with a PAGI
scope and send callback:
my ($bookmark)=map { $_->[1] }
grep { lc($_->[0]) eq 'x-d1-bookmark' } @{$scope_hr->{'headers'}};
my $session_or=await $db_or->with_session(
(defined($bookmark)&&length($bookmark)) ? $bookmark : 'first-primary',
);
my $result_hr=await $session_or->all('SELECT name FROM things');
my $next=await $session_or->get_bookmark();
await $send_cr->({type => 'http.response.start', status => 200, headers => [
['content-type', 'application/json'], ['cache-control', 'no-store'],
(defined($next) ? (['x-d1-bookmark', $next]) : ()),
]});
await $send_cr->({type => 'http.response.body',
body => JSON::PP->new()->utf8()->encode(WebDyne::Cloudflare::json_value($result_hr)),
more_body => 0});
Choose bookmark/header names separately for multiple databases. The library never automatically reads or writes HTTP headers. Finish database work before response headers when exporting the final bookmark, including streaming pages.
The protocol remains version 1 with an additive session_bindings allow-list.
Older hosts without that advertisement reject session creation in Perl; hosts
also verify native withSession support. Session IDs stay scoped to a request
and binding. run, all and batch preserve provider metadata, including
served_by_primary and served_by_region when supplied; first and raw
retain their existing row-only results.
See Cloudflare read replication and the Sessions API.