open()

Last updated 29 October 2026

View .md

The two open functions: every option, what they return and what they throw.

@tonbo/sqlite-ursula/tonbo opens a Tonbo database by name with an API key. The root entry opens a stream by URL with a token you supply; the /tonbo function calls it once it has the URL and the token.

open() by name

From @tonbo/sqlite-ursula/tonbo, since 0.11.1.

function open(
  nameOrUrl: string | URL,
  options?: TonboOpenOptions,
): Promise<UrsulaDatabase>

interface TonboOpenOptions extends OpenOptions {
  apiKey?: string;
  apiOrigin?: string;
  authOrigin?: string;
}
import { open } from "@tonbo/sqlite-ursula/tonbo";

const db = await open("notes", { dir: "./notes-cache" });

nameOrUrl is a database name, or a URL: a URL object, or a string that starts with a scheme such as https://. A name is looked up with GET {apiOrigin}/v1/databases/{name}, the key as its bearer token, in the key’s account; a URL skips the lookup, and a URL of another account’s database answers not found with this key. A database that is still being created is waited for, and one that is being deleted fails as not found. Then the stream is opened as the root open() opens it. Unless a token or URSULA_VFS_TOKEN_FILE serves the stream, it waits for the key’s first Streams token, which the extension exchanges and keeps fresh in the background (see Tokens); this is so since 0.11.2, and 0.11.1 passed the open a token provider. The lookup and the exchanges go out from the extension, with its certificate authorities and the environment’s proxy (see URSULA_VFS_CA_FILE). The name’s rule is on Manage databases.

Options

TonboOpenOptions: every root option, and:

OptionTypeDefaultWhat it does
apiKeystringTONBO_API_KEY, else the login tonbo login savedThe Tonbo API key, a tbo_ key. It goes only to the two origins below. Not needed to open a URL when token or URSULA_VFS_TOKEN_FILE serves the stream.
apiOriginstringTONBO_API_ORIGIN, else https://api.tonbo.devThe Management API’s origin, for the name lookup. https://, or http:// to a loopback host, with no path.
authOriginstringTONBO_AUTH_ORIGIN, else https://tonbo.devThe origin of the token endpoint, /api/iam/token, where the key is exchanged. The same rules as apiOrigin.
tokenstring or TokenProviderA provider for the keyServes the stream’s requests in place of the key’s tokens. A name still needs the key for its lookup.
signalAbortSignal30 secondsBounds the lookup and the open together.

The key’s order and the login file’s rules are on Opening by name, and the variables on Environment variables.

open() by URL

From @tonbo/sqlite-ursula.

function open(url: string | URL, options?: OpenOptions): Promise<UrsulaDatabase>

interface OpenOptions extends Omit<DatabaseSyncOptions, "open" | "readOnly"> {
  dir?: string;
  token?: string | TokenProvider;
  signal?: AbortSignal;
  maxPendingBytes?: number;
  recreate?: boolean;
  readOnly?: boolean;
}
import { open } from "@tonbo/sqlite-ursula";

const db = await open(process.env.NOTES_URL, {
  token: process.env.STREAMS_TOKEN,
  signal: AbortSignal.timeout(10_000),
});

The first open of a stream in the process binds its local cache, catches it up from the stream and claims the stream, which fences every earlier owner. Later opens of it in the process return another connection on the same binding, with no network, and concurrent opens share one bind and its outcome. Worker threads call open too. After a reset, an open binds anew at once, on a copy of the reset database’s cache.

Options

OpenOptions:

OptionTypeDefaultWhat it does
dirstring<user cache>/sqlite-ursula/0.8The cache root, created when missing. A stream is open under one root at a time in the process: an open under another root waits for the database there to retire, and fails with bound_elsewhere while it has connections open.
tokenstring or TokenProviderNone: URSULA_VFS_TOKEN_FILE when setThe bearer token requests carry, or a provider that hands out tokens and keeps them fresh. The process carries one token, set by the last open that names one. See Tokens.
signalAbortSignal30 secondsWhen the open gives up. It then rejects with UrsulaUnavailableError: the last condition the open met, or cancelled.
maxPendingBytesnumber64 MiBHow many bytes of compressed commits may wait for the stream before writes fail with backlog_full. A whole number of bytes.
recreatebooleanfalseWhen the stream is missing and the cache holds data: create the stream again and move the cache into salvage/ (see Salvage), instead of failing with not_found. Tonbo creates database streams itself, so on Tonbo create the database first.
readOnlybooleanfalseOpen without claiming the stream, so the owner is never fenced; writes fail with SQLITE_READONLY. See Read-only opens. Not with recreate.
timeoutnumber5000node:sqlite’s busy timeout in milliseconds. The connections of a binding share its files, hence the default.
The restDatabaseSyncOptionsnode:sqlitePassed to DatabaseSync, such as enableForeignKeyConstraints. Not open, which is always true, and not readOnly, which the option above sets.

A token, maxPendingBytes, recreate or readOnly of the wrong type rejects with UrsulaSetupError invalid_argument before any request. The user cache is $XDG_CACHE_HOME, else ~/.cache on Linux and ~/Library/Caches on macOS.

Read-only opens

In a process that owns the stream, readOnly returns a read-only connection on the owner’s binding: it sees the owner’s latest commits, acknowledged or not, and its synced() waits as the owner’s does. In any other process it builds or catches up a reader cache of its own (role: "reader", reader-<n>/ in the stream’s cache directory) to the stream’s tail at the open, and reads the stream as of then: it does not follow later commits, and its synced() resolves at once. Close every read-only connection of the process and open again to read newer commits. A closed stream opens too. When the stream is unreachable it opens an earlier reader cache as it is, and otherwise waits until the signal fires.

Returns

A promise of an UrsulaDatabase, a node:sqlite DatabaseSync subclass, so drizzle, kysely and Pi’s driver take it. Commit, then await db.synced() before anything outside the process acts on what you wrote or read.

When the stream is unreachable and the cache is a trusted one that claimed the stream before, the open resolves anyway, with status().state provisional: reads come from the cache, commits are local and pending, and synced() waits. Once the stream answers, the claim is checked and applied, and the state turns live; if another owner took the stream meanwhile, the database resets with nothing sent (see state). Without such a cache the open waits for the stream until its signal fires.

Errors

Both functions reject with:

  • UrsulaSetupError when the open cannot succeed as asked, such as invalid_url, not_found, bound_elsewhere or wrong_cluster. Its action is fix, or retry for in_use and claim_races.
  • UrsulaUnavailableError when the signal fired first: the last condition the open met, or cancelled.
  • With a token provider: the provider’s own UrsulaError when it failed for good, UrsulaSetupError invalid_argument when it handed out a token that cannot be sent, and UrsulaUnavailableError unavailable when it handed out none before the signal fired.

The /tonbo open also rejects with TonboError for a key or a database to fix, UrsulaUnavailableError (unreachable, unavailable or rate_limited) when the Tonbo API did not answer before the signal fired, and UrsulaSetupError: invalid_name for an empty name, invalid_argument for an option or an answer it cannot use, or for a second API key while the first key’s tokens serve (since 0.11.2; see One token per process), invalid_url for a stream URL it cannot take or that would get the key’s tokens over plain http:// to another host, and local_setup for a login file it cannot read.