kysely-replication
Version:
Replication-aware Kysely query execution
226 lines (222 loc) • 7.44 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
KyselyReplicationDialect: () => KyselyReplicationDialect,
KyselyReplicationDriver: () => KyselyReplicationDriver
});
module.exports = __toCommonJS(index_exports);
// src/connection.ts
var import_kysely = require("kysely");
var PRIMARY_OPERATION_NODE_KINDS = {
AlterTableNode: true,
CreateIndexNode: true,
CreateSchemaNode: true,
CreateTableNode: true,
CreateTypeNode: true,
CreateViewNode: true,
DeleteQueryNode: true,
DropIndexNode: true,
DropSchemaNode: true,
DropTableNode: true,
DropTypeNode: true,
DropViewNode: true,
InsertQueryNode: true,
MergeQueryNode: true,
RawNode: true,
UpdateQueryNode: true
};
var KyselyReplicationConnection = class {
#primaryDriver;
#getReplicaDriver;
#onReplicaTransaction;
#connection;
#driver;
constructor(primary, getReplica, onReplicaTransaction) {
this.#primaryDriver = primary;
this.#getReplicaDriver = getReplica;
this.#onReplicaTransaction = onReplicaTransaction;
this.#connection = null;
this.#driver = null;
}
async executeQuery(compiledQuery) {
const { connection } = await this.#acquireDriverAndConnection(compiledQuery);
return await connection.executeQuery(compiledQuery);
}
async *streamQuery(compiledQuery, chunkSize) {
const { connection } = await this.#acquireDriverAndConnection(compiledQuery);
for await (const result of connection.streamQuery(
compiledQuery,
chunkSize
)) {
yield result;
}
}
async beginTransaction(settings) {
const { connection, driver } = await this.#acquireDriverAndConnection("transaction");
if (driver !== this.#primaryDriver) {
const message = "KyselyReplication: transaction started with replica connection!";
if (this.#onReplicaTransaction === "error") {
throw new Error(message);
}
if (this.#onReplicaTransaction === "warn") {
console.warn(message);
}
}
await driver.beginTransaction(connection, settings);
}
async commitTransaction() {
if (!this.#connection) {
throw new Error("commitTransaction called without a transaction");
}
await this.#driver?.commitTransaction(this.#connection);
}
async rollbackTransaction() {
if (!this.#connection) {
throw new Error("rollbackTransaction called without a transaction");
}
await this.#driver?.rollbackTransaction(this.#connection);
}
async release() {
if (!this.#connection) return;
await this.#driver?.releaseConnection(this.#connection);
}
async #acquireDriverAndConnection(compiledQueryOrContext) {
if (this.#connection && this.#driver) {
return { connection: this.#connection, driver: this.#driver };
}
this.#driver = compiledQueryOrContext === "transaction" || this.#isQueryForPrimary(compiledQueryOrContext) ? this.#primaryDriver : await this.#getReplicaDriver(compiledQueryOrContext);
this.#connection = await this.#driver.acquireConnection();
return { connection: this.#connection, driver: this.#driver };
}
#isQueryForPrimary(compiledQuery) {
const { query } = compiledQuery;
if ("__dialect__" in query) {
return query.__dialect__ === "primary";
}
return this.#isOperationNodeForPrimary(query) || import_kysely.SelectQueryNode.is(query) && Boolean(
query.with?.expressions.some(
(e) => this.#isOperationNodeForPrimary(e.expression)
)
);
}
#isOperationNodeForPrimary(node) {
return PRIMARY_OPERATION_NODE_KINDS[node.kind];
}
};
// src/driver.ts
var KyselyReplicationDriver = class {
#primaryDriver;
#replicaDrivers;
#replicaStrategy;
constructor(primaryDriver, replicaDrivers, replicaStrategy) {
this.#primaryDriver = primaryDriver;
this.#replicaDrivers = replicaDrivers;
this.#replicaStrategy = replicaStrategy;
}
async acquireConnection() {
return new KyselyReplicationConnection(
this.#primaryDriver,
async (compiledQuery) => {
const replicaIndex = "__replicaIndex__" in compiledQuery.query ? compiledQuery.query.__replicaIndex__ : await this.#replicaStrategy.next(this.#replicaDrivers.length);
const replicaDriver = this.#replicaDrivers[replicaIndex];
if (!replicaDriver) {
throw new Error(
`KyselyReplication: no replicas found at index ${replicaIndex}!`
);
}
return replicaDriver;
},
this.#replicaStrategy.onTransaction || "error"
);
}
async beginTransaction(connection, settings) {
await connection.beginTransaction(settings);
}
async commitTransaction(connection) {
await connection.commitTransaction();
}
async destroy() {
const results = await Promise.allSettled([
this.#primaryDriver.destroy(),
...this.#replicaDrivers.map((replica) => replica.destroy())
]);
const errors = this.#compileErrors(results);
if (errors.length) {
throw new AggregateError(
errors,
"KyselyReplicationDriver.destroy failed!"
);
}
}
async init() {
const results = await Promise.allSettled([
this.#primaryDriver.init(),
...this.#replicaDrivers.map((replica) => replica.init())
]);
const errors = this.#compileErrors(results);
if (errors.length) {
throw new AggregateError(errors, "KyselyReplicationDriver.init failed!");
}
}
async releaseConnection(connection) {
await connection.release();
}
async rollbackTransaction(connection) {
await connection.rollbackTransaction();
}
#compileErrors(results) {
return results.map(
(result, index) => result.status === "fulfilled" ? null : `${!index ? "primary" : `replica-${index - 1}`}: ${result.reason}`
).filter(Boolean);
}
};
// src/dialect.ts
var KyselyReplicationDialect = class {
#config;
constructor(config) {
this.#config = {
...config,
replicaDialects: [...config.replicaDialects]
};
}
createAdapter() {
return this.#config.primaryDialect.createAdapter();
}
createDriver() {
return new KyselyReplicationDriver(
this.#config.primaryDialect.createDriver(),
this.#config.replicaDialects.map((replica) => replica.createDriver()),
this.#config.replicaStrategy
);
}
createIntrospector(db) {
return this.#config.primaryDialect.createIntrospector(db);
}
createQueryCompiler() {
return this.#config.primaryDialect.createQueryCompiler();
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
KyselyReplicationDialect,
KyselyReplicationDriver
});
//# sourceMappingURL=index.cjs.map