Skip to main content
doc0blade47/drizzle-ormdocumentation home
Sign in

Edge and Mobile SQLite

Last updated Oct 12, 2026100% grounded
Relevant source files

The following files were used as context for generating this wiki page:

Overview

Drizzle ORM provides dedicated driver integrations and session handlers for running SQLite databases across edge runtimes, serverless workers, WebAssembly environments, and native mobile applications. These drivers bridge Drizzle's synchronous and asynchronous query dialects with target environments such as Cloudflare D1, Cloudflare Durable Objects storage, Expo SQLite for React Native, OP-SQLite bindings, sql.js WebAssembly instances, and remote RPC sqlite-proxy endpoints. Each driver configures customized statement preparation, batch execution, and transaction lifecycle management tailored to the constraints and performance characteristics of its underlying runtime.

Sources: drizzle-orm/src/durable-sqlite/driver.ts:1-60, drizzle-orm/src/d1/driver.ts:1-78, drizzle-orm/src/sql-js/driver.ts:1-45, drizzle-orm/src/expo-sqlite/driver.ts:1-53, drizzle-orm/src/sqlite-proxy/driver.ts:1-100, drizzle-orm/src/op-sqlite/driver.ts:1-57, drizzle-orm/src/d1/session.ts:30-126, drizzle-orm/src/durable-sqlite/session.ts:23-72, drizzle-orm/src/sql-js/session.ts:24-66

Cloudflare D1 Driver Architecture

Driver Initialization and Client Configuration

The Cloudflare D1 integration initializes through the drizzle() entrypoint function which accepts a D1 database client (AnyD1Database, supporting both official @cloudflare/workers-types and Miniflare D1 bindings) and an optional DrizzleConfig parameter. It configures an asynchronous SQLite dialect (SQLiteAsyncDialect) utilizing the specified casing configuration, instantiates a logger (defaulting to DefaultLogger when config.logger is true), and extracts relational schema configurations if provided via config.schema.

export function drizzle<
	TSchema extends Record<string, unknown> = Record<string, never>,
	TClient extends AnyD1Database = AnyD1Database,
>(
	client: TClient,
	config: DrizzleConfig<TSchema> = {},
): DrizzleD1Database<TSchema> & {
	$client: TClient;
} {
	const dialect = new SQLiteAsyncDialect({ casing: config.casing });
	let logger;
	if (config.logger === true) {
		logger = new DefaultLogger();
	} else if (config.logger !== false) {
		logger = config.logger;
	}
 
	let schema: RelationalSchemaConfig<TablesRelationalConfig> | undefined;
	if (config.schema) {
		const tablesConfig = extractTablesRelationalConfig(
			config.schema,
			createTableRelationsHelpers,
		);
		schema = {
			fullSchema: config.schema,
			schema: tablesConfig.tables,
			tableNamesMap: tablesConfig.tableNamesMap,
		};
	}
 
	const session = new SQLiteD1Session(client as D1Database, dialect, schema, { logger, cache: config.cache });
	const db = new DrizzleD1Database('async', dialect, session, schema) as DrizzleD1Database<TSchema>;
	(<any> db).$client = client;
	(<any> db).$cache = config.cache;
	if ((<any> db).$cache) {
		(<any> db).$cache['invalidate'] = config.cache?.onMutate;
	}
 
	return db as any;
}

Sources: drizzle-orm/src/d1/driver.ts:39-78

The initialization function wires up SQLiteD1Session with the client, dialect, schema, and operational options including caching. It then constructs a DrizzleD1Database instance, attaches the raw client as $client, and exposes cache invalidation handlers on $cache when present.

const session = new SQLiteD1Session(client as D1Database, dialect, schema, { logger, cache: config.cache });
const db = new DrizzleD1Database('async', dialect, session, schema) as DrizzleD1Database<TSchema>;
(<any> db).$client = client;
(<any> db).$cache = config.cache;
if ((<any> db).$cache) {
	(<any> db).$cache['invalidate'] = config.cache?.onMutate;
}

Sources: drizzle-orm/src/d1/driver.ts:69-76

Async Dialect Integration and Session Management

SQLiteD1Session extends SQLiteSession with an 'async' execution mode and handles query preparation by invoking client.prepare(query.sql) on the underlying D1 database handle. Prepared statements are wrapped in D1PreparedQuery instances that manage parameter binding, query logging, caching execution, and result mapping.

