kysely
Version:
Type safe SQL query builder
437 lines (436 loc) • 15 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TransactionBuilder = exports.ConnectionBuilder = exports.isKyselyProps = exports.Transaction = exports.Kysely = void 0;
const schema_js_1 = require("./schema/schema.js");
const dynamic_js_1 = require("./dynamic/dynamic.js");
const default_connection_provider_js_1 = require("./driver/default-connection-provider.js");
const query_creator_js_1 = require("./query-creator.js");
const default_query_executor_js_1 = require("./query-executor/default-query-executor.js");
const object_utils_js_1 = require("./util/object-utils.js");
const runtime_driver_js_1 = require("./driver/runtime-driver.js");
const single_connection_provider_js_1 = require("./driver/single-connection-provider.js");
const driver_js_1 = require("./driver/driver.js");
const prevent_await_js_1 = require("./util/prevent-await.js");
const function_module_js_1 = require("./query-builder/function-module.js");
const log_js_1 = require("./util/log.js");
const query_id_js_1 = require("./util/query-id.js");
const compilable_js_1 = require("./util/compilable.js");
const case_builder_js_1 = require("./query-builder/case-builder.js");
const case_node_js_1 = require("./operation-node/case-node.js");
const expression_parser_js_1 = require("./parser/expression-parser.js");
const with_schema_plugin_js_1 = require("./plugin/with-schema/with-schema-plugin.js");
/**
* The main Kysely class.
*
* You should create one instance of `Kysely` per database using the {@link Kysely}
* constructor. Each `Kysely` instance maintains it's own connection pool.
*
* ### Examples
*
* This example assumes your database has tables `person` and `pet`:
*
* ```ts
* import { Kysely, Generated, PostgresDialect } from 'kysely'
*
* interface PersonTable {
* id: Generated<number>
* first_name: string
* last_name: string
* }
*
* interface PetTable {
* id: Generated<number>
* owner_id: number
* name: string
* species: 'cat' | 'dog'
* }
*
* interface Database {
* person: PersonTable,
* pet: PetTable
* }
*
* const db = new Kysely<Database>({
* dialect: new PostgresDialect({
* host: 'localhost',
* database: 'kysely_test',
* })
* })
* ```
*
* @typeParam DB - The database interface type. Keys of this type must be table names
* in the database and values must be interfaces that describe the rows in those
* tables. See the examples above.
*/
class Kysely extends query_creator_js_1.QueryCreator {
#props;
constructor(args) {
let superProps;
let props;
if (isKyselyProps(args)) {
superProps = { executor: args.executor };
props = { ...args };
}
else {
const dialect = args.dialect;
const driver = dialect.createDriver();
const compiler = dialect.createQueryCompiler();
const adapter = dialect.createAdapter();
const log = new log_js_1.Log(args.log ?? []);
const runtimeDriver = new runtime_driver_js_1.RuntimeDriver(driver, log);
const connectionProvider = new default_connection_provider_js_1.DefaultConnectionProvider(runtimeDriver);
const executor = new default_query_executor_js_1.DefaultQueryExecutor(compiler, adapter, connectionProvider, args.plugins ?? []);
superProps = { executor };
props = {
config: args,
executor,
dialect,
driver: runtimeDriver,
};
}
super(superProps);
this.#props = (0, object_utils_js_1.freeze)(props);
}
/**
* Returns the {@link SchemaModule} module for building database schema.
*/
get schema() {
return new schema_js_1.SchemaModule(this.#props.executor);
}
/**
* Returns a the {@link DynamicModule} module.
*
* The {@link DynamicModule} module can be used to bypass strict typing and
* passing in dynamic values for the queries.
*/
get dynamic() {
return new dynamic_js_1.DynamicModule();
}
/**
* Returns a {@link DatabaseIntrospector | database introspector}.
*/
get introspection() {
return this.#props.dialect.createIntrospector(this.withoutPlugins());
}
case(value) {
return new case_builder_js_1.CaseBuilder({
node: case_node_js_1.CaseNode.create((0, object_utils_js_1.isUndefined)(value) ? undefined : (0, expression_parser_js_1.parseExpression)(value)),
});
}
/**
* Returns a {@link FunctionModule} that can be used to write type safe function
* calls.
*
* ```ts
* await db.selectFrom('person')
* .innerJoin('pet', 'pet.owner_id', 'person.id')
* .select((eb) => [
* 'person.id',
* eb.fn.count('pet.id').as('pet_count')
* ])
* .groupBy('person.id')
* .having((eb) => eb.fn.count('pet.id'), '>', 10)
* .execute()
* ```
*
* The generated SQL (PostgreSQL):
*
* ```sql
* select "person"."id", count("pet"."id") as "pet_count"
* from "person"
* inner join "pet" on "pet"."owner_id" = "person"."id"
* group by "person"."id"
* having count("pet"."id") > $1
* ```
*/
get fn() {
return (0, function_module_js_1.createFunctionModule)();
}
/**
* Creates a {@link TransactionBuilder} that can be used to run queries inside a transaction.
*
* The returned {@link TransactionBuilder} can be used to configure the transaction. The
* {@link TransactionBuilder.execute} method can then be called to run the transaction.
* {@link TransactionBuilder.execute} takes a function that is run inside the
* transaction. If the function throws, the transaction is rolled back. Otherwise
* the transaction is committed.
*
* The callback function passed to the {@link TransactionBuilder.execute | execute}
* method gets the transaction object as its only argument. The transaction is
* of type {@link Transaction} which inherits {@link Kysely}. Any query
* started through the transaction object is executed inside the transaction.
*
* ### Examples
*
* <!-- siteExample("transactions", "Simple transaction", 10) -->
*
* This example inserts two rows in a transaction. If an error is thrown inside
* the callback passed to the `execute` method, the transaction is rolled back.
* Otherwise it's committed.
*
* ```ts
* const catto = await db.transaction().execute(async (trx) => {
* const jennifer = await trx.insertInto('person')
* .values({
* first_name: 'Jennifer',
* last_name: 'Aniston',
* age: 40,
* })
* .returning('id')
* .executeTakeFirstOrThrow()
*
* return await trx.insertInto('pet')
* .values({
* owner_id: jennifer.id,
* name: 'Catto',
* species: 'cat',
* is_favorite: false,
* })
* .returningAll()
* .executeTakeFirst()
* })
* ```
*
* Setting the isolation level:
*
* ```ts
* await db
* .transaction()
* .setIsolationLevel('serializable')
* .execute(async (trx) => {
* await doStuff(trx)
* })
* ```
*/
transaction() {
return new TransactionBuilder({ ...this.#props });
}
/**
* Provides a kysely instance bound to a single database connection.
*
* ### Examples
*
* ```ts
* await db
* .connection()
* .execute(async (db) => {
* // `db` is an instance of `Kysely` that's bound to a single
* // database connection. All queries executed through `db` use
* // the same connection.
* await doStuff(db)
* })
* ```
*/
connection() {
return new ConnectionBuilder({ ...this.#props });
}
/**
* Returns a copy of this Kysely instance with the given plugin installed.
*/
withPlugin(plugin) {
return new Kysely({
...this.#props,
executor: this.#props.executor.withPlugin(plugin),
});
}
/**
* Returns a copy of this Kysely instance without any plugins.
*/
withoutPlugins() {
return new Kysely({
...this.#props,
executor: this.#props.executor.withoutPlugins(),
});
}
/**
* @override
*/
withSchema(schema) {
return new Kysely({
...this.#props,
executor: this.#props.executor.withPluginAtFront(new with_schema_plugin_js_1.WithSchemaPlugin(schema)),
});
}
/**
* Returns a copy of this Kysely instance with tables added to its
* database type.
*
* This method only modifies the types and doesn't affect any of the
* executed queries in any way.
*
* ### Examples
*
* The following example adds and uses a temporary table:
*
* @example
* ```ts
* await db.schema
* .createTable('temp_table')
* .temporary()
* .addColumn('some_column', 'integer')
* .execute()
*
* const tempDb = db.withTables<{
* temp_table: {
* some_column: number
* }
* }>()
*
* await tempDb
* .insertInto('temp_table')
* .values({ some_column: 100 })
* .execute()
* ```
*/
withTables() {
return new Kysely({ ...this.#props });
}
/**
* Releases all resources and disconnects from the database.
*
* You need to call this when you are done using the `Kysely` instance.
*/
async destroy() {
await this.#props.driver.destroy();
}
/**
* Returns true if this `Kysely` instance is a transaction.
*
* You can also use `db instanceof Transaction`.
*/
get isTransaction() {
return false;
}
/**
* @internal
* @private
*/
getExecutor() {
return this.#props.executor;
}
/**
* Executes a given compiled query or query builder.
*
* See {@link https://github.com/koskimas/kysely/blob/master/site/docs/recipes/splitting-build-compile-and-execute-code.md#execute-compiled-queries splitting build, compile and execute code recipe} for more information.
*/
executeQuery(query, queryId = (0, query_id_js_1.createQueryId)()) {
const compiledQuery = (0, compilable_js_1.isCompilable)(query) ? query.compile() : query;
return this.getExecutor().executeQuery(compiledQuery, queryId);
}
}
exports.Kysely = Kysely;
class Transaction extends Kysely {
#props;
constructor(props) {
super(props);
this.#props = props;
}
// The return type is `true` instead of `boolean` to make Kysely<DB>
// unassignable to Transaction<DB> while allowing assignment the
// other way around.
get isTransaction() {
return true;
}
transaction() {
throw new Error('calling the transaction method for a Transaction is not supported');
}
connection() {
throw new Error('calling the connection method for a Transaction is not supported');
}
async destroy() {
throw new Error('calling the destroy method for a Transaction is not supported');
}
withPlugin(plugin) {
return new Transaction({
...this.#props,
executor: this.#props.executor.withPlugin(plugin),
});
}
withoutPlugins() {
return new Transaction({
...this.#props,
executor: this.#props.executor.withoutPlugins(),
});
}
/**
* @override
*/
withSchema(schema) {
return new Transaction({
...this.#props,
executor: this.#props.executor.withPluginAtFront(new with_schema_plugin_js_1.WithSchemaPlugin(schema)),
});
}
withTables() {
return new Transaction({ ...this.#props });
}
}
exports.Transaction = Transaction;
function isKyselyProps(obj) {
return ((0, object_utils_js_1.isObject)(obj) &&
(0, object_utils_js_1.isObject)(obj.config) &&
(0, object_utils_js_1.isObject)(obj.driver) &&
(0, object_utils_js_1.isObject)(obj.executor) &&
(0, object_utils_js_1.isObject)(obj.dialect));
}
exports.isKyselyProps = isKyselyProps;
class ConnectionBuilder {
#props;
constructor(props) {
this.#props = (0, object_utils_js_1.freeze)(props);
}
async execute(callback) {
return this.#props.executor.provideConnection(async (connection) => {
const executor = this.#props.executor.withConnectionProvider(new single_connection_provider_js_1.SingleConnectionProvider(connection));
const db = new Kysely({
...this.#props,
executor,
});
return await callback(db);
});
}
}
exports.ConnectionBuilder = ConnectionBuilder;
(0, prevent_await_js_1.preventAwait)(ConnectionBuilder, "don't await ConnectionBuilder instances directly. To execute the query you need to call the `execute` method");
class TransactionBuilder {
#props;
constructor(props) {
this.#props = (0, object_utils_js_1.freeze)(props);
}
setIsolationLevel(isolationLevel) {
return new TransactionBuilder({
...this.#props,
isolationLevel,
});
}
async execute(callback) {
const { isolationLevel, ...kyselyProps } = this.#props;
const settings = { isolationLevel };
validateTransactionSettings(settings);
return this.#props.executor.provideConnection(async (connection) => {
const executor = this.#props.executor.withConnectionProvider(new single_connection_provider_js_1.SingleConnectionProvider(connection));
const transaction = new Transaction({
...kyselyProps,
executor,
});
try {
await this.#props.driver.beginTransaction(connection, settings);
const result = await callback(transaction);
await this.#props.driver.commitTransaction(connection);
return result;
}
catch (error) {
await this.#props.driver.rollbackTransaction(connection);
throw error;
}
});
}
}
exports.TransactionBuilder = TransactionBuilder;
(0, prevent_await_js_1.preventAwait)(TransactionBuilder, "don't await TransactionBuilder instances directly. To execute the transaction you need to call the `execute` method");
function validateTransactionSettings(settings) {
if (settings.isolationLevel &&
!driver_js_1.TRANSACTION_ISOLATION_LEVELS.includes(settings.isolationLevel)) {
throw new Error(`invalid transaction isolation level ${settings.isolationLevel}`);
}
}