Skip to content

Commit ce30ea2

Browse files
committed
fix: preserve transaction state in $use, $unuse, and $unuseAll
When calling $use(), $unuse(), or $unuseAll() on a transaction client, the returned client would escape the active transaction because the constructor always creates a fresh Kysely instance from kyselyProps. Propagate the transaction Kysely instance to the new client when the current client is inside a transaction. Fixes #2494
1 parent d982cc5 commit ce30ea2

File tree

2 files changed

+51
-0
lines changed

2 files changed

+51
-0
lines changed

packages/orm/src/client/client-impl.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,10 @@ export class ClientImpl {
378378
newClient.inputValidator = new InputValidator(newClient as any, {
379379
enabled: newOptions.validateInput !== false,
380380
});
381+
// preserve transaction state so the new client stays in the same transaction
382+
if (this.kysely.isTransaction) {
383+
newClient.kysely = this.kysely;
384+
}
381385
return newClient;
382386
}
383387

@@ -399,6 +403,10 @@ export class ClientImpl {
399403
newClient.inputValidator = new InputValidator(newClient as any, {
400404
enabled: newClient.$options.validateInput !== false,
401405
});
406+
// preserve transaction state so the new client stays in the same transaction
407+
if (this.kysely.isTransaction) {
408+
newClient.kysely = this.kysely;
409+
}
402410
return newClient;
403411
}
404412

@@ -414,6 +422,10 @@ export class ClientImpl {
414422
newClient.inputValidator = new InputValidator(newClient as any, {
415423
enabled: newOptions.validateInput !== false,
416424
});
425+
// preserve transaction state so the new client stays in the same transaction
426+
if (this.kysely.isTransaction) {
427+
newClient.kysely = this.kysely;
428+
}
417429
return newClient;
418430
}
419431

tests/e2e/orm/client-api/transaction.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,45 @@ describe('Client raw query tests', () => {
101101

102102
await expect(client.user.findMany()).toResolveWithLength(0);
103103
});
104+
105+
it('$unuseAll preserves transaction isolation', async () => {
106+
await expect(
107+
client.$transaction(async (tx) => {
108+
await tx.$unuseAll().user.create({
109+
data: { email: 'u1@test.com' },
110+
});
111+
throw new Error('rollback');
112+
}),
113+
).rejects.toThrow('rollback');
114+
115+
await expect(client.user.findMany()).toResolveWithLength(0);
116+
});
117+
118+
it('$unuse preserves transaction isolation', async () => {
119+
await expect(
120+
client.$transaction(async (tx) => {
121+
await tx.$unuse('nonexistent').user.create({
122+
data: { email: 'u1@test.com' },
123+
});
124+
throw new Error('rollback');
125+
}),
126+
).rejects.toThrow('rollback');
127+
128+
await expect(client.user.findMany()).toResolveWithLength(0);
129+
});
130+
131+
it('$use preserves transaction isolation', async () => {
132+
await expect(
133+
client.$transaction(async (tx) => {
134+
await (tx as any).$use({ id: 'noop', handle: (_node: any, proceed: any) => proceed(_node) }).user.create({
135+
data: { email: 'u1@test.com' },
136+
});
137+
throw new Error('rollback');
138+
}),
139+
).rejects.toThrow('rollback');
140+
141+
await expect(client.user.findMany()).toResolveWithLength(0);
142+
});
104143
});
105144

106145
describe('sequential transaction', () => {

0 commit comments

Comments
 (0)