prepareQuery(
	query: Query,
	fields: SelectedFieldsOrdered | undefined,
	executeMethod: SQLiteExecuteMethod,
	isResponseInArrayMode: boolean,
	customResultMapper?: (rows: unknown[][]) => unknown,
	queryMetadata?: {
		type: 'select' | 'update' | 'delete' | 'insert';
		tables: string[];
	},
	cacheConfig?: WithCacheConfig,
): D1PreparedQuery {
	const stmt = this.client.prepare(query.sql);
	return new D1PreparedQuery(
		stmt,
		query,
		this.logger,
		this.cache,
		queryMetadata,
		cacheConfig,
		fields,
		executeMethod,
		isResponseInArrayMode,
		customResultMapper,
	);
}

Sources: drizzle-orm/src/d1/session.ts:50-75

Batch operations executed through session.batch() iterate over batch items, extract underlying prepared queries, bind parameters when present, or prepare fresh SQL statements if parameters are absent, before dispatching them concurrently via client.batch().

async batch<T extends BatchItem<'sqlite'>[] | readonly BatchItem<'sqlite'>[]>(queries: T) {
	const preparedQueries: PreparedQuery[] = [];
	const builtQueries: D1PreparedStatement[] = [];
 
	for (const query of queries) {
		const preparedQuery = query._prepare();
		const builtQuery = preparedQuery.getQuery();
		preparedQueries.push(preparedQuery);
		if (builtQuery.params.length > 0) {
			builtQueries.push((preparedQuery as D1PreparedQuery).stmt.bind(...builtQuery.params));
		} else {
			const builtQuery = preparedQuery.getQuery();
			builtQueries.push(
				this.client.prepare(builtQuery.sql).bind(...builtQuery.params),
			);
		}
	}
 
	const batchResults = await this.client.batch<any>(builtQueries);
	return batchResults.map((result, i) => preparedQueries[i]!.mapResult(result, true));
}

Sources: drizzle-orm/src/d1/session.ts:77-97

Transaction Lifecycle and Statement Execution

Transactions are managed explicitly via SQL commands issued against the session. The transaction() method initiates a block by executing begin with optional behavior configuration, runs the transaction callback with a D1Transaction instance, commits on success, or rolls back upon catching any error.

override async transaction<T>(
	transaction: (tx: D1Transaction<TFullSchema, TSchema>) => T | Promise<T>,
	config?: SQLiteTransactionConfig,
): Promise<T> {
	const tx = new D1Transaction('async', this.dialect, this, this.schema);
	await this.run(sql.raw(`begin${config?.behavior ? ' ' + config.behavior : ''}`));
	try {
		const result = await transaction(tx);
		await this.run(sql`commit`);
		return result;
	} catch (err) {
		await this.run(sql`rollback`);
		throw err;
	}
}

Sources: drizzle-orm/src/d1/session.ts:111-125

Nested transactions within D1Transaction rely on SQLite savepoints. Each nested level generates a unique savepoint name using its nestedIndex, executing savepoint spN, releasing it upon completion, or rolling back to the savepoint on failure.

override async transaction<T>(transaction: (tx: D1Transaction<TFullSchema, TSchema>) => Promise<T>): Promise<T> {
	const savepointName = `sp${this.nestedIndex}`;
	const tx = new D1Transaction('async', this.dialect, this.session, this.schema, this.nestedIndex + 1);
	await this.session.run(sql.raw(`savepoint ${savepointName}`));
	try {
		const result = await transaction(tx);
		await this.session.run(sql.raw(`release savepoint ${savepointName}`));
		return result;
	} catch (err) {
		await this.session.run(sql.raw(`rollback to savepoint ${savepointName}`));
		throw err;
	}
}

Sources: drizzle-orm/src/d1/session.ts:134-146

Note

D1PreparedQuery methods (run, all, get, values) substitute query placeholders using fillPlaceholders(), log parameters through the configured logger, and optionally wrap execution in queryWithCache() before delegating directly to D1's .run(), .all(), or .raw() statement bindings.

Sources: drizzle-orm/src/d1/session.ts:199-283

Cloudflare Durable Objects Storage Driver

Driver Initialization and Synchronous Dialect Setup

The Cloudflare Durable Objects storage driver initializes through the drizzle() entrypoint function which accepts a DurableObjectStorage client and an optional DrizzleConfig parameter. It instantiates a synchronous SQLite dialect (SQLiteSyncDialect) using the specified casing configuration, configures logging through DefaultLogger or custom loggers, and extracts relational schema configurations when supplied.

export function drizzle<
	TSchema extends Record<string, unknown> = Record<string, never>,
	TClient extends DurableObjectStorage = DurableObjectStorage,
>(
	client: TClient,
	config: DrizzleConfig<TSchema> = {},
): DrizzleSqliteDODatabase<TSchema> & {
	$client: TClient;
} {
	const dialect = new SQLiteSyncDialect({ casing: config.casing });
	let logger;
	if (config.logger === true) {
		logger = new DefaultLogger();
	} else if (config.logger !== false) {
		logger = config.logger;
	}
 
	let schema: RelationalSchemaConfig<TablesRelationalConfig> | undefined;
	if (config.schema) {
		const tablesConfig = extractTablesRelationalConfig(
			config.schema,
			createTableRelationsHelpers,
		);
		schema = {
			fullSchema: config.schema,
			schema: tablesConfig.tables,
			tableNamesMap: tablesConfig.tableNamesMap,
		};
	}

Sources: drizzle-orm/src/durable-sqlite/driver.ts:25-53

The initialization function wires up SQLiteDOSession with the storage client, dialect, schema, and operational options. It constructs a DrizzleSqliteDODatabase instance operating in 'sync' mode, assigns the raw client to $client, and returns the database handle.

	const session = new SQLiteDOSession(client as DurableObjectStorage, dialect, schema, { logger });
	const db = new DrizzleSqliteDODatabase('sync', dialect, session, schema) as DrizzleSqliteDODatabase<TSchema>;
	(<any> db).$client = client;
 
	return db as any;
}

Sources: drizzle-orm/src/durable-sqlite/driver.ts:55-60

Session Management and Synchronous Prepared Queries

SQLiteDOSession extends SQLiteSession with a 'sync' execution mode and maps queries to SQLiteDOPreparedQuery instances via prepareQuery(). Transactions within the session invoke client.transactionSync() directly on the underlying storage client to wrap operations in synchronous transaction blocks.

export class SQLiteDOSession<TFullSchema extends Record<string, unknown>, TSchema extends TablesRelationalConfig>
	extends SQLiteSession<
		'sync',
		SqlStorageCursor<Record<string, SqlStorageValue>>,
		TFullSchema,
		TSchema
	>
{
	static override readonly [entityKind]: string = 'SQLiteDOSession';
 
	private logger: Logger;
 
	constructor(
		private client: DurableObjectStorage,
		dialect: SQLiteSyncDialect,
		private schema: RelationalSchemaConfig<TSchema> | undefined,
		options: SQLiteDOSessionOptions = {},
	) {
		super(dialect);
		this.logger = options.logger ?? new NoopLogger();
	}
 
	prepareQuery<T extends Omit<PreparedQueryConfig, 'run'>>(
		query: Query,
		fields: SelectedFieldsOrdered | undefined,
		executeMethod: SQLiteExecuteMethod,
		isResponseInArrayMode: boolean,
		customResultMapper?: (rows: unknown[][]) => unknown,
	): SQLiteDOPreparedQuery<T> {
		return new SQLiteDOPreparedQuery(
			this.client,
			query,
			this.logger,
			fields,
			executeMethod,
			isResponseInArrayMode,
			customResultMapper,
		);
	}
 
	override transaction<T>(
		transaction: (
			tx: SQLiteTransaction<'sync', SqlStorageCursor<Record<string, SqlStorageValue>>, TFullSchema, TSchema>,
		) => T,
		_config?: SQLiteTransactionConfig,
	): T {
		const tx = new SQLiteDOTransaction('sync', this.dialect, this, this.schema);
		return this.client.transactionSync(() => transaction(tx));
	}
}

Sources: drizzle-orm/src/durable-sqlite/session.ts:23-72

SQLiteDOPreparedQuery executes statements synchronously against client.sql.exec(). Depending on the requested method, it processes results across several execution paths:

MethodReturn TypeExecution Behavior
run()voidExecutes client.sql.exec() with parameters when present, returning no result.
all()T['all']Returns an array of rows by calling .toArray() on the execution result, mapping fields if configured.
get()T['get']Returns the first row value by invoking .next().value or mapping the first row of result arrays.
values()T['values']Returns raw array-of-arrays representation using .raw().toArray().
export class SQLiteDOPreparedQuery<T extends PreparedQueryConfig = PreparedQueryConfig> extends PreparedQueryBase<{
	type: 'sync';
	run: void;
	all: T['all'];
	get: T['get'];
	values: T['values'];
	execute: T['execute'];
}> {
	static override readonly [entityKind]: string = 'SQLiteDOPreparedQuery';
 
	constructor(
		private client: DurableObjectStorage,
		query: Query,
		private logger: Logger,
		private fields: SelectedFieldsOrdered | undefined,
		executeMethod: SQLiteExecuteMethod,
		private _isResponseInArrayMode: boolean,
		private customResultMapper?: (rows: unknown[][]) => unknown,
	) {
		// 3-6 params are for cache. As long as we don't support sync cache - it will be skipped here
		super('sync', executeMethod, query, undefined, undefined, undefined);
	}
 
	run(placeholderValues?: Record<string, unknown>): void {
		const params = fillPlaceholders(this.query.params, placeholderValues ?? {});
		this.logger.logQuery(this.query.sql, params);
 
		params.length > 0 ? this.client.sql.exec(this.query.sql, ...params) : this.client.sql.exec(this.query.sql);
	}
 
	all(placeholderValues?: Record<string, unknown>): T['all'] {
		const { fields, joinsNotNullableMap, query, logger, client, customResultMapper } = this;
		if (!fields && !customResultMapper) {
			const params = fillPlaceholders(query.params, placeholderValues ?? {});
			logger.logQuery(query.sql, params);
 
			return params.length > 0 ? client.sql.exec(query.sql, ...params).toArray() : client.sql.exec(query.sql).toArray();
		}
 
		const rows = this.values(placeholderValues) as unknown[][];
 
		if (customResultMapper) {
			return customResultMapper(rows) as T['all'];
		}
 
		return rows.map((row) => mapResultRow(fields!, row, joinsNotNullableMap));
	}
 
	get(placeholderValues?: Record<string, unknown>): T['get'] {
		const params = fillPlaceholders(this.query.params, placeholderValues ?? {});
		this.logger.logQuery(this.query.sql, params);
 
		const { fields, client, joinsNotNullableMap, customResultMapper, query } = this;
		if (!fields && !customResultMapper) {
			return (params.length > 0 ? client.sql.exec(query.sql, ...params) : client.sql.exec(query.sql)).next().value;
		}
 
		const rows = this.values(placeholderValues) as unknown[][];
		const row = rows[0];
 
		if (!row) {
			return undefined;
		}
 
		if (customResultMapper) {
			return customResultMapper(rows) as T['get'];
		}
 
		return mapResultRow(fields!, row, joinsNotNullableMap);
	}
 
	values(placeholderValues?: Record<string, unknown>): T['values'] {
		const params = fillPlaceholders(this.query.params, placeholderValues ?? {});
		this.logger.logQuery(this.query.sql, params);
 
		const res = params.length > 0
			? this.client.sql.exec(this.query.sql, ...params)
			: this.client.sql.exec(this.query.sql);
 
		// @ts-ignore .raw().toArray() exists
		return res.raw().toArray();
	}
}

Sources: drizzle-orm/src/durable-sqlite/session.ts:90-177

Note

SQLiteDOTransaction extends SQLiteTransaction to support nested transaction nesting indices, spawning new SQLiteDOTransaction instances and delegating back to this.session.transaction() to maintain synchronous execution safety across storage boundaries.

Sources: drizzle-orm/src/durable-sqlite/session.ts:74-88

Expo SQLite Driver Implementation

Driver Initialization and Synchronous Dialect Setup

The Expo SQLite driver initializes through the drizzle() export function, accepting an expo-sqlite database client (SQLiteDatabase) and an optional DrizzleConfig object. It instantiates a SQLiteSyncDialect instance configured with any provided column casing rules. When schema definitions are present, it invokes extractTablesRelationalConfig() along with table relation helpers to build a relational schema structure containing full schemas, table maps, and name mappings.

export function drizzle<TSchema extends Record<string, unknown> = Record<string, never>>(
	client: SQLiteDatabase,
	config: DrizzleConfig<TSchema> = {},
): ExpoSQLiteDatabase<TSchema> & {
	$client: SQLiteDatabase;
} {
	const dialect = new SQLiteSyncDialect({ casing: config.casing });
	let logger;
	if (config.logger === true) {
		logger = new DefaultLogger();
	} else if (config.logger !== false) {
		logger = config.logger;
	}
 
	let schema: RelationalSchemaConfig<TablesRelationalConfig> | undefined;
	if (config.schema) {
		const tablesConfig = extractTablesRelationalConfig(
			config.schema,
			createTableRelationsHelpers,
		);
		schema = {
			fullSchema: config.schema,
			schema: tablesConfig.tables,
			tableNamesMap: tablesConfig.tableNamesMap,
		};
	}

Sources: drizzle-orm/src/expo-sqlite/driver.ts:21-46

The drizzle() function constructs an ExpoSQLiteSession passing the client, dialect, schema, and logger options. It then instantiates an ExpoSQLiteDatabase instance operating in 'sync' mode, assigns the raw client handle to the $client property, and returns the database handle typed against the provided schema.

	const session = new ExpoSQLiteSession(client, dialect, schema, { logger });
	const db = new ExpoSQLiteDatabase('sync', dialect, session, schema) as ExpoSQLiteDatabase<TSchema>;
	(<any> db).$client = client;
 
	return db as any;
}

Sources: drizzle-orm/src/expo-sqlite/driver.ts:48-53

Note

ExpoSQLiteDatabase extends BaseSQLiteDatabase with a fixed 'sync' execution mode type parameter and an entity kind of 'ExpoSQLiteDatabase'.

Sources: drizzle-orm/src/expo-sqlite/driver.ts:15-19

OP-SQLite React Native Driver

Driver Initialization and Async Dialect Setup

The OP-SQLite driver provides native SQLite integration for React Native environments via @op-engineering/op-sqlite. The drizzle() initialization function accepts an OPSQLiteConnection client and an optional DrizzleConfig record, returning an OPSQLiteDatabase instance configured for asynchronous execution mode.

export function drizzle<TSchema extends Record<string, unknown> = Record<string, never>>(
	client: OPSQLiteConnection,
	config: DrizzleConfig<TSchema> = {},
): OPSQLiteDatabase<TSchema> & {
	$client: OPSQLiteConnection;
} {
	const dialect = new SQLiteAsyncDialect({ casing: config.casing });

Sources: drizzle-orm/src/op-sqlite/driver.ts:21-27

The function configures logging based on the boolean or custom logger provided in config.logger, and maps relational table configurations when config.schema is defined.

let logger;
if (config.logger === true) {
	logger = new DefaultLogger();
} else if (config.logger !== false) {
	logger = config.logger;
}
 
let schema: RelationalSchemaConfig<TablesRelationalConfig> | undefined;
if (config.schema) {
	const tablesConfig = extractTablesRelationalConfig(
		config.schema,
		createTableRelationsHelpers,
	);
	schema = {
		fullSchema: config.schema,
		schema: tablesConfig.tables,
		tableNamesMap: tablesConfig.tableNamesMap,
	};
}

Sources: drizzle-orm/src/op-sqlite/driver.ts:28-46

Session Orchestration and Client Properties

An OPSQLiteSession is instantiated with the connection client, asynchronous dialect, schema configuration, logger, and cache options. The returned database object exposes both the underlying client handle via $client and cache mutation handlers when cache options are active.

const session = new OPSQLiteSession(client, dialect, schema, { logger, cache: config.cache });
const db = new OPSQLiteDatabase('async', dialect, session, schema) as OPSQLiteDatabase<TSchema>;
(<any> db).$client = client;
(<any> db).$cache = config.cache;
if ((<any> db).$cache) {
	(<any> db).$cache['invalidate'] = config.cache?.onMutate;
}
 
return db as any;

Sources: drizzle-orm/src/op-sqlite/driver.ts:48-56

Note

OPSQLiteDatabase extends BaseSQLiteDatabase with an 'async' execution mode and a QueryResult return type, establishing entity identity via its static entity kind property.

Sources: drizzle-orm/src/op-sqlite/driver.ts:15-19

SQL.js In-Memory WebAssembly Sessions

Driver Initialization and Synchronous Dialect Setup

The sql.js driver executes SQLite queries inside browser and sandbox environments using WebAssembly memory instances. The entry point drizzle() accepts an sql.js Database client instance alongside an optional DrizzleConfig record, returning an SQLJsDatabase instance configured for synchronous execution ('sync').

export function drizzle<TSchema extends Record<string, unknown> = Record<string, never>>(
	client: Database,
	config: DrizzleConfig<TSchema> = {},
): SQLJsDatabase<TSchema> {

Sources: drizzle-orm/src/sql-js/driver.ts:18-21

During initialization, it creates a SQLiteSyncDialect instance utilizing any casing rules specified in configuration options. It inspects config.logger to instantiate either a DefaultLogger when set to true or a custom logger object when provided. If config.schema is defined, extractTablesRelationalConfig() processes table relations helpers to construct relational configuration maps.

const dialect = new SQLiteSyncDialect({ casing: config.casing });
let logger;
if (config.logger === true) {
	logger = new DefaultLogger();
} else if (config.logger !== false) {
	logger = config.logger;
}
 
let schema: RelationalSchemaConfig<TablesRelationalConfig> | undefined;
if (config.schema) {
	const tablesConfig = extractTablesRelationalConfig(
		config.schema,
		createTableRelationsHelpers,
	);
	schema = {
		fullSchema: config.schema,
		schema: tablesConfig.tables,
		tableNamesMap: tablesConfig.tableNamesMap,
	};
}

Sources: drizzle-orm/src/sql-js/driver.ts:22-41

An SQLJsSession is then instantiated with the database client, dialect, schema configuration, and logger options, which wraps a BaseSQLiteDatabase instance operating in synchronous mode.

const session = new SQLJsSession(client, dialect, schema, { logger });
return new BaseSQLiteDatabase('sync', dialect, session, schema) as SQLJsDatabase<TSchema>;

Sources: drizzle-orm/src/sql-js/driver.ts:43-44

Note

SQLJsDatabase is type-defined as a BaseSQLiteDatabase<'sync', void, TSchema> alias, enforcing synchronous execution across all query builders and operations.

Sources: drizzle-orm/src/sql-js/driver.ts:14-16

Session Management, Transactions, and Field Normalization

The SQLJsSession manages synchronous query preparation and transaction lifecycles over the WebAssembly Database client. Transactions execute raw SQL statements for begin, commit, and rollback, whereas SQLJsTransaction implements nested savepoints using savepoint spN, release savepoint spN, and rollback to savepoint spN.

	override transaction<T>(
		transaction: (tx: SQLJsTransaction<TFullSchema, TSchema>) => T,
		config: SQLiteTransactionConfig = {},
	): T {
		const tx = new SQLJsTransaction('sync', this.dialect, this, this.schema);
		this.run(sql.raw(`begin${config.behavior ? ` ${config.behavior}` : ''}`));
		try {
			const result = transaction(tx);
			this.run(sql`commit`);
			return result;
		} catch (err) {
			this.run(sql`rollback`);
			throw err;
		}
	}
}
 
export class SQLJsTransaction<
	TFullSchema extends Record<string, unknown>,
	TSchema extends TablesRelationalConfig,
> extends SQLiteTransaction<'sync', void, TFullSchema, TSchema> {
	static override readonly [entityKind]: string = 'SQLJsTransaction';
 
	override transaction<T>(transaction: (tx: SQLJsTransaction<TFullSchema, TSchema>) => T): T {
		const savepointName = `sp${this.nestedIndex + 1}`;
		const tx = new SQLJsTransaction('sync', this.dialect, this.session, this.schema, this.nestedIndex + 1);
		tx.run(sql.raw(`savepoint ${savepointName}`));
		try {
			const result = transaction(tx);
			tx.run(sql.raw(`release savepoint ${savepointName}`));
			return result;
		} catch (err) {
			tx.run(sql.raw(`rollback to savepoint ${savepointName}`));
			throw err;
		}
	}

Sources: drizzle-orm/src/sql-js/session.ts:51-86

The PreparedQuery class handles execution methods run, all, get, and values by preparing statements via this.client.prepare(), binding placeholders with fillPlaceholders(), logging queries, and freeing statement handles after execution completes.

run(placeholderValues?: Record<string, unknown>): void {
	const stmt = this.client.prepare(this.query.sql);
 
	const params = fillPlaceholders(this.query.params, placeholderValues ?? {});
	this.logger.logQuery(this.query.sql, params);
	const result = stmt.run(params as BindParams);
 
	stmt.free();
 
	return result;
}
 
all(placeholderValues?: Record<string, unknown>): T['all'] {
	const stmt = this.client.prepare(this.query.sql);
 
	const { fields, joinsNotNullableMap, logger, query, customResultMapper } = this;
	if (!fields && !customResultMapper) {
		const params = fillPlaceholders(query.params, placeholderValues ?? {});
		logger.logQuery(query.sql, params);
		stmt.bind(params as BindParams);
		const rows: unknown[] = [];
		while (stmt.step()) {
			rows.push(stmt.getAsObject());
		}
 
		stmt.free();
 
		return rows;
	}
 
	const rows = this.values(placeholderValues) as unknown[][];
 
	if (customResultMapper) {
		return customResultMapper(rows, normalizeFieldValue) as T['all'];
	}
 
	return rows.map((row) => mapResultRow(fields!, row.map((v) => normalizeFieldValue(v)), joinsNotNullableMap));
}
 
get(placeholderValues?: Record<string, unknown>): T['get'] {
	const stmt = this.client.prepare(this.query.sql);
 
	const params = fillPlaceholders(this.query.params, placeholderValues ?? {});
	this.logger.logQuery(this.query.sql, params);
 
	const { fields, joinsNotNullableMap, customResultMapper } = this;
	if (!fields && !customResultMapper) {
		const result = stmt.getAsObject(params as BindParams);
 
		stmt.free();
 
		return result;
	}
 
	const row = stmt.get(params as BindParams);
 
	stmt.free();
 
	if (!row || (row.length === 0 && fields!.length > 0)) {
		return undefined;
	}
 
	if (customResultMapper) {
		return customResultMapper([row], normalizeFieldValue) as T['get'];
	}
 
	return mapResultRow(fields!, row.map((v) => normalizeFieldValue(v)), joinsNotNullableMap);
}
 
values(placeholderValues?: Record<string, unknown>): T['values'] {
	const stmt = this.client.prepare(this.query.sql);
 
	const params = fillPlaceholders(this.query.params, placeholderValues ?? {});
	this.logger.logQuery(this.query.sql, params);
	stmt.bind(params as BindParams);
	const rows: unknown[] = [];
	while (stmt.step()) {
		rows.push(stmt.get());
	}
	stmt.free();
	return rows;
}

Sources: drizzle-orm/src/sql-js/session.ts:106-189

Helper FunctionInput TypeOutput TypeBehavior
normalizeFieldValueunknownunknownInspects Uint8Array instances, converting them via Buffer.from() if Buffer exists, or via TextDecoder().decode() if available.
fillPlaceholdersunknown[], Record<string, unknown>unknown[]Maps query parameter placeholders to runtime values supplied in execution parameters.
function normalizeFieldValue(value: unknown) {
	if (value instanceof Uint8Array) { // eslint-disable-line no-instanceof/no-instanceof
		if (typeof Buffer !== 'undefined') {
			if (!(value instanceof Buffer)) { // eslint-disable-line no-instanceof/no-instanceof
				return Buffer.from(value);
			}
			return value;
		}
		if (typeof TextDecoder !== 'undefined') {
			return new TextDecoder().decode(value);
		}
		throw new Error('TextDecoder is not available. Please provide either Buffer or TextDecoder polyfill.');
	}
	return value;
}

Sources: drizzle-orm/src/sql-js/session.ts:197-210

Warning

normalizeFieldValue throws an explicit runtime error when encountering a Uint8Array binary result row if neither Buffer nor TextDecoder environment globals are defined.

Sources: drizzle-orm/src/sql-js/session.ts:197-209

Generic SQLite Proxy Driver Protocol

Remote Callback Types and Database Initialization

The remote SQLite proxy driver delegates execution via asynchronous callback functions rather than tying directly to a local database library. The entry point drizzle() accepts an AsyncRemoteCallback, an optional AsyncBatchRemoteCallback, and a DrizzleConfig record to construct a SqliteRemoteDatabase instance operating in asynchronous mode ('async').

export type AsyncRemoteCallback = (
	sql: string,
	params: any[],
	method: 'run' | 'all' | 'values' | 'get',
) => Promise<{ rows: any[] }>;
 
export type AsyncBatchRemoteCallback = (batch: {
	sql: string;
	params: any[];
	method: 'run' | 'all' | 'values' | 'get';
}[]) => Promise<{ rows: any[] }[]>;
 
export type RemoteCallback = AsyncRemoteCallback;
 
export function drizzle<TSchema extends Record<string, unknown> = Record<string, never>>(
	callback: RemoteCallback,
	config?: DrizzleConfig<TSchema>,
): SqliteRemoteDatabase<TSchema>;
export function drizzle<TSchema extends Record<string, unknown> = Record<string, never>>(
	callback: RemoteCallback,
	batchCallback?: AsyncBatchRemoteCallback,
	config?: DrizzleConfig<TSchema>,
): SqliteRemoteDatabase<TSchema>;
export function drizzle<TSchema extends Record<string, unknown> = Record<string, never>>(
	callback: RemoteCallback,
	batchCallback?: AsyncBatchRemoteCallback | DrizzleConfig<TSchema>,
	config?: DrizzleConfig<TSchema>,
): SqliteRemoteDatabase<TSchema> {

Sources: drizzle-orm/src/sqlite-proxy/driver.ts:30-57

During construction, drizzle() initializes a SQLiteAsyncDialect using casing configurations, sets up logging via DefaultLogger when config.logger is enabled, and extracts relational table structures if config.schema is specified.

const dialect = new SQLiteAsyncDialect({ casing: config?.casing });
let logger;
let cache;
let _batchCallback: AsyncBatchRemoteCallback | undefined;
let _config: DrizzleConfig<TSchema> = {};
 
if (batchCallback) {
	if (typeof batchCallback === 'function') {
		_batchCallback = batchCallback as AsyncBatchRemoteCallback;
		_config = config ?? {};
	} else {
		_batchCallback = undefined;
		_config = batchCallback as DrizzleConfig<TSchema>;
	}
 
	if (_config.logger === true) {
		logger = new DefaultLogger();
	} else if (_config.logger !== false) {
		logger = _config.logger;
		cache = _config.cache;
	}
}
 
let schema: RelationalSchemaConfig<TablesRelationalConfig> | undefined;
if (_config.schema) {
	const tablesConfig = extractTablesRelationalConfig(
		_config.schema,
		createTableRelationsHelpers,
	);
	schema = {
		fullSchema: _config.schema,
		schema: tablesConfig.tables,
		tableNamesMap: tablesConfig.tableNamesMap,
	};
}

Sources: drizzle-orm/src/sqlite-proxy/driver.ts:58-92

The resulting SqliteRemoteDatabase binds a SQLiteRemoteSession containing the remote callbacks, dialect, schema definitions, and cache handlers, and configures cache invalidation hooks on $cache when provided.

const session = new SQLiteRemoteSession(callback, dialect, schema, _batchCallback, { logger, cache });
const db = new SqliteRemoteDatabase('async', dialect, session, schema) as SqliteRemoteDatabase<TSchema>;
(<any> db).$cache = cache;
if ((<any> db).$cache) {
	(<any> db).$cache['invalidate'] = cache?.onMutate;
}
return db;

Sources: drizzle-orm/src/sqlite-proxy/driver.ts:93-100

Note

SqliteRemoteDatabase overrides the batch() method to forward batch query arrays directly to the underlying session's batch handler.

Sources: drizzle-orm/src/sqlite-proxy/driver.ts:23-28

Remote Callback Signatures and Packaging

Callback TypeParameter SignatureReturn TypePurpose
AsyncRemoteCallbacksql: string, params: any[], method: 'run' | 'all' | 'values' | 'get'Promise<{ rows: any[] }>Executes a single SQL statement remotely with parameters and execution method.
AsyncBatchRemoteCallbackbatch: { sql: string, params: any[], method: 'run' | 'all' | 'values' | 'get' }[]Promise<{ rows: any[] }[]>Packages multiple statements into a single batch payload for remote RPC transmission.
export type AsyncRemoteCallback = (
	sql: string,
	params: any[],
	method: 'run' | 'all' | 'values' | 'get',
) => Promise<{ rows: any[] }>;
 
export type AsyncBatchRemoteCallback = (batch: {
	sql: string;
	params: any[];
	method: 'run' | 'all' | 'values' | 'get';
}[]) => Promise<{ rows: any[] }[]>;

Sources: drizzle-orm/src/sqlite-proxy/driver.ts:30-40

Was this page helpful?