effect-sql-kysely
Version:
A full-featured integration between `@effect/sql` and `Kysely` that provides type-safe database operations with Effect's powerful error handling and resource management.
79 lines (78 loc) • 3.92 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.makeSqlClient = makeSqlClient;
exports.makeKyselyEffect = makeKyselyEffect;
const sql_1 = require("@effect/sql");
const effect_1 = require("effect");
const Cause_1 = require("effect/Cause");
const kysely_1 = require("kysely");
const beginConnection_js_1 = require("./internal/beginConnection.js");
const transformRows = sql_1.Statement.defaultTransforms((s) => s, false).array;
/**
* Low-level ability to construct an @effect/sql SqlClient interface for a Kysely database.
*/
function makeSqlClient({ database, compiler, spanAttributes = [], chunkSize = 16, }) {
class ConnectionImpl {
db;
constructor(db) {
this.db = db;
}
executeUnprepared(sql, params) {
return effect_1.Effect.tryPromise({
try: () => this.db.executeQuery(compileSqlQuery(sql, params)).then((r) => transformRows(r.rows)),
catch: (cause) => new sql_1.SqlError.SqlError({ cause }),
});
}
execute(sql, params) {
return effect_1.Effect.tryPromise({
try: () => this.db.executeQuery(compileSqlQuery(sql, params)).then((r) => transformRows(r.rows)),
catch: (cause) => new sql_1.SqlError.SqlError({ cause }),
});
}
executeWithoutTransform(sql, params) {
return effect_1.Effect.tryPromise({
try: () => this.db.executeQuery(compileSqlQuery(sql, params)).then((r) => r.rows),
catch: (cause) => new sql_1.SqlError.SqlError({ cause }),
});
}
executeValues(sql, params) {
return effect_1.Effect.map(this.executeRaw(sql, params), (results) => results.map((x) => Object.values(x)));
}
executeRaw(sql, params) {
return effect_1.Effect.tryPromise({
try: () => this.db.executeQuery(compileSqlQuery(sql, params)).then((r) => transformRows(r.rows)),
catch: (cause) => new sql_1.SqlError.SqlError({ cause }),
});
}
executeStream(sql, params) {
const query = compileSqlQuery(sql, params);
return effect_1.Stream.suspend(() => effect_1.Stream.mapChunks(effect_1.Stream.fromAsyncIterable(this.db.getExecutor().stream(query, chunkSize), (cause) => new sql_1.SqlError.SqlError({ cause })), effect_1.Chunk.flatMap((result) => effect_1.Chunk.unsafeFromArray(result.rows))));
}
}
return sql_1.SqlClient.make({
// Our default connection is managed by Kysely
acquirer: effect_1.Effect.succeed(new ConnectionImpl(database)),
// Our SQL statement compiler
compiler,
// We don't utilize db.transaction() because Sql.client.make will handle the actual transaction
// But we do ensure that all queries are run within a single connection
transactionAcquirer: effect_1.Effect.map(effect_1.Effect.acquireRelease(effect_1.Effect.promise(() => (0, beginConnection_js_1.beginConnection)(database)), (conn, exit) => effect_1.Effect.promise(() => effect_1.Exit.match(exit, {
// If the scope fails we rollback the transaction
onFailure: (cause) => conn.fail((0, Cause_1.squash)(cause)),
// If the scope succeeds we commit the transaction
onSuccess: () => conn.success(),
}))), ({ conn }) => new ConnectionImpl(conn)),
spanAttributes,
});
}
function makeKyselyEffect(database, sql) {
return (f) => {
// We utilize compile() and sql.unsafe to enable utilizing Effect's notion of a Transaction
const compiled = f(database).compile();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return sql.unsafe(compiled.sql, compiled.parameters);
};
}
function compileSqlQuery(sql, params) {
return kysely_1.CompiledQuery.raw(sql, params);
}