Docs
BlogHomeStart building

Database driver (totalum-sdk/d1)


import … from 'totalum-sdk/d1' — or the same namespace on totalum from 'totalum-sdk'. Install, configuration and errors: Totalum SDK.

The project's SQL database (Cloudflare D1, SQLite), used through Drizzle: drizzle(totalumD1()). totalumD1() returns a D1Database that finds the database wherever the code runs — the TOTALUM_DB binding inside the preview and the published app, the local database under next dev, and an HTTPS connection with the project key in a script or on a laptop — so the same code works everywhere. The database is the project's own: no other project can reach it.

ts
import { drizzle } from 'drizzle-orm/d1';
import { totalumD1 } from 'totalum-sdk/d1';

export const db = drizzle(totalumD1());
Method or typeWhat it does
totalumD1The project's own database as a D1Database, wherever the code runs: the bound TOTALUM_DB inside a Worker (the preview and the published app), Turso when the environment moved there, the local D1 that initOpenNextCloudflareForDev() binds under next dev, and an HTTPS client to the Totalum API everywhere else (a script, a laptop: with TOTALUM_PROJECT_KEY).
isTotalumD1ErrorWhether e is a database error (a TotalumD1Error): a SQL error, a refusal of the driver, or the Totalum API's answer to a statement.
READ_YOUR_WRITES_COOKIEThe name of the cookie withTotalumSession sets for 10 s after a write (__tlm_d1_w): while the browser holds it, its reads go to the primary database, so a user always sees what they just saved.
withTotalumSessionWraps one request of the app's Worker entry (the template does it for you) so reads use the nearest read replica and writes stay consistent for the user who made them.
D1DatabaseThe project's database, as totalumD1() returns it: Cloudflare's D1Database interface, so drizzle(totalumD1()) works, and you can also run SQL by hand with prepare/batch.
D1DatabaseSessionA session for read replicas, from db.withSession(): its statements see a consistent view, and its bookmark can continue the view in a later request.
D1ExecResultWhat db.exec() returns.
D1PreparedStatementA prepared SQL statement, from db.prepare(sql): bind its parameters, then run it with first, all, run or raw.
D1ResultWhat run(), all() and each batch() statement return: the rows and the statement's metadata.
TotalumD1ClientCodeThe codes the database driver itself throws (before or without the Totalum API): no key, an unsupported call, a statement of another instance in a batch, the network, an unreadable answer.
TotalumD1ErrorThe database TotalumError: message is D1_ERROR: …-shaped so it reads like a native failure — byte-identical to the binding's message when the failure is SQLite's.
TotalumD1ErrorCodeDeprecated: Since 1.0.0: use TotalumErrorCode (the same type).
TotalumD1OptionsThe options of totalumD1().

#totalumD1

ts
totalumD1(options?: TotalumD1Options): D1Database

The project's own database as a D1Database, wherever the code runs: the bound TOTALUM_DB inside a Worker (the preview and the published app), Turso when the environment moved there, the local D1 that initOpenNextCloudflareForDev() binds under next dev, and an HTTPS client to the Totalum API everywhere else (a script, a laptop: with TOTALUM_PROJECT_KEY). Detection happens on the first statement, never at call time, so drizzle(totalumD1()) at module scope is fine. A transient failure of a read is retried for you.

Parameters

NameTypeRequiredDescription
optionsTotalumD1OptionsnoThe key, API URL and environment for the HTTPS path, migrate mode, or an explicit binding.
options.mode'query' | 'migrate'no'migrate' (scripts/migrate.ts only) sends x-totalum-migrate: 1 and enables exec(); default 'query'.
options.destructivebooleannoMigrate mode only: sends x-totalum-migrate-destructive: 1.
options.bindingD1DatabasenoAn explicit binding wins over detection (plain Workers: env.TOTALUM_DB; tests: a Miniflare D1).
options.transport'auto' | 'https'no'https' forces the HTTPS path even inside a Worker (the conformance corpus); default 'auto'.
options.keystringnoProject key tlm_pk_…; default TOTALUM_PROJECT_KEY. Server-side only.
options.apiUrlstringnoThe Totalum API's URL: default TOTALUM_SDK_API_URL, else https://sdk-api.totalum.app.
options.env'dev' | 'live'noSent as x-totalum-env on every call (the database it targets, and dev vs live usage); default TOTALUM_ENV, else live.

Returns D1Database — The database: pass it to drizzle(), or use prepare/batch directly.

Errors — besides the errors every call can throw:

CodeHTTPWhen
SDK_NOT_CONFIGURED—Thrown by the first statement when no binding is found and TOTALUM_PROJECT_KEY is unset.

Examples

ts
// src/db/index.ts
import { drizzle } from 'drizzle-orm/d1';
import { totalumD1 } from 'totalum-sdk/d1';
import * as schema from './schema';

export const db = drizzle(totalumD1(), { schema });

// anywhere on the server
const latest = await db.select().from(schema.orders).orderBy(desc(schema.orders.createdAt)).limit(5);
ts
// plain SQL, from a script (npx tsx scripts/count-users.ts) — over HTTPS with TOTALUM_PROJECT_KEY
const row = await totalumD1().prepare('SELECT COUNT(*) AS n FROM users').first();

Result:

json
{ "n": 128 }

#isTotalumD1Error

ts
isTotalumD1Error(e: unknown): boolean

Whether e is a database error (a TotalumD1Error): a SQL error, a refusal of the driver, or the Totalum API's answer to a statement.

Parameters

NameTypeRequiredDescription
eunknownyesAnything caught.

Returns boolean — true for a TotalumD1Error (then e.errorCode, e.message are typed).

Example

ts
try {
  await db.insert(users).values({ email });
} catch (e) {
  if (isTotalumD1Error(e) && e.message.includes('UNIQUE constraint failed')) return { error: 'Email already used' };
  throw e;
}
ts
const READ_YOUR_WRITES_COOKIE: '__tlm_d1_w'

The name of the cookie withTotalumSession sets for 10 s after a write (__tlm_d1_w): while the browser holds it, its reads go to the primary database, so a user always sees what they just saved. Exported for apps that clear or inspect it.

Example

ts
import { READ_YOUR_WRITES_COOKIE } from 'totalum-sdk/d1';
const fresh = request.headers.get('cookie')?.includes(`${READ_YOUR_WRITES_COOKIE}=`);

#withTotalumSession

ts
withTotalumSession(request: Request, env: E, run: (env: E) => Promise<Response>): Promise<Response>

Wraps one request of the app's Worker entry (the template does it for you) so reads use the nearest read replica and writes stay consistent for the user who made them. In detail: a GET/HEAD reads through a first-unconstrained session — the nearest replica — unless the browser wrote in the last 10 s; any other method uses the plain binding (writes go to the primary; measured faster than any session) and its response sets the short cookie that sends that browser's next reads to the primary. A replica read that takes over REPLICA_READ_TIMEOUT_MS is answered by the primary instead, and the rest of that request reads the primary. A mutation's pure read the primary does not answer in that time is asked again on the primary (one call stalled while the database answered others; a sign-up waited on its first SELECT until the Worker was cancelled). run receives the env to hand to the app, whose totalumD1() reads TOTALUM_DB from it on every statement. No D1 binding (Turso, a laptop): run(env) unchanged.

Parameters

NameTypeRequiredDescription
requestRequestyesThe incoming request.
envEyesThe Worker's env (with the TOTALUM_DB binding).
run(env: E) => Promise<Response>yesHandles the request with the env to use (its TOTALUM_DB may be a session).

Returns Promise<Response> — The response, with the short read-your-writes cookie after a write.

Errors — besides the errors every call can throw:

CodeHTTPWhen
SQL_ERROR—Whatever run throws is passed on unchanged.

Example

ts
// worker.ts (the template's entry)
import { withTotalumSession } from 'totalum-sdk/d1';
import handler from './.open-next/worker.js';

export default {
  fetch: (request: Request, env: Env, ctx: ExecutionContext) =>
    withTotalumSession(request, env, (env) => handler.fetch(request, env, ctx)),
};

#D1Database

The project's database, as totalumD1() returns it: Cloudflare's D1Database interface, so drizzle(totalumD1()) works, and you can also run SQL by hand with prepare/batch.

#db.prepare

ts
db.prepare(sql: string): D1PreparedStatement

Prepares one SQL statement (with ? placeholders); run it with first, all, run or raw. Nothing runs until then.

Parameters

NameTypeRequiredDescription
sqlstringyesThe SQL text. Several statements separated by ; run together, atomically.

Returns D1PreparedStatement — The prepared statement.

Example

ts
const db = totalumD1();
const user = await db.prepare('SELECT * FROM users WHERE id = ?').bind(42).first();

#db.batch

ts
db.batch(statements: D1PreparedStatement[]): Promise<D1Result[]>

Runs several statements in one round trip, as one atomic unit: all apply or none (D1 has no BEGIN/COMMIT; this is its transaction). Drizzle's db.batch([...]) uses it.

Parameters

NameTypeRequiredDescription
statementsD1PreparedStatement[]yesStatements prepared by this database.

Returns Promise<D1Result[]> — One result per statement, in order.

Each item:

FieldTypeAlways presentDescription
resultsT[]yesThe rows, as objects keyed by column name (empty for a statement that returns none).
successtrueyesAlways true: a failure throws instead.
meta{ duration, size_after, rows_read, rows_written, last_row_id, changed_db, changes, served_by_region?, served_by_primary? }yesTiming, rows read and written, changes and the last inserted rowid.
meta.durationnumberyesHow long the statement took in the database, in ms.
meta.size_afternumberyesThe database's size after the statement, in bytes.
meta.rows_readnumberyesRows the statement read (what makes a query slow: add an index when it is high).
meta.rows_writtennumberyesRows the statement wrote.
meta.last_row_idnumberyesThe rowid of the last inserted row (INSERT without RETURNING).
meta.changed_dbbooleanyesWhether the database changed.
meta.changesnumberyesRows inserted, updated or deleted.
meta.served_by_regionstringnoThe region that answered (with read replicas).
meta.served_by_primarybooleannoWhether the primary database answered (false for a read replica).

Errors — besides the errors every call can throw:

CodeHTTPWhen
SQL_ERROR—A statement failed; none was applied.
BATCH_FOREIGN_STATEMENT—A statement prepared by another totalumD1() instance.

Example

ts
const [debit, credit] = await db.batch([
  db.prepare('UPDATE accounts SET balance = balance - ? WHERE id = ?').bind(50, 1),
  db.prepare('UPDATE accounts SET balance = balance + ? WHERE id = ?').bind(50, 2),
]);

Result:

ts
[
  { "success": true, "meta": { "changes": 1, … }, "results": [] },
  { "success": true, "meta": { "changes": 1, … }, "results": [] }
]

#db.exec

ts
db.exec(sql: string): Promise<D1ExecResult>

Runs a SQL script (several statements, no parameters): only in migrate mode, totalumD1({ mode: 'migrate' }) from scripts/migrate.ts. For data, use prepare or batch.

Parameters

NameTypeRequiredDescription
sqlstringyesThe script.

Returns Promise<D1ExecResult> — How many statements ran and how long they took.

FieldTypeAlways presentDescription
countnumberyesHow many statements ran.
durationnumberyesTheir total time in ms.

Errors — besides the errors every call can throw:

CodeHTTPWhen
EXEC_REQUIRES_MIGRATE_MODE—Called outside migrate mode.
SQL_ERROR—A statement failed.

Example

ts
const db = totalumD1({ mode: 'migrate' });
await db.exec('CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT NOT NULL);');

Result:

json
{ "count": 1, "duration": 0.42 }

#db.dump

ts
db.dump(): Promise<ArrayBuffer>

Not available: always throws NOT_SUPPORTED. Database backups are in the project's settings in Totalum.

Returns Promise<ArrayBuffer> — Never resolves.

Errors — besides the errors every call can throw:

CodeHTTPWhen
NOT_SUPPORTED—Always.

Example

ts
await db.dump(); // throws TotalumD1Error NOT_SUPPORTED

#db.withSession

ts
db.withSession(constraintOrBookmark?: string): D1DatabaseSession

A session for read replicas: 'first-unconstrained' reads from the nearest replica, 'first-primary' starts at the primary, a bookmark continues a previous session. The template already wraps each request with withTotalumSession, so app code rarely needs this. Over HTTPS and on Turso the session is the database itself.

Parameters

NameTypeRequiredDescription
constraintOrBookmarkstringno'first-unconstrained', 'first-primary' or a bookmark from getBookmark().

Returns D1DatabaseSession — The session.

Example

ts
const session = db.withSession('first-unconstrained');
const { results } = await session.prepare('SELECT * FROM products LIMIT 20').all();

#D1DatabaseSession

A session for read replicas, from db.withSession(): its statements see a consistent view, and its bookmark can continue the view in a later request.

#dbSession.prepare

ts
dbSession.prepare(sql: string): D1PreparedStatement

Prepares a statement in this session.

Parameters

NameTypeRequiredDescription
sqlstringyesOne SQL statement with ? placeholders.

Returns D1PreparedStatement — The prepared statement.

Example

ts
const session = db.withSession('first-unconstrained');
const posts = await session.prepare('SELECT * FROM posts ORDER BY id DESC LIMIT 10').all();

#dbSession.batch

ts
dbSession.batch(statements: D1PreparedStatement[]): Promise<D1Result[]>

Runs several statements in this session, as one atomic batch.

Parameters

NameTypeRequiredDescription
statementsD1PreparedStatement[]yesStatements prepared by this session.

Returns Promise<D1Result[]> — One result per statement.

Each item:

FieldTypeAlways presentDescription
resultsT[]yesThe rows, as objects keyed by column name (empty for a statement that returns none).
successtrueyesAlways true: a failure throws instead.
meta{ duration, size_after, rows_read, rows_written, last_row_id, changed_db, changes, served_by_region?, served_by_primary? }yesTiming, rows read and written, changes and the last inserted rowid.
meta.durationnumberyesHow long the statement took in the database, in ms.
meta.size_afternumberyesThe database's size after the statement, in bytes.
meta.rows_readnumberyesRows the statement read (what makes a query slow: add an index when it is high).
meta.rows_writtennumberyesRows the statement wrote.
meta.last_row_idnumberyesThe rowid of the last inserted row (INSERT without RETURNING).
meta.changed_dbbooleanyesWhether the database changed.
meta.changesnumberyesRows inserted, updated or deleted.
meta.served_by_regionstringnoThe region that answered (with read replicas).
meta.served_by_primarybooleannoWhether the primary database answered (false for a read replica).

Errors — besides the errors every call can throw:

CodeHTTPWhen
SQL_ERROR—A statement failed; none was applied.

Example

ts
const [users, posts] = await session.batch([session.prepare('SELECT * FROM users'), session.prepare('SELECT * FROM posts')]);

#dbSession.getBookmark

ts
dbSession.getBookmark(): string | null

The session's bookmark after its last statement: pass it to withSession(bookmark) in a later request to read at least that state. null before the first statement, and always over HTTPS and on Turso (one database, no replicas).

Returns string \| null — The bookmark, or null.

Example

ts
const bookmark = session.getBookmark();

Result:

json
"0000002a-00000003-00004f5e-7a4ce4bd3c0e9b0e1f2c3d4e5f60718"

#D1ExecResult

What db.exec() returns.

FieldTypeAlways presentDescription
countnumberyesHow many statements ran.
durationnumberyesTheir total time in ms.

#D1PreparedStatement

A prepared SQL statement, from db.prepare(sql): bind its parameters, then run it with first, all, run or raw. Nothing runs until one of those is awaited.

#stmt.bind

ts
stmt.bind(...values: unknown[]): D1PreparedStatement

Binds the values of the statement's ? placeholders, in order, and returns a new statement (the original is unchanged). Values: strings, numbers, booleans (stored as 1/0), null, ArrayBuffer/Uint8Array (BLOB).

Parameters

NameTypeRequiredDescription
valuesunknown[]yesOne value per ?, in order.

Returns D1PreparedStatement — A statement with those parameters.

Example

ts
const stmt = db.prepare('SELECT * FROM users WHERE email = ? AND active = ?').bind('ana@example.com', 1);

#stmt.first

ts
stmt.first(colName?: string): Promise<T | null>

Runs the statement and returns the first row (or one column of it), or null when there is none.

Parameters

NameTypeRequiredDescription
colNamestringnoReturn only this column's value.

Returns Promise<T \| null> — The first row, that column's value, or null.

Errors — besides the errors every call can throw:

CodeHTTPWhen
SQL_ERROR—SQLite refused the statement (message is SQLite's, as D1_ERROR: …).

Example

ts
const user = await db.prepare('SELECT id, name FROM users WHERE email = ?').bind('ana@example.com').first();
const count = await db.prepare('SELECT COUNT(*) AS n FROM users').first<number>('n');

Result:

ts
user = { id: 42, name: 'Ana' }   count = 128

#stmt.run

ts
stmt.run(): Promise<D1Result>

Runs a statement that changes data (INSERT, UPDATE, DELETE, DDL) and returns its metadata (changes, last_row_id) — and its rows, with RETURNING.

Returns Promise<D1Result> — The result: results (rows of a RETURNING) and meta.

FieldTypeAlways presentDescription
resultsT[]yesThe rows, as objects keyed by column name (empty for a statement that returns none).
successtrueyesAlways true: a failure throws instead.
meta{ duration, size_after, rows_read, rows_written, last_row_id, changed_db, changes, served_by_region?, served_by_primary? }yesTiming, rows read and written, changes and the last inserted rowid.
meta.durationnumberyesHow long the statement took in the database, in ms.
meta.size_afternumberyesThe database's size after the statement, in bytes.
meta.rows_readnumberyesRows the statement read (what makes a query slow: add an index when it is high).
meta.rows_writtennumberyesRows the statement wrote.
meta.last_row_idnumberyesThe rowid of the last inserted row (INSERT without RETURNING).
meta.changed_dbbooleanyesWhether the database changed.
meta.changesnumberyesRows inserted, updated or deleted.
meta.served_by_regionstringnoThe region that answered (with read replicas).
meta.served_by_primarybooleannoWhether the primary database answered (false for a read replica).

Errors — besides the errors every call can throw:

CodeHTTPWhen
SQL_ERROR—SQLite refused the statement (a constraint, a syntax error).

Example

ts
const r = await db.prepare('INSERT INTO users (email, name) VALUES (?, ?)').bind('ana@example.com', 'Ana').run();

Result:

json
{
  "success": true,
  "meta": { "duration": 0.31, "size_after": 12288, "rows_read": 1, "rows_written": 1, "last_row_id": 42, "changed_db": true, "changes": 1 },
  "results": []
}

#stmt.all

ts
stmt.all(): Promise<D1Result>

Runs a query and returns every row.

Returns Promise<D1Result> — The result: results (the rows) and meta.

FieldTypeAlways presentDescription
resultsT[]yesThe rows, as objects keyed by column name (empty for a statement that returns none).
successtrueyesAlways true: a failure throws instead.
meta{ duration, size_after, rows_read, rows_written, last_row_id, changed_db, changes, served_by_region?, served_by_primary? }yesTiming, rows read and written, changes and the last inserted rowid.
meta.durationnumberyesHow long the statement took in the database, in ms.
meta.size_afternumberyesThe database's size after the statement, in bytes.
meta.rows_readnumberyesRows the statement read (what makes a query slow: add an index when it is high).
meta.rows_writtennumberyesRows the statement wrote.
meta.last_row_idnumberyesThe rowid of the last inserted row (INSERT without RETURNING).
meta.changed_dbbooleanyesWhether the database changed.
meta.changesnumberyesRows inserted, updated or deleted.
meta.served_by_regionstringnoThe region that answered (with read replicas).
meta.served_by_primarybooleannoWhether the primary database answered (false for a read replica).

Errors — besides the errors every call can throw:

CodeHTTPWhen
SQL_ERROR—SQLite refused the statement.

Example

ts
const { results } = await db.prepare("SELECT 'Ana' AS name, 42 AS age").all();

Result:

json
{
  "success": true,
  "meta": { "served_by_region": "WEUR", "served_by_primary": true, "duration": 0.1934, "changes": 0, "last_row_id": 0, "changed_db": false, "size_after": 12288, "rows_read": 0, "rows_written": 0 },
  "results": [{ "name": "Ana", "age": 42 }]
}

#stmt.raw

ts
stmt.raw(options: { columnNames }): Promise<[string[], ...T[]]>
stmt.raw(options?: { columnNames? }): Promise<T[]>

Runs a query and returns the rows as arrays of values (no column names), or with columnNames: true the names first.

Parameters

NameTypeRequiredDescription
options{ columnNames? }no{ columnNames: true } puts the column names first.
options.columnNamesbooleannotrue: the first array is the column names.

Returns Promise<T[]> — The rows as arrays.

Errors — besides the errors every call can throw:

CodeHTTPWhen
SQL_ERROR—SQLite refused the statement.

Example

ts
const rows = await db.prepare('SELECT id, name FROM users LIMIT 2').raw({ columnNames: true });

Result:

json
[["id", "name"], [41, "Bob"], [42, "Ana"]]

#D1Result

What run(), all() and each batch() statement return: the rows and the statement's metadata.

FieldTypeAlways presentDescription
resultsT[]yesThe rows, as objects keyed by column name (empty for a statement that returns none).
successtrueyesAlways true: a failure throws instead.
meta{ duration, size_after, rows_read, rows_written, last_row_id, changed_db, changes, served_by_region?, served_by_primary? }yesTiming, rows read and written, changes and the last inserted rowid.
meta.durationnumberyesHow long the statement took in the database, in ms.
meta.size_afternumberyesThe database's size after the statement, in bytes.
meta.rows_readnumberyesRows the statement read (what makes a query slow: add an index when it is high).
meta.rows_writtennumberyesRows the statement wrote.
meta.last_row_idnumberyesThe rowid of the last inserted row (INSERT without RETURNING).
meta.changed_dbbooleanyesWhether the database changed.
meta.changesnumberyesRows inserted, updated or deleted.
meta.served_by_regionstringnoThe region that answered (with read replicas).
meta.served_by_primarybooleannoWhether the primary database answered (false for a read replica).

#TotalumD1ClientCode

The codes the database driver itself throws (before or without the Totalum API): no key, an unsupported call, a statement of another instance in a batch, the network, an unreadable answer.

ValueMeaning
'SDK_NOT_CONFIGURED'No binding was found and TOTALUM_PROJECT_KEY is unset.
'NOT_SUPPORTED'The call is not available (dump()).
'BATCH_FOREIGN_STATEMENT'batch() got a statement prepared by another totalumD1() instance.
'NETWORK_ERROR'The Totalum API could not be reached (HTTPS path).
'RESPONSE_PARSE_ERROR'The answer was not the expected JSON (HTTPS path).

#TotalumD1Error

The database TotalumError: message is D1_ERROR: …-shaped so it reads like a native failure — byte-identical to the binding's message when the failure is SQLite's.

MemberTypeDescription
namestring'TotalumD1Error', or 'Error' for SQLite's own failures (as the native binding throws them).
MethodDescription
TotalumD1Error.client(errorCode, message) → TotalumD1ErrorA client-side refusal (D1_ERROR: <code>: <message>).
TotalumD1Error.engine(sqliteMessage, options?) → TotalumD1ErrorSQLite's own failure, as the binding throws it: name Error, message D1_ERROR: <sqlite>, cause an Error with SQLite's words — so String(err), err.message and String(err.cause) read the same on every path. Still a TotalumD1Error (errorCode SQL_ERROR) for code that checks.
TotalumD1Error.fromWire(errors, status, requestId) → TotalumD1ErrorThe Totalum API error envelope → typed error; SQL_ERROR is rebuilt as the binding's own error from sqliteMessage.

#TotalumD1ErrorCode

Deprecated. Since 1.0.0: use TotalumErrorCode (the same type).

Deprecated: Since 1.0.0: use TotalumErrorCode (the same type).

ts
type TotalumD1ErrorCode = 'SERVICE_UNAUTHORIZED' | 'SERVICE_REPLAY' | 'ACTOR_MISMATCH' | 'PAYLOAD_TOO_LARGE' | 'VALIDATION_ERROR' | 'PROJECT_NOT_FOUND' | 'PROJECT_NOT_READY' | 'OPERATION_IN_PROGRESS' | … // 421 values

#TotalumD1Options

The options of totalumD1(). key, apiUrl and env (HTTPS path only) resolve exactly as in every other namespace.

NameTypeRequiredDescription
mode'query' | 'migrate'no'migrate' (scripts/migrate.ts only) sends x-totalum-migrate: 1 and enables exec(); default 'query'.
destructivebooleannoMigrate mode only: sends x-totalum-migrate-destructive: 1.
bindingD1DatabasenoAn explicit binding wins over detection (plain Workers: env.TOTALUM_DB; tests: a Miniflare D1).
transport'auto' | 'https'no'https' forces the HTTPS path even inside a Worker (the conformance corpus); default 'auto'.
keystringnoProject key tlm_pk_…; default TOTALUM_PROJECT_KEY. Server-side only.
apiUrlstringnoThe Totalum API's URL: default TOTALUM_SDK_API_URL, else https://sdk-api.totalum.app.
env'dev' | 'live'noSent as x-totalum-env on every call (the database it targets, and dev vs live usage); default TOTALUM_ENV, else live.