The Durable Object Storage API allows Durable Objects to access transactional and strongly consistent storage. A Durable Object's attached storage is private to its unique instance and cannot be accessed by other objects.
The Durable Object Storage API comes with several methods, including SQL, point-in-time recovery (PITR), key-value (KV), and alarm APIs. Available API methods depend on the storage backend for a Durable Objects class, either SQLite or KV.
Methods 1 SQLite-backed Durable Object class KV-backed Durable Object class SQL API â â PITR API â â KV API â 2, 3 â Alarms API â âFootnotes
1 Each method is implicitly wrapped inside a transaction, such that its results are atomic and isolated from all other storage operations, even when accessing multiple key-value pairs.
2 KV API methods like get()
, put()
, delete()
, or list()
store data in a hidden SQLite table.
3 KV methods which were previously asynchronous with KV storage (for example, get
, put
, delete
, deleteAll
, list
) are synchronous, even though they return promises. These methods will have completed their operations before they return the promise.
Recommended SQLite-backed Durable Objects
Cloudflare recommends all new Durable Object namespaces use the SQLite storage backend. These Durable Objects can continue to use storage key-value API.
Additionally, SQLite-backed Durable Objects allow you to store more types of data (such as tables), and offers Point In Time Recovery API which can restore a Durable Object's embedded SQLite database contents (both SQL data and key-value data) to any point in the past 30 days.
The key-value storage backend remains for backwards compatibility, and a migration path from KV storage backend to SQLite storage backend for existing Durable Object namespaces will be available in the future.
Storage billing on SQLite-backed Durable Objects
Storage billing is not yet enabled for Durable Object classes using the SQLite storage backend. SQLite-backed Durable Objects will incur charges for requests and duration. Storage billing for SQLite-backed Durable Objects will be enabled at a later date with advance notice with the shared pricing.
Durable Objects gain access to Storage API via the DurableObjectStorage
interface and accessed by the DurableObjectState::storage
property. This is frequently accessed via this.ctx.storage
with the ctx
parameter passed to the Durable Object constructor.
The following code snippet shows you how to store and retrieve data using the Durable Object Storage API.
export class Counter extends DurableObject {
constructor(ctx, env) {
super(ctx, env);
}
async increment() {
let value = (await this.ctx.storage.get("value")) || 0;
value += 1;
await this.ctx.storage.put("value", value);
return value;
}
}
export class Counter extends DurableObject {
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
}
async increment(): Promise<number> {
let value: number = (await this.ctx.storage.get('value')) || 0;
value += 1;
await this.ctx.storage.put('value', value);
return value;
}
}
JavaScript is a single-threaded and event-driven programming language. This means that JavaScript runtimes, by default, allow requests to interleave with each other which can lead to concurrency bugs. The Durable Objects runtime uses a combination of input gates and output gates to avoid this type of concurrency bug when performing storage operations. Learn more in our blog post â.
The SqlStorage
interface encapsulates methods that modify the SQLite database embedded within a Durable Object. The SqlStorage
interface is accessible via the sql
property of DurableObjectStorage
class.
For example, using sql.exec()
, a user can create a table, then insert rows into the table.
import { DurableObject } from "cloudflare:workers";
export class MyDurableObject extends DurableObject {
sql: SqlStorage;
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
this.sql = ctx.storage.sql;
this.sql.exec(`CREATE TABLE IF NOT EXISTS artist(
artistid INTEGER PRIMARY KEY,
artistname TEXT
);INSERT INTO artist (artistid, artistname) VALUES
(123, 'Alice'),
(456, 'Bob'),
(789, 'Charlie');`
);
}
}
ctx.storage.sql
are only allowed on Durable Object classes with SQLite storage backend and will return an error if called on Durable Object classes with a key-value storage backend.exec(query: string, ...bindings: any[])
: SqlStorageCursor
query
: string
query
can contain ?
placeholders for parameter bindings. Multiple SQL statements, separated with a semicolon, can be executed in the query
. With multiple SQL statements, any parameter bindings are applied to the last SQL statement in the query
, and the returned cursor is only for the last SQL statement....bindings
: any[] Optional
?
placeholders in query
.A cursor (SqlStorageCursor
) to iterate over query row results as objects. SqlStorageCursor
is a JavaScript Iterable â, which supports iteration using for (let row of cursor)
. SqlStorageCursor
is also a JavaScript Iterator â, which supports iteration using cursor.next()
.
SqlStorageCursor
supports the following methods:
next()
done
and value
properties adhering to the JavaScript Iterator â. done
is set to false
when a next value is present, and value
is set to the next row object in the query result. done
is set to true
when the entire cursor is consumed, and no value
is set.toArray()
one()
one()
throws an exception.raw()
: Iterator
next()
and toArray()
methods above.raw()
iterator iterate over the same query results and can be combined. For example:let cursor = this.sql.exec("SELECT * FROM artist ORDER BY artistname ASC;");
let rawResult = cursor.raw().next();
if (!rawResult.done) {
console.log(rawResult.value); // prints [ 123, 'Alice' ]
} else {
// query returned zero results
}
console.log(cursor.toArray()); // prints [{ artistid: 456, artistname: 'Bob' },{ artistid: 789, artistname: 'Charlie' }]
SqlStorageCursor
has the following properties:
columnNames
: string[]
raw
iterator.rowsRead
: number
query
. This may increase as you iterate the cursor. The final value is used for SQL billing.rowsWritten
: number
query
. This may increase as you iterate the cursor. The final value is used for SQL billing.int64
), then retrieve the same value, the returned value may be less precise than your original number.SQL transactions
Note that sql.exec()
cannot execute transaction-related statements like BEGIN TRANSACTION
or SAVEPOINT
. Instead, use the ctx.storage.transaction()
or ctx.storage.transactionSync()
APIs to start a transaction, and then execute SQL queries in your callback.
SQL API examples below use the following SQL schema:
import { DurableObject } from "cloudflare:workers";
export class MyDurableObject extends DurableObject {
sql: SqlStorage
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
this.sql = ctx.storage.sql;
this.sql.exec(`CREATE TABLE IF NOT EXISTS artist(
artistid INTEGER PRIMARY KEY,
artistname TEXT
);INSERT INTO artist (artistid, artistname) VALUES
(123, 'Alice'),
(456, 'Bob'),
(789, 'Charlie');`
);
}
}
Iterate over query results as row objects:
let cursor = this.sql.exec("SELECT * FROM artist;");
for (let row of cursor) {
// Iterate over row object and do something
}
Convert query results to an array of row objects:
// Return array of row objects: [{"artistid":123,"artistname":"Alice"},{"artistid":456,"artistname":"Bob"},{"artistid":789,"artistname":"Charlie"}]
let resultsArray1 = this.sql.exec("SELECT * FROM artist;").toArray();
// OR
let resultsArray2 = Array.from(this.sql.exec("SELECT * FROM artist;"));
// OR
let resultsArray3 = [...this.sql.exec("SELECT * FROM artist;")]; // JavaScript spread syntax
Convert query results to an array of row values arrays:
// Returns [[123,"Alice"],[456,"Bob"],[789,"Charlie"]]
let cursor = this.sql.exec("SELECT * FROM artist;");
let resultsArray = cursor.raw().toArray();
// Returns ["artistid","artistname"]
let columnNameArray = this.sql.exec("SELECT * FROM artist;").columnNames.toArray();
Get first row object of query results:
// Returns {"artistid":123,"artistname":"Alice"}
let firstRow = this.sql.exec("SELECT * FROM artist ORDER BY artistname DESC;").toArray()[0];
Check if query results have exactly one row:
// returns error
this.sql.exec("SELECT * FROM artist ORDER BY artistname ASC;").one();
// returns { artistid: 123, artistname: 'Alice' }
let oneRow = this.sql.exec("SELECT * FROM artist WHERE artistname = ?;", "Alice").one()
Returned cursor behavior:
let cursor = this.sql.exec("SELECT * FROM artist ORDER BY artistname ASC;");
let result = cursor.next();
if (!result.done) {
console.log(result.value); // prints { artistid: 123, artistname: 'Alice' }
} else {
// query returned zero results
}
let remainingRows = cursor.toArray();
console.log(remainingRows); // prints [{ artistid: 456, artistname: 'Bob' },{ artistid: 789, artistname: 'Charlie' }]
Returned cursor and raw()
iterator iterate over the same query results:
let cursor = this.sql.exec("SELECT * FROM artist ORDER BY artistname ASC;");
let result = cursor.raw().next();
if (!result.done) {
console.log(result.value); // prints [ 123, 'Alice' ]
} else {
// query returned zero results
}
console.log(cursor.toArray()); // prints [{ artistid: 456, artistname: 'Bob' },{ artistid: 789, artistname: 'Charlie' }]
sql.exec().rowsRead()
:
let cursor = this.sql.exec("SELECT * FROM artist;");
cursor.next()
console.log(cursor.rowsRead); // prints 1
cursor.toArray(); // consumes remaining cursor
console.log(cursor.rowsRead); // prints 3
databaseSize
: number
The current SQLite database size in bytes.
let size = ctx.storage.sql.databaseSize;
PITR (Point In Time Recovery) API
For SQLite-backed Durable Objects, the following point-in-time-recovery (PITR) API methods are available to restore a Durable Object's embedded SQLite database to any point in time in the past 30 days. These methods apply to the entire SQLite database contents, including both the object's stored SQL data and stored key-value data using the key-value put()
API. The PITR API is not supported in local development because a durable log of data changes is not stored locally.
The PITR API represents points in time using 'bookmarks'. A bookmark is a mostly alphanumeric string like 0000007b-0000b26e-00001538-0c3e87bb37b3db5cc52eedb93cd3b96b
. Bookmarks are designed to be lexically comparable: a bookmark representing an earlier point in time compares less than one representing a later point, using regular string comparison.
ctx.storage.getCurrentBookmark()
: Promise<string>
ctx.storage.getBookmarkForTime(timestamp: number | Date)
: Promise<string>
new Date(timestamp)
.onNextSessionRestoreBookmark
ctx.storage.onNextSessionRestoreBookmark(bookmark: string)
: Promise<string>
ctx.abort()
to restart the Durable Object, thus completing the point-in-time recovery.This method returns a special bookmark representing the point in time immediately before the recovery takes place (even though that point in time is still technically in the future). Thus, after the recovery completes, it can be undone by performing a second recovery to this bookmark.
let now = new Date();
// restore to 2 days ago
let bookmark = ctx.storage.getBookmarkForTime(now - 2);
ctx.storage.onNextSessionRestoreBookmark(bookmark);
get(key string, options Object optional)
: Promise<any>
get(keys Array<string>, options Object optional)
: Promise<Map<string, any>>
Map
â will be whatever was previously written for the corresponding key. Results in the Map
will be sorted in increasing order of their UTF-8 encodings, with any requested keys that do not exist being omitted. Supports up to 128 keys at a time.allowConcurrency
: boolean
allowConcurrency: true
to opt out of this behavior and allow concurrent events to be delivered.noCache
: boolean
put(key string, value any, options Object optional)
: Promise
Stores the value and associates it with the given key. The value can be any type supported by the structured clone algorithm â, which is true of most types.
The size of keys and values have different limits depending on the Durable Object storage backend you are using. Refer to either:
put(entries Object, options Object optional)
: Promise
delete(key string, options Object optional)
: Promise<boolean>
true
if the key existed or false
if it did not.delete(keys Array<string>, options Object optional)
: Promise<number>
put()
, delete()
and deleteAll()
support the following options:
allowUnconfirmed
boolean
By default, the system will pause outgoing network messages from the Durable Object until all previous writes have been confirmed flushed to disk. If the write fails, the system will reset the Object, discard all outgoing messages, and respond to any clients with errors instead.
This way, Durable Objects can continue executing in parallel with a write operation, without having to worry about prematurely confirming writes, because it is impossible for any external party to observe the Object's actions unless the write actually succeeds.
After any write, subsequent network messages may be slightly delayed. Some applications may consider it acceptable to communicate on the basis of unconfirmed writes. Some programs may prefer to allow network traffic immediately. In this case, set allowUnconfirmed
to true
to opt out of the default behavior.
If you want to allow some outgoing network messages to proceed immediately but not others, you can use the allowUnconfirmed option to avoid blocking the messages that you want to proceed and then separately call the sync()
method, which returns a promise that only resolves once all previous writes have successfully been persisted to disk.
noCache
boolean
If true, then the key/value will be discarded from memory as soon as it has completed writing to disk.
Use noCache
if the key will not be used again in the near future. noCache
will never change the semantics of your code, but it may affect performance.
If you use get()
to retrieve the key before the write has completed, the copy from the write buffer will be returned, thus ensuring consistency with the latest call to put()
.
Automatic write coalescing
If you invoke put()
(or delete()
) multiple times without performing any await
in the meantime, the operations will automatically be combined and submitted atomically. In case of a machine failure, either all of the writes will have been stored to disk or none of the writes will have been stored to disk.
Write buffer behavior
The put()
method returns a Promise
, but most applications can discard this promise without using await
. The Promise
usually completes immediately, because put()
writes to an in-memory write buffer that is flushed to disk asynchronously. However, if an application performs a large number of put()
without waiting for any I/O, the write buffer could theoretically grow large enough to cause the isolate to exceed its 128 MB memory limit. To avoid this scenario, such applications should use await
on the Promise
returned by put()
. The system will then apply backpressure onto the application, slowing it down so that the write buffer has time to flush. Using await
will disable automatic write coalescing.
list(options Object optional)
: Promise<Map<string, any>>
Returns all keys and values associated with the current Durable Object in ascending sorted order based on the keys' UTF-8 encodings.
The type of each returned value in the Map
â will be whatever was previously written for the corresponding key.
Be aware of how much data may be stored in your Durable Object before calling this version of list
without options because all the data will be loaded into the Durable Object's memory, potentially hitting its limit. If that is a concern, pass options to list
as documented below.
start
string
startAfter
string
start
.end
string
prefix
string
reverse
boolean
reverse
does not change the meaning of start
, startKey
, or endKey
. start
still defines the smallest key in lexicographic order that can be returned (inclusive), effectively serving as the endpoint for a reverse-order list. end
still defines the largest key in lexicographic order that the list should consider (exclusive), effectively serving as the starting point for a reverse-order list.limit
number
allowConcurrency
boolean
get()
, above.noCache
boolean
get()
, above.getAlarm(options Object optional)
: Promise<Number | null>
getAlarm()
returns null
.get()
, but without noCache
.setAlarm(scheduledTime Date | number, options Object optional)
: Promise
Sets the current alarm time, accepting either a JavaScript Date
, or integer milliseconds since epoch.
If setAlarm()
is called with a time equal to or before Date.now()
, the alarm will be scheduled for asynchronous execution in the immediate future. If the alarm handler is currently executing in this case, it will not be canceled. Alarms can be set to millisecond granularity and will usually execute within a few milliseconds after the set time, but can be delayed by up to a minute due to maintenance or failures while failover takes place.
deleteAlarm(options Object optional)
: Promise
setAlarm()
and deleteAlarm()
support the same options as put()
, but without noCache
.deleteAll(options Object optional)
: Promise
deleteAll()
removes all keys and associated values for an individual Durable Object. For Durable Objects with a SQLite storage backend, deleteAll()
removes the entire contents of a Durable Object's private SQLite database, including both SQL data and key-value data.deleteAll()
operation can fail, which may leave a subset of data undeleted. Durable Objects with a SQLite storage backend do not have a partial deleteAll()
issue because deleteAll()
operations are atomic (all or nothing).deleteAll()
does not proactively delete alarms. Use deleteAlarm()
to delete an alarm.transactionSync(callback)
: any
Only available when using SQLite-backed Durable Objects.
Invokes callback()
wrapped in a transaction, and returns its result.
If callback()
throws an exception, the transaction will be rolled back.
async
nor otherwise return a Promise. Only synchronous storage operations can be part of the transaction. This is intended for use with SQL queries using ctx.storage.sql.exec()
, which complete sychronously.transaction(closureFunction(txn))
: Promise
Runs the sequence of storage operations called on txn
in a single transaction that either commits successfully or aborts.
Explicit transactions are no longer necessary. Any series of write operations with no intervening await
will automatically be submitted atomically, and the system will prevent concurrent events from executing while await
a read operation (unless you use allowConcurrency: true
). Therefore, a series of reads followed by a series of writes (with no other intervening I/O) are automatically atomic and behave like a transaction.
txn
Provides access to the put()
, get()
, delete()
and list()
methods documented above to run in the current transaction context. In order to get transactional behavior within a transaction closure, you must call the methods on the txn
Object instead of on the top-level ctx.storage
Object.
Also supports a rollback()
function that ensures any changes made during the transaction will be rolled back rather than committed. After rollback()
is called, any subsequent operations on the txn
Object will fail with an exception. rollback()
takes no parameters and returns nothing to the caller.
When using the SQLite-backed storage engine, the txn
object is obsolete. Any storage operations performed directly on the ctx.storage
object, including SQL queries using ctx.storage.sql.exec()
, will be considered part of the transaction.
sync()
: Promise
Synchronizes any pending writes to disk.
This is similar to normal behavior from automatic write coalescing. If there are any pending writes in the write buffer (including those submitted with the allowUnconfirmed
option), the returned promise will resolve when they complete. If there are no pending writes, the returned promise will be already resolved.
sql
is a readonly property of type DurableObjectStorage
encapsulating the SQL API.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4