Skip to content
Discord Get Started

TypeScript SDK

The get-db9 SDK provides typed TypeScript access to DB9 databases — provisioning, SQL execution, filesystem operations, branching, and token management. This is the server-side SDK for Node.js environments.

Client-side? For browser and edge environments, use the @db9/browser SDK — it provides a query builder with Row-Level Security enforcement and publishable key authentication.

ScenarioRecommended tool
One-liner database provisioning in a script or testSDK — instantDatabase()
Programmatic fleet management (create, branch, delete)SDK — createDb9Client()
File read/write from Node.js (RAG, ingestion)SDK — FsClient (see the 0.6.1 connection workaround)
Interactive SQL from a terminalCLI — db9 db sql (CLI Reference)
ORM or driver connection to an existing databaseRaw pgwire — use the connection string (Connect)
Agent onboarding (Codex, Claude Code)CLI — db9 onboard (Agent Workflows)

Runtime requirement: Node.js 18+ (native fetch). TypeScript 5+ for full type exports.

Terminal
npm install get-db9

Also available via yarn add get-db9, pnpm add get-db9, or bun add get-db9.

View the package on npm.

The SDK shares the credential store (~/.db9/credentials) with the db9 CLI. If you have logged in via the CLI, the SDK picks up the token automatically.

The SDK works with any Node.js framework. For framework-specific setup guides with connection patterns and best practices:

  • Next.js — Server Components, Server Actions, Route Handlers
  • Express / Hono — middleware and route patterns
  • SvelteKit — server-only load functions
  • Nuxt — server API routes
  • Remix — loaders and actions
  • Astro — SSR mode with Node adapter

For Python, Ruby, Go, and PHP frameworks, use the connection string and admin password from GET /customer/databases/{id}/credentials (client.databases.credentials(id) in this SDK) with your language’s PostgreSQL driver — the connection strings the SDK returns carry no password. See Connect for driver-specific examples.

instantDatabase() creates or reuses a database by name. If no name is provided, it defaults to "default".

TypeScript
import { instantDatabase } from 'get-db9';
const db = await instantDatabase({
name: 'myapp',
seed: 'CREATE TABLE users (id SERIAL PRIMARY KEY, email TEXT)'
});
console.log(db.databaseId);
console.log(db.connectionString);
console.log(db.adminUser, db.adminPassword);
console.log(db.state, db.createdAt);

adminUser is only the role name (admin). When connectionString is present, it already includes the full username (<tenant_id>.admin) for a direct PostgreSQL connection. Use that connection string for pgwire so the tenant ID prefix is preserved.

Result shape:

TypeScript
interface InstantDatabaseResult {
databaseId: string;
connectionString: string;
adminUser: string;
adminPassword: string;
state: string;
createdAt: string;
}

connectionString has no password. adminPassword is set only on the call that creates the database; when instantDatabase() returns an existing database, connectionString, adminUser and adminPassword are empty strings. To connect on any call, read the stored credentials — see Connect:

TypeScript
const creds = await createDb9Client().databases.credentials(db.databaseId);
const url = new URL(creds.connection_string);
url.password = creds.admin_password;
url.searchParams.set('sslmode', 'verify-full');

High-level API that wraps createDb9Client(), checks for an existing database by name, creates one if missing, and optionally executes seed SQL. Seed SQL runs only on initial creation — if the database already exists, seed is skipped.

TypeScript
function instantDatabase(
options?: InstantDatabaseOptions
): Promise<InstantDatabaseResult>;
OptionTypeDescription
namestringDatabase name. Default: 'default'.
baseUrlstringOverride API endpoint. Default: https://db9.ai/api. Version 0.6.1 does not read DB9_API_URL; pass this option explicitly.
fetchFetchFnCustom fetch implementation.
credentialStoreCredentialStoreToken load/save strategy.
seedstringSQL text executed via client.databases.sql() on creation only.
seedFilestringSQL file content executed via client.databases.sqlFile() on creation only.
timeoutnumberRequest timeout in milliseconds.
maxRetriesnumberRetry count for failed requests (capped at 3).
retryDelaynumberDelay between retries in milliseconds.
TypeScript
const db = await instantDatabase({
name: 'analytics',
seedFile: `
CREATE TABLE events (
id BIGSERIAL PRIMARY KEY,
user_id TEXT NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
`
});

Low-level typed client exposing grouped APIs: auth, tokens, databases, and fs.

The client lazy-loads the token from the credential store on the first protected call. If no token is found, it throws Db9Error (status 401). Use db9 login or pass a token via options.token before calling protected methods.

TypeScript
function createDb9Client(options?: Db9ClientOptions): Db9Client;
OptionTypeDescription
baseUrlstringDefault: https://db9.ai/api. Version 0.6.1 does not read DB9_API_URL; pass this option explicitly.
tokenstringBearer token; skips credential store lookup.
fetchFetchFnCustom HTTP implementation.
credentialStoreCredentialStoreLoad/save token state.
timeoutnumberRequest timeout in milliseconds.
maxRetriesnumberRetry count for failed requests (capped at 3).
retryDelaynumberDelay between retries in milliseconds.
WebSocketWebSocketConstructorOverride WebSocket for client.fs operations (see Filesystem).
wsPortnumberOverride the fs9 WebSocket port (default: 5480).
TypeScript
import { createDb9Client, MemoryCredentialStore } from 'get-db9';
const client = createDb9Client({
baseUrl: 'https://api.db9.ai',
credentialStore: new MemoryCredentialStore()
});
  • me(): Promise<CustomerResponse> — Get current user profile

The SDK itself does not handle login or registration. Use the CLI for auth:

Terminal
# Zero-setup trial (auto creates anonymous account + token)
db9 create --name quickstart
# Upgrade anonymous account to verified SSO identity
db9 claim
# Human operator login (browser-based)
db9 login
# API key login (CI/CD, agents)
db9 login --api-key <KEY>
# Create an automation token
db9 token create --name my-agent --expires-in-days 365
# Agent runtime
export DB9_API_KEY=<token>

Once authenticated via CLI, the SDK picks up the stored token automatically.

Create, inspect, and revoke API tokens for CI/CD and programmatic access.

  • create(req: CreateTokenRequest): Promise<CreateTokenResponse> — Create named token with optional expiry
  • list(): Promise<TokenResponse[]>
  • revoke(tokenId: string): Promise<MessageResponse>
TypeScript
const newToken = await client.tokens.create({
name: 'ci-deploy',
expires_in_days: 90
});
console.log(newToken.token); // Store in secret manager
const tokens = await client.tokens.list();
for (const token of tokens) {
console.log(token.id, token.name, token.created_at, token.expires_at);
}
await client.tokens.revoke(tokens[0].id);

Core lifecycle APIs: create, list, get, delete, reset password, retrieve credentials, and read observability metrics.

  • create(req: CreateDatabaseRequest): Promise<DatabaseResponse>
  • list(): Promise<DatabaseResponse[]>
  • get(databaseId: string): Promise<DatabaseResponse>
  • delete(databaseId: string): Promise<MessageResponse>
  • resetPassword(databaseId: string): Promise<CustomerPasswordResetResponse>
  • credentials(databaseId: string): Promise<CustomerPasswordResetResponse> — Get stored admin credentials without resetting
  • observability(databaseId: string): Promise<TenantObservabilityResponse>
TypeScript
const db = await client.databases.create({
name: 'billing',
region: 'us-west-2',
admin_password: 'StrongAdminPass1'
});
const all = await client.databases.list();
const current = await client.databases.get(db.id);
const rotated = await client.databases.resetPassword(db.id);
const creds = await client.databases.credentials(db.id);
const metrics = await client.databases.observability(db.id);
await client.databases.delete(db.id);

For programmatic provisioning patterns, see Provisioning.

Execute SQL strings or SQL file content through the customer API. Both methods return SqlResult.

  • sql(databaseId: string, query: string): Promise<SqlResult>
  • sqlFile(databaseId: string, fileContent: string): Promise<SqlResult>
TypeScript
const result = await client.databases.sql(
databaseId,
'SELECT id, email FROM users ORDER BY id LIMIT 10'
);
console.log(result.columns);
console.log(result.rows);
console.log(result.row_count, result.command, result.error);
const fromFile = await client.databases.sqlFile(databaseId, `
CREATE TABLE audit_log (id BIGSERIAL PRIMARY KEY, event TEXT);
INSERT INTO audit_log(event) VALUES ('created');
`);
SqlResult FieldTypeDescription
columnsColumnInfo[]Column metadata for result rows.
rowsunknown[][]Result values matrix. NUMERIC values arrive as JavaScript numbers (rounded to a 64-bit float, scale dropped), and BIGINT values above 2^53 can be rounded — cast to text (amount::text) to keep them exact. Floating-point NaN, Infinity and -Infinity arrive as the strings 'NaN', 'Infinity' and '-Infinity', so a float column can mix numbers and strings. See SQL Execution.
row_countnumberRows affected/returned.
commandstringExecuted command label (SELECT, INSERT, etc.).
errorstring | SqlErrorDetailStructured error with message, code, detail, hint, and position.

Introspect schema objects or export SQL dump payloads.

  • schema(databaseId: string): Promise<SchemaResponse>
  • dump(databaseId: string, req?: DumpRequest): Promise<DumpResponse>
TypeScript
const schema = await client.databases.schema(databaseId);
for (const table of schema.tables) {
console.log(table.schema, table.name);
}
const ddlOnly = await client.databases.dump(databaseId, { ddl_only: true });
console.log(ddlOnly.object_count);
console.log(ddlOnly.sql);

Apply SQL migrations with checksums and inspect migration history.

  • applyMigration(databaseId: string, req: MigrationApplyRequest): Promise<MigrationApplyResponse>
  • listMigrations(databaseId: string): Promise<MigrationMetadata[]>
TypeScript
await client.databases.applyMigration(databaseId, {
name: '20260218_add_users',
sql: 'CREATE TABLE users (id SERIAL PRIMARY KEY, email TEXT NOT NULL);',
checksum: 'f0b9c43b'
});
const applied = await client.databases.listMigrations(databaseId);
for (const migration of applied) {
console.log(migration.name, migration.applied_at, migration.checksum);
}

Create a database branch from an existing database. Branch creation starts in CLONING state — poll with get() until it reaches ACTIVE or CREATE_FAILED.

  • branch(databaseId: string, req: BranchRequest): Promise<DatabaseResponse>
TypeScript
const featureDb = await client.databases.branch(databaseId, {
name: 'feature-auth'
});
let current = featureDb;
while (current.state === 'CLONING') {
await new Promise((resolve) => setTimeout(resolve, 1000));
current = await client.databases.get(featureDb.id);
}
if (current.state === 'CREATE_FAILED') {
throw new Error('Branch clone failed');
}
console.log('Branch ready:', current.id, current.name);

For branch workflow patterns, see Multi-Tenant Patterns.

Manage Postgres users inside a database.

  • list(databaseId: string): Promise<UserResponse[]>
  • create(databaseId: string, req: CreateUserRequest): Promise<MessageResponse>
  • delete(databaseId: string, username: string): Promise<MessageResponse>
TypeScript
await client.databases.users.create(databaseId, {
username: 'app_user',
password: 'AppUserPass!'
});
const users = await client.databases.users.list(databaseId);
users.forEach((u) => {
console.log(u.name, u.can_login, u.can_create_db, u.is_superuser);
});
await client.databases.users.delete(databaseId, 'app_user');

Cloud filesystem operations for reading, writing, and managing files attached to each database. Built for RAG pipelines, document ingestion, and agent workflows.

  • connect(dbId): Promise<FsClient> — Open an authenticated persistent WebSocket connection (caller must call close(); subject to the 0.6.1 limitation above)
  • list(dbId, path): Promise<FileInfo[]> — List directory contents
  • read(dbId, path): Promise<string> — Read file content as text
  • readBinary(dbId, path): Promise<Uint8Array> — Read file as binary
  • write(dbId, path, content): Promise<void> — Write a file
  • append(dbId, path, content): Promise<number> — Append to a file, returns bytes written
  • stat(dbId, path): Promise<FileInfo> — Get file metadata
  • exists(dbId, path): Promise<boolean> — Check if file exists
  • mkdir(dbId, path): Promise<void> — Create a directory recursively
  • remove(dbId, path, opts?): Promise<void> — Delete a file or directory
  • rename(dbId, oldPath, newPath): Promise<void> — Move or rename a path

Install the WebSocket implementation used by this example:

Terminal
npm install get-db9 ws
npm install -D @types/ws
TypeScript
import WebSocket from 'ws';
import {
createDb9Client,
FsClient,
type CustomerPasswordResetResponse,
type WebSocketConstructor,
} from 'get-db9';
const client = createDb9Client();
const dbId = 'my-database-id';
// The API returns this field; the published 0.6.1 type does not declare it.
const credentials = await client.databases.credentials(dbId) as
CustomerPasswordResetResponse & { fs_websocket_url?: string };
if (!credentials.fs_websocket_url) {
throw new Error('No filesystem WebSocket endpoint was returned');
}
const fs = await FsClient.connect(
credentials.fs_websocket_url,
WebSocket as unknown as WebSocketConstructor,
);
try {
const username = decodeURIComponent(new URL(credentials.connection_string).username);
await fs.authenticate(username, credentials.admin_password);
await fs.mkdir('/sdk-example');
await fs.writeFile('/sdk-example/hello.txt', 'Hello from db9!');
const content = new TextDecoder().decode(await fs.readFile('/sdk-example/hello.txt'));
console.log(content);
const files = await fs.readdir('/sdk-example');
console.log(files);
const info = await fs.stat('/sdk-example/hello.txt');
console.log(info.type, info.size, info.mtime);
await fs.appendFile('/sdk-example/hello.txt', '\nHello again');
await fs.rename('/sdk-example/hello.txt', '/sdk-example/hello-2.txt');
await fs.rm('/sdk-example/hello-2.txt');
await fs.rm('/sdk-example');
} finally {
await fs.close();
}
FieldTypeDescription
pathstringFull file path.
sizenumberFile size in bytes.
type'file' | 'dir'Entry type.
modenumberUnix file mode.
mtimestringLast modified time as RFC 3339 text.
OptionTypeDescription
recursivebooleanRemove directories recursively (default: false).

FsClient.connect(url, WebSocket) opens a persistent connection; call authenticate() before using it. client.fs.connect(dbId) normally authenticates automatically, but has the 0.6.1 endpoint limitation described above. The FsClient methods differ slightly from the convenience methods:

  • authenticate(username: string, password: string): Promise<FsAuthInfo> — Authenticate the connection. Must be called first after connecting.
  • stat(path: string): Promise<FileInfo> — Get file metadata
  • readdir(path: string): Promise<FileInfo[]> — List directory contents (note: readdir, not list)
  • mkdir(path: string, recursive?: boolean): Promise<void> — Create a directory
  • readFile(path: string): Promise<Uint8Array> — Read an entire file as raw bytes (not a string)
  • writeFile(path: string, data: Uint8Array | ArrayBuffer | string): Promise<number> — Overwrite a file, returns bytes written
  • appendFile(path: string, data: Uint8Array | ArrayBuffer | string): Promise<number> — Append to a file, returns bytes written
  • rm(path: string, recursive?: boolean): Promise<void> — Delete a file or directory (note: rm, not remove, and it takes a boolean — not the options object that client.fs.remove() accepts)
  • rename(oldPath: string, newPath: string): Promise<void> — Move or rename a path
  • close(): Promise<void> — Gracefully close the WebSocket connection

Credential stores implement a shared async interface used by client auto-auth.

  • FileCredentialStore(path?) — TOML file store at ~/.db9/credentials (shared with db9 CLI)
  • MemoryCredentialStore — Volatile in-memory store for tests and serverless
  • defaultCredentialStore() — Factory that returns new FileCredentialStore()
TypeScript
import {
createDb9Client,
FileCredentialStore,
MemoryCredentialStore,
defaultCredentialStore
} from 'get-db9';
const fileStore = new FileCredentialStore();
const customStore = new FileCredentialStore('/tmp/db9-credentials.toml');
const memStore = new MemoryCredentialStore();
const client = createDb9Client({ credentialStore: fileStore });

API failures throw named Db9Error subclasses based on HTTP status code.

  • Db9Error — Base class with statusCode, message, and optional response
  • Db9AuthError — Status 401
  • Db9NotFoundError — Status 404
  • Db9ConflictError — Status 409 (e.g., duplicate database name)

Filesystem operations (client.fs.* and FsClient) throw FsError instead — it is a separate class, not a Db9Error subclass.

TypeScript
import {
createDb9Client,
Db9Error,
Db9AuthError,
Db9NotFoundError,
Db9ConflictError
} from 'get-db9';
const client = createDb9Client();
try {
await client.databases.get('missing-id');
} catch (error) {
if (error instanceof Db9NotFoundError) {
console.error('Database not found');
} else if (error instanceof Db9AuthError) {
console.error('Authentication required — run `db9 login` first');
} else if (error instanceof Db9ConflictError) {
console.error('Conflict (e.g., duplicate name)');
} else if (error instanceof Db9Error) {
console.error(`db9 API error ${error.statusCode}: ${error.message}`);
} else {
throw error;
}
}

The package re-exports all interfaces from ./types in addition to client, credential, and filesystem types.

Already documented inline: InstantDatabaseResult (see Quick Start), SqlResult and ColumnInfo (see SQL Execution), FileInfo and FsRemoveOptions (see Filesystem).

Returned by create(), list(), get(), and branch().

TypeScript
interface DatabaseResponse {
id: string;
name: string;
state: string;
parent_database_id?: string;
region?: string;
endpoints?: Endpoint[];
admin_user?: string;
admin_password?: string;
created_at: string;
connection_string?: string;
}
FieldTypeDescription
idstringUnique database identifier.
namestringHuman-readable database name.
statestringLifecycle state: CREATING, ACTIVE, CLONING, DISABLING, DISABLED, CREATE_FAILED.
parent_database_idstring?Source database ID for branch databases.
regionstring?Region as supplied at creation (default us-west-2).
endpointsEndpoint[]?Connection endpoints (host, port, type).
admin_userstring?Admin role name (admin); the PostgreSQL wire username includes the tenant ID prefix, as in <tenant_id>.admin.
admin_passwordstring?Admin Postgres password. Only present in create() and branch() responses.
created_atstringISO 8601 creation timestamp.
connection_stringstring?postgresql:// URI without the password.

Returned by client.auth.me().

TypeScript
interface CustomerResponse {
id: string;
email: string;
created_at: string;
status: string;
}
FieldTypeDescription
idstringCustomer account identifier.
emailstringAccount email address. Empty for anonymous accounts.
created_atstringISO 8601 account creation timestamp.
statusstringAccount status: active, anonymous.

Returned by client.tokens.list(). Metadata only — does not include the token secret.

TypeScript
interface TokenResponse {
id: string;
name: string;
created_at: string;
expires_at?: string;
}
FieldTypeDescription
idstringToken identifier. Use this to revoke the token.
namestringHuman-readable token label.
created_atstringISO 8601 creation timestamp.
expires_atstring?ISO 8601 expiry timestamp. undefined means never expires.

Returned by client.tokens.create(). Includes the token secret — store it immediately.

TypeScript
interface CreateTokenResponse {
id: string;
name: string;
token: string;
created_at: string;
expires_at?: string;
}
FieldTypeDescription
idstringToken identifier.
namestringHuman-readable token label.
tokenstringThe secret token value. Shown only once — store in a secret manager.
created_atstringISO 8601 creation timestamp.
expires_atstring?ISO 8601 expiry timestamp.

Returned by client.databases.schema().

TypeScript
interface SchemaResponse {
tables: TableMetadata[];
views: ViewMetadata[];
}
FieldTypeDescription
tablesTableMetadata[]All tables in the database.
viewsViewMetadata[]All views in the database.
TypeScript
interface TableMetadata {
name: string;
schema: string;
columns: ColumnMetadata[];
}
FieldTypeDescription
namestringTable name.
schemastringPostgres schema name (e.g. public).
columnsColumnMetadata[]Column definitions for this table.
TypeScript
interface ColumnMetadata {
name: string;
type: string;
nullable: boolean;
default_value?: string;
}
FieldTypeDescription
namestringColumn name.
typestringPostgres data type (e.g. text, integer, timestamp).
nullablebooleanWhether the column accepts NULL.
default_valuestring?Default expression, if defined.
TypeScript
interface ViewMetadata {
name: string;
schema: string;
}
FieldTypeDescription
namestringView name.
schemastringPostgres schema name.

Returned by client.databases.dump().

TypeScript
interface DumpResponse {
sql: string;
object_count: number;
}
FieldTypeDescription
sqlstringFull SQL dump text.
object_countnumberNumber of database objects included in the dump.

Returned by client.databases.observability().

TypeScript
interface TenantObservabilityResponse {
summary: ObservabilitySummary;
samples: QuerySample[];
}
FieldTypeDescription
summaryObservabilitySummaryAggregate metrics for the observation window.
samplesQuerySample[]Per-query performance samples.
TypeScript
interface ObservabilitySummary {
window_seconds: number;
statement_count: number;
txn_commit_count: number;
error_count: number;
qps: number;
tps: number;
latency_avg_ms: number;
latency_p99_ms: number;
active_connections: number;
}
FieldTypeDescription
window_secondsnumberObservation window length in seconds.
statement_countnumberTotal statements executed in the window.
txn_commit_countnumberTotal committed transactions.
error_countnumberTotal statement errors.
qpsnumberAverage queries per second.
tpsnumberAverage transactions per second.
latency_avg_msnumberMean query latency in milliseconds.
latency_p99_msnumber99th-percentile query latency in milliseconds.
active_connectionsnumberCurrent active connection count on the server node that answered — not a database-wide total; see Observability.
TypeScript
interface QuerySample {
query: string;
sample_count: number;
error_count: number;
latency_avg_ms: number;
latency_p99_ms: number;
latency_max_ms: number;
last_seen_ms_ago: number;
}
FieldTypeDescription
querystringNormalized query text: every literal value is replaced with ? (for example SELECT ? :: INT AS x , ? AS y).
sample_countnumberNumber of times this query pattern was observed.
error_countnumberNumber of times this query pattern produced an error.
latency_avg_msnumberMean execution latency in milliseconds.
latency_p99_msnumber99th-percentile latency in milliseconds.
latency_max_msnumberMaximum observed latency in milliseconds.
last_seen_ms_agonumberMilliseconds since this query was last observed.

Returned by client.databases.resetPassword() and client.databases.credentials().

TypeScript
interface CustomerPasswordResetResponse {
admin_user: string;
admin_password: string;
connection_string: string;
}
FieldTypeDescription
admin_userstringAdmin role name (admin); use the username in connection_string for a direct connection because it includes the tenant ID prefix.
admin_passwordstringAdmin Postgres password (new password after reset).
connection_stringstringpostgresql:// URI without the password — add admin_password to connect.

Returned by client.databases.users.list().

TypeScript
interface UserResponse {
name: string;
is_superuser: boolean;
can_login: boolean;
can_create_db: boolean;
can_create_role: boolean;
}
FieldTypeDescription
namestringPostgres username.
is_superuserbooleanWhether the user has superuser privileges.
can_loginbooleanWhether the user can open connections.
can_create_dbbooleanWhether the user can create databases.
can_create_rolebooleanWhether the user can create roles.

Returned by client.databases.applyMigration().

TypeScript
interface MigrationApplyResponse {
status: string;
name: string;
}
FieldTypeDescription
statusstringMigration result: applied or skipped (already applied).
namestringMigration name as provided in the request.

Returned by client.databases.listMigrations().

TypeScript
interface MigrationMetadata {
name: string;
checksum: string;
applied_at: string;
sql_preview: string;
}
FieldTypeDescription
namestringMigration name.
checksumstringChecksum provided at apply time.
applied_atstringApplication time. User-applied migrations return RFC 3339 text; built-in server migrations can return Unix seconds as a numeric string. The list includes both kinds.
sql_previewstringTruncated SQL for display purposes.

Returned by delete and revoke operations.

TypeScript
interface MessageResponse {
message: string;
}
FieldTypeDescription
messagestringHuman-readable confirmation message.
TypeKey Fields
CreateDatabaseRequestname: string, region?: string, admin_password?: string
CreateTokenRequestname?: string, expires_in_days?: number
BranchRequestname: string
DumpRequestddl_only?: boolean
CreateUserRequestusername: string, password: string
MigrationApplyRequestname: string, sql: string, checksum: string
TypeDescription
TenantStateUnion: 'CREATING' | 'ACTIVE' | 'DISABLING' | 'DISABLED' | 'CREATE_FAILED'
SqlErrorDetailStructured SQL error: message, code?, position?, hint?, detail?
EndpointConnection endpoint: host, port, type, region?, priority, enabled, description?
InstantDatabaseOptionsOptions for instantDatabase() — see Quick Start
Db9ClientOptionsOptions for createDb9Client() — see createDb9Client
CredentialStoreInterface for token load/save — see Credential Storage
FetchFnCustom fetch implementation type
FsListOptions, FsConnectOptionsFilesystem operation options