kysely-replication
Version:
Replication-aware Kysely query execution
1 lines • 13.1 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts","../src/connection.ts","../src/driver.ts","../src/dialect.ts"],"sourcesContent":["export * from './config.js'\nexport * from './dialect.js'\nexport * from './driver.js'\n","import {\n\ttype CompiledQuery,\n\ttype DatabaseConnection,\n\ttype Driver,\n\ttype OperationNode,\n\ttype QueryResult,\n\ttype RootOperationNode,\n\tSelectQueryNode,\n\ttype TransactionSettings,\n} from 'kysely'\n\nconst PRIMARY_OPERATION_NODE_KINDS: Record<\n\tExclude<RootOperationNode['kind'], 'SelectQueryNode'>,\n\ttrue\n> = {\n\tAlterTableNode: true,\n\tCreateIndexNode: true,\n\tCreateSchemaNode: true,\n\tCreateTableNode: true,\n\tCreateTypeNode: true,\n\tCreateViewNode: true,\n\tDeleteQueryNode: true,\n\tDropIndexNode: true,\n\tDropSchemaNode: true,\n\tDropTableNode: true,\n\tDropTypeNode: true,\n\tDropViewNode: true,\n\tInsertQueryNode: true,\n\tMergeQueryNode: true,\n\tRawNode: true,\n\tUpdateQueryNode: true,\n}\n\nexport class KyselyReplicationConnection implements DatabaseConnection {\n\treadonly #primaryDriver: Driver\n\treadonly #getReplicaDriver: (compiledQuery: CompiledQuery) => Promise<Driver>\n\treadonly #onReplicaTransaction: 'error' | 'warn' | 'allow'\n\t#connection: DatabaseConnection | null\n\t#driver: Driver | null\n\n\tconstructor(\n\t\tprimary: Driver,\n\t\tgetReplica: (compiledQuery: CompiledQuery) => Promise<Driver>,\n\t\tonReplicaTransaction: 'error' | 'warn' | 'allow',\n\t) {\n\t\tthis.#primaryDriver = primary\n\t\tthis.#getReplicaDriver = getReplica\n\t\tthis.#onReplicaTransaction = onReplicaTransaction\n\t\tthis.#connection = null\n\t\tthis.#driver = null\n\t}\n\n\tasync executeQuery<R>(compiledQuery: CompiledQuery): Promise<QueryResult<R>> {\n\t\tconst { connection } = await this.#acquireDriverAndConnection(compiledQuery)\n\n\t\treturn await connection.executeQuery(compiledQuery)\n\t}\n\n\tasync *streamQuery<R>(\n\t\tcompiledQuery: CompiledQuery,\n\t\tchunkSize: number,\n\t): AsyncIterableIterator<QueryResult<R>> {\n\t\tconst { connection } = await this.#acquireDriverAndConnection(compiledQuery)\n\n\t\tfor await (const result of connection.streamQuery<R>(\n\t\t\tcompiledQuery,\n\t\t\tchunkSize,\n\t\t)) {\n\t\t\tyield result\n\t\t}\n\t}\n\n\tasync beginTransaction(settings: TransactionSettings): Promise<void> {\n\t\tconst { connection, driver } =\n\t\t\tawait this.#acquireDriverAndConnection('transaction')\n\n\t\tif (driver !== this.#primaryDriver) {\n\t\t\tconst message =\n\t\t\t\t'KyselyReplication: transaction started with replica connection!'\n\n\t\t\tif (this.#onReplicaTransaction === 'error') {\n\t\t\t\tthrow new Error(message)\n\t\t\t}\n\n\t\t\tif (this.#onReplicaTransaction === 'warn') {\n\t\t\t\tconsole.warn(message)\n\t\t\t}\n\t\t}\n\n\t\tawait driver.beginTransaction(connection, settings)\n\t}\n\n\tasync commitTransaction(): Promise<void> {\n\t\tif (!this.#connection) {\n\t\t\tthrow new Error('commitTransaction called without a transaction')\n\t\t}\n\n\t\tawait this.#driver?.commitTransaction(this.#connection)\n\t}\n\n\tasync rollbackTransaction(): Promise<void> {\n\t\tif (!this.#connection) {\n\t\t\tthrow new Error('rollbackTransaction called without a transaction')\n\t\t}\n\n\t\tawait this.#driver?.rollbackTransaction(this.#connection)\n\t}\n\n\tasync release(): Promise<void> {\n\t\tif (!this.#connection) return\n\n\t\tawait this.#driver?.releaseConnection(this.#connection)\n\t}\n\n\tasync #acquireDriverAndConnection(\n\t\tcompiledQueryOrContext: CompiledQuery | 'transaction',\n\t): Promise<{ connection: DatabaseConnection; driver: Driver }> {\n\t\tif (this.#connection && this.#driver) {\n\t\t\treturn { connection: this.#connection, driver: this.#driver }\n\t\t}\n\n\t\tthis.#driver =\n\t\t\tcompiledQueryOrContext === 'transaction' ||\n\t\t\tthis.#isQueryForPrimary(compiledQueryOrContext)\n\t\t\t\t? this.#primaryDriver\n\t\t\t\t: await this.#getReplicaDriver(compiledQueryOrContext)\n\n\t\tthis.#connection = await this.#driver.acquireConnection()\n\n\t\treturn { connection: this.#connection, driver: this.#driver }\n\t}\n\n\t#isQueryForPrimary(compiledQuery: CompiledQuery): boolean {\n\t\tconst { query } = compiledQuery\n\n\t\tif ('__dialect__' in query) {\n\t\t\treturn query.__dialect__ === 'primary'\n\t\t}\n\n\t\treturn (\n\t\t\tthis.#isOperationNodeForPrimary(query) ||\n\t\t\t(SelectQueryNode.is(query) &&\n\t\t\t\tBoolean(\n\t\t\t\t\tquery.with?.expressions.some((e) =>\n\t\t\t\t\t\tthis.#isOperationNodeForPrimary(e.expression),\n\t\t\t\t\t),\n\t\t\t\t))\n\t\t)\n\t}\n\n\t#isOperationNodeForPrimary(node: OperationNode): boolean {\n\t\treturn PRIMARY_OPERATION_NODE_KINDS[\n\t\t\tnode.kind as keyof typeof PRIMARY_OPERATION_NODE_KINDS\n\t\t]\n\t}\n}\n","import type {\n\tCompiledQuery,\n\tDatabaseConnection,\n\tDriver,\n\tTransactionSettings,\n} from 'kysely'\nimport type { ReplicaStrategy } from './config.js'\nimport { KyselyReplicationConnection } from './connection.js'\n\nexport class KyselyReplicationDriver implements Driver {\n\treadonly #primaryDriver: Driver\n\treadonly #replicaDrivers: readonly Driver[]\n\treadonly #replicaStrategy: ReplicaStrategy\n\n\tconstructor(\n\t\tprimaryDriver: Driver,\n\t\treplicaDrivers: readonly Driver[],\n\t\treplicaStrategy: ReplicaStrategy,\n\t) {\n\t\tthis.#primaryDriver = primaryDriver\n\t\tthis.#replicaDrivers = replicaDrivers\n\t\tthis.#replicaStrategy = replicaStrategy\n\t}\n\n\tasync acquireConnection(): Promise<DatabaseConnection> {\n\t\treturn new KyselyReplicationConnection(\n\t\t\tthis.#primaryDriver,\n\t\t\tasync (compiledQuery: CompiledQuery) => {\n\t\t\t\tconst replicaIndex =\n\t\t\t\t\t'__replicaIndex__' in compiledQuery.query\n\t\t\t\t\t\t? (compiledQuery.query.__replicaIndex__ as number)\n\t\t\t\t\t\t: await this.#replicaStrategy.next(this.#replicaDrivers.length)\n\n\t\t\t\tconst replicaDriver = this.#replicaDrivers[replicaIndex]\n\n\t\t\t\tif (!replicaDriver) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`KyselyReplication: no replicas found at index ${replicaIndex}!`,\n\t\t\t\t\t)\n\t\t\t\t}\n\n\t\t\t\treturn replicaDriver\n\t\t\t},\n\t\t\tthis.#replicaStrategy.onTransaction || 'error',\n\t\t)\n\t}\n\n\tasync beginTransaction(\n\t\tconnection: KyselyReplicationConnection,\n\t\tsettings: TransactionSettings,\n\t): Promise<void> {\n\t\tawait connection.beginTransaction(settings)\n\t}\n\n\tasync commitTransaction(\n\t\tconnection: KyselyReplicationConnection,\n\t): Promise<void> {\n\t\tawait connection.commitTransaction()\n\t}\n\n\tasync destroy(): Promise<void> {\n\t\tconst results = await Promise.allSettled([\n\t\t\tthis.#primaryDriver.destroy(),\n\t\t\t...this.#replicaDrivers.map((replica) => replica.destroy()),\n\t\t])\n\n\t\tconst errors = this.#compileErrors(results)\n\n\t\tif (errors.length) {\n\t\t\tthrow new AggregateError(\n\t\t\t\terrors,\n\t\t\t\t'KyselyReplicationDriver.destroy failed!',\n\t\t\t)\n\t\t}\n\t}\n\n\tasync init(): Promise<void> {\n\t\tconst results = await Promise.allSettled([\n\t\t\tthis.#primaryDriver.init(),\n\t\t\t...this.#replicaDrivers.map((replica) => replica.init()),\n\t\t])\n\n\t\tconst errors = this.#compileErrors(results)\n\n\t\tif (errors.length) {\n\t\t\tthrow new AggregateError(errors, 'KyselyReplicationDriver.init failed!')\n\t\t}\n\t}\n\n\tasync releaseConnection(\n\t\tconnection: KyselyReplicationConnection,\n\t): Promise<void> {\n\t\tawait connection.release()\n\t}\n\n\tasync rollbackTransaction(\n\t\tconnection: KyselyReplicationConnection,\n\t): Promise<void> {\n\t\tawait connection.rollbackTransaction()\n\t}\n\n\t#compileErrors(results: PromiseSettledResult<unknown>[]): string[] {\n\t\treturn results\n\t\t\t.map((result, index) =>\n\t\t\t\tresult.status === 'fulfilled'\n\t\t\t\t\t? null\n\t\t\t\t\t: `${!index ? 'primary' : `replica-${index - 1}`}: ${result.reason}`,\n\t\t\t)\n\t\t\t.filter(Boolean) as string[]\n\t}\n}\n","import type {\n\tDatabaseIntrospector,\n\tDialect,\n\tDialectAdapter,\n\tDriver,\n\tKysely,\n\tQueryCompiler,\n} from 'kysely'\nimport type { KyselyReplicationDialectConfig } from './config.js'\nimport { KyselyReplicationDriver } from './driver.js'\n\nexport class KyselyReplicationDialect implements Dialect {\n\treadonly #config: KyselyReplicationDialectConfig\n\n\tconstructor(config: KyselyReplicationDialectConfig) {\n\t\tthis.#config = {\n\t\t\t...config,\n\t\t\treplicaDialects: [...config.replicaDialects],\n\t\t}\n\t}\n\n\tcreateAdapter(): DialectAdapter {\n\t\treturn this.#config.primaryDialect.createAdapter()\n\t}\n\n\tcreateDriver(): Driver {\n\t\treturn new KyselyReplicationDriver(\n\t\t\tthis.#config.primaryDialect.createDriver(),\n\t\t\tthis.#config.replicaDialects.map((replica) => replica.createDriver()),\n\t\t\tthis.#config.replicaStrategy,\n\t\t)\n\t}\n\n\tcreateIntrospector(db: Kysely<unknown>): DatabaseIntrospector {\n\t\treturn this.#config.primaryDialect.createIntrospector(db)\n\t}\n\n\tcreateQueryCompiler(): QueryCompiler {\n\t\treturn this.#config.primaryDialect.createQueryCompiler()\n\t}\n}\n"],"mappings":"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","names":[]}