@geekmidas/db 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,392 +3,392 @@ import { describe, expect, it, vi } from 'vitest';
3
3
  import { type DatabaseConnection, withTransaction } from '../kysely';
4
4
 
5
5
  describe('Kysely Transaction Helper', () => {
6
- describe('withTransaction', () => {
7
- it('should execute callback within new transaction for Kysely instance', async () => {
8
- const executeSpy = vi.fn(async (cb) => {
9
- const mockTrx = { isTransaction: true } as Transaction<any>;
10
- return cb(mockTrx);
11
- });
12
-
13
- const mockDb = {
14
- isTransaction: false,
15
- transaction: vi.fn(() => ({
16
- execute: executeSpy,
17
- })),
18
- } as unknown as Kysely<any>;
19
-
20
- const callback = vi.fn(async (trx) => {
21
- expect(trx.isTransaction).toBe(true);
22
- return 'result';
23
- });
24
-
25
- const result = await withTransaction(mockDb, callback);
26
-
27
- expect(mockDb.transaction).toHaveBeenCalledTimes(1);
28
- expect(executeSpy).toHaveBeenCalledTimes(1);
29
- expect(callback).toHaveBeenCalledTimes(1);
30
- expect(result).toBe('result');
31
- });
32
-
33
- it('should reuse existing transaction', async () => {
34
- const mockTrx = {
35
- isTransaction: true,
36
- } as Transaction<any>;
37
-
38
- const callback = vi.fn(async (trx) => {
39
- expect(trx).toBe(mockTrx);
40
- return 'reused';
41
- });
42
-
43
- const result = await withTransaction(mockTrx, callback);
44
-
45
- expect(callback).toHaveBeenCalledWith(mockTrx);
46
- expect(result).toBe('reused');
47
- });
48
-
49
- it('should handle ControlledTransaction by reusing it', async () => {
50
- const mockControlledTrx = {
51
- isTransaction: true,
52
- } as any; // ControlledTransaction
53
-
54
- const callback = vi.fn(async (trx) => {
55
- return 'controlled';
56
- });
57
-
58
- const result = await withTransaction(mockControlledTrx, callback);
59
-
60
- expect(callback).toHaveBeenCalledWith(mockControlledTrx);
61
- expect(result).toBe('controlled');
62
- });
63
-
64
- it('should propagate callback return value', async () => {
65
- const executeSpy = vi.fn(async (cb) => {
66
- const mockTrx = { isTransaction: true } as Transaction<any>;
67
- return cb(mockTrx);
68
- });
69
-
70
- const mockDb = {
71
- isTransaction: false,
72
- transaction: vi.fn(() => ({
73
- execute: executeSpy,
74
- })),
75
- } as unknown as Kysely<any>;
76
-
77
- const result = await withTransaction(mockDb, async () => {
78
- return { id: 123, name: 'Test' };
79
- });
80
-
81
- expect(result).toEqual({ id: 123, name: 'Test' });
82
- });
83
-
84
- it('should propagate errors from callback', async () => {
85
- const executeSpy = vi.fn(async (cb) => {
86
- const mockTrx = { isTransaction: true } as Transaction<any>;
87
- return cb(mockTrx);
88
- });
89
-
90
- const mockDb = {
91
- isTransaction: false,
92
- transaction: vi.fn(() => ({
93
- execute: executeSpy,
94
- })),
95
- } as unknown as Kysely<any>;
96
-
97
- const error = new Error('Transaction failed');
98
-
99
- await expect(
100
- withTransaction(mockDb, async () => {
101
- throw error;
102
- }),
103
- ).rejects.toThrow('Transaction failed');
104
- });
105
-
106
- it('should allow nested transactions with reuse', async () => {
107
- const outerExecuteSpy = vi.fn(async (cb) => {
108
- const mockTrx = { isTransaction: true } as Transaction<any>;
109
- return cb(mockTrx);
110
- });
111
-
112
- const mockDb = {
113
- isTransaction: false,
114
- transaction: vi.fn(() => ({
115
- execute: outerExecuteSpy,
116
- })),
117
- } as unknown as Kysely<any>;
118
-
119
- const result = await withTransaction(mockDb, async (outerTrx) => {
120
- // Inner call should reuse the transaction
121
- return withTransaction(outerTrx, async (innerTrx) => {
122
- expect(innerTrx).toBe(outerTrx);
123
- return 'nested';
124
- });
125
- });
126
-
127
- expect(mockDb.transaction).toHaveBeenCalledTimes(1);
128
- expect(result).toBe('nested');
129
- });
130
-
131
- it('should work with database queries', async () => {
132
- const executeSpy = vi.fn(async (cb) => {
133
- const mockTrx = {
134
- isTransaction: true,
135
- selectFrom: vi.fn(() => ({
136
- selectAll: vi.fn(() => ({
137
- execute: vi.fn(async () => [
138
- { id: 1, name: 'User 1' },
139
- { id: 2, name: 'User 2' },
140
- ]),
141
- })),
142
- })),
143
- } as unknown as Transaction<any>;
144
- return cb(mockTrx);
145
- });
146
-
147
- const mockDb = {
148
- isTransaction: false,
149
- transaction: vi.fn(() => ({
150
- execute: executeSpy,
151
- })),
152
- } as unknown as Kysely<any>;
153
-
154
- const result = await withTransaction(mockDb, async (trx) => {
155
- const users = await (trx as any)
156
- .selectFrom('users')
157
- .selectAll()
158
- .execute();
159
- return users;
160
- });
161
-
162
- expect(result).toEqual([
163
- { id: 1, name: 'User 1' },
164
- { id: 2, name: 'User 2' },
165
- ]);
166
- });
167
-
168
- it('should handle async operations in callback', async () => {
169
- const executeSpy = vi.fn(async (cb) => {
170
- const mockTrx = { isTransaction: true } as Transaction<any>;
171
- return cb(mockTrx);
172
- });
173
-
174
- const mockDb = {
175
- isTransaction: false,
176
- transaction: vi.fn(() => ({
177
- execute: executeSpy,
178
- })),
179
- } as unknown as Kysely<any>;
180
-
181
- const result = await withTransaction(mockDb, async (trx) => {
182
- await new Promise((resolve) => setTimeout(resolve, 10));
183
- return 'async-result';
184
- });
185
-
186
- expect(result).toBe('async-result');
187
- });
188
-
189
- it('should support different return types', async () => {
190
- const executeSpy = vi.fn(async (cb) => {
191
- const mockTrx = { isTransaction: true } as Transaction<any>;
192
- return cb(mockTrx);
193
- });
194
-
195
- const mockDb = {
196
- isTransaction: false,
197
- transaction: vi.fn(() => ({
198
- execute: executeSpy,
199
- })),
200
- } as unknown as Kysely<any>;
201
-
202
- // Number
203
- const numResult = await withTransaction(mockDb, async () => 42);
204
- expect(numResult).toBe(42);
205
-
206
- // String
207
- const strResult = await withTransaction(mockDb, async () => 'test');
208
- expect(strResult).toBe('test');
209
-
210
- // Boolean
211
- const boolResult = await withTransaction(mockDb, async () => true);
212
- expect(boolResult).toBe(true);
213
-
214
- // Array
215
- const arrResult = await withTransaction(mockDb, async () => [1, 2, 3]);
216
- expect(arrResult).toEqual([1, 2, 3]);
217
-
218
- // Object
219
- const objResult = await withTransaction(mockDb, async () => ({
220
- key: 'value',
221
- }));
222
- expect(objResult).toEqual({ key: 'value' });
223
- });
224
-
225
- it('should maintain transaction isolation', async () => {
226
- const executeSpy = vi.fn(async (cb) => {
227
- const mockTrx = {
228
- isTransaction: true,
229
- id: 'trx-123',
230
- } as unknown as Transaction<any>;
231
- return cb(mockTrx);
232
- });
233
-
234
- const mockDb = {
235
- isTransaction: false,
236
- transaction: vi.fn(() => ({
237
- execute: executeSpy,
238
- })),
239
- } as unknown as Kysely<any>;
240
-
241
- await withTransaction(mockDb, async (trx1) => {
242
- expect((trx1 as any).id).toBe('trx-123');
243
-
244
- // Nested transaction reuses same transaction
245
- await withTransaction(trx1, async (trx2) => {
246
- expect(trx2).toBe(trx1);
247
- expect((trx2 as any).id).toBe('trx-123');
248
- });
249
- });
250
- });
251
-
252
- it('should set isolation level when provided', async () => {
253
- const executeSpy = vi.fn(async (cb) => {
254
- const mockTrx = { isTransaction: true } as Transaction<any>;
255
- return cb(mockTrx);
256
- });
257
-
258
- const setIsolationLevelSpy = vi.fn(() => ({
259
- execute: executeSpy,
260
- }));
261
-
262
- const mockDb = {
263
- isTransaction: false,
264
- transaction: vi.fn(() => ({
265
- setIsolationLevel: setIsolationLevelSpy,
266
- execute: executeSpy,
267
- })),
268
- } as unknown as Kysely<any>;
269
-
270
- const callback = vi.fn(async (trx) => 'result');
271
-
272
- const result = await withTransaction(mockDb, callback, {
273
- isolationLevel: 'serializable',
274
- });
275
-
276
- expect(mockDb.transaction).toHaveBeenCalledTimes(1);
277
- expect(setIsolationLevelSpy).toHaveBeenCalledWith('serializable');
278
- expect(executeSpy).toHaveBeenCalledTimes(1);
279
- expect(callback).toHaveBeenCalledTimes(1);
280
- expect(result).toBe('result');
281
- });
282
-
283
- it('should not set isolation level when not provided', async () => {
284
- const executeSpy = vi.fn(async (cb) => {
285
- const mockTrx = { isTransaction: true } as Transaction<any>;
286
- return cb(mockTrx);
287
- });
288
-
289
- const setIsolationLevelSpy = vi.fn(() => ({
290
- execute: executeSpy,
291
- }));
292
-
293
- const mockDb = {
294
- isTransaction: false,
295
- transaction: vi.fn(() => ({
296
- setIsolationLevel: setIsolationLevelSpy,
297
- execute: executeSpy,
298
- })),
299
- } as unknown as Kysely<any>;
300
-
301
- const callback = vi.fn(async (trx) => 'result');
302
-
303
- const result = await withTransaction(mockDb, callback);
304
-
305
- expect(mockDb.transaction).toHaveBeenCalledTimes(1);
306
- expect(setIsolationLevelSpy).not.toHaveBeenCalled();
307
- expect(executeSpy).toHaveBeenCalledTimes(1);
308
- expect(result).toBe('result');
309
- });
310
-
311
- it('should support different isolation levels', async () => {
312
- const executeSpy = vi.fn(async (cb) => {
313
- const mockTrx = { isTransaction: true } as Transaction<any>;
314
- return cb(mockTrx);
315
- });
316
-
317
- const setIsolationLevelSpy = vi.fn(() => ({
318
- execute: executeSpy,
319
- }));
320
-
321
- const mockDb = {
322
- isTransaction: false,
323
- transaction: vi.fn(() => ({
324
- setIsolationLevel: setIsolationLevelSpy,
325
- execute: executeSpy,
326
- })),
327
- } as unknown as Kysely<any>;
328
-
329
- const levels = [
330
- 'read uncommitted',
331
- 'read committed',
332
- 'repeatable read',
333
- 'serializable',
334
- 'snapshot',
335
- ] as const;
336
-
337
- for (const level of levels) {
338
- setIsolationLevelSpy.mockClear();
339
-
340
- await withTransaction(mockDb, async () => 'result', {
341
- isolationLevel: level,
342
- });
343
-
344
- expect(setIsolationLevelSpy).toHaveBeenCalledWith(level);
345
- }
346
- });
347
-
348
- it('should not set isolation level for existing transactions', async () => {
349
- const mockTrx = {
350
- isTransaction: true,
351
- setIsolationLevel: vi.fn(),
352
- } as unknown as Transaction<any>;
353
-
354
- const callback = vi.fn(async (trx) => {
355
- expect(trx).toBe(mockTrx);
356
- return 'reused';
357
- });
358
-
359
- const result = await withTransaction(mockTrx, callback, {
360
- isolationLevel: 'serializable',
361
- });
362
-
363
- expect((mockTrx as any).setIsolationLevel).not.toHaveBeenCalled();
364
- expect(result).toBe('reused');
365
- });
366
- });
367
-
368
- describe('DatabaseConnection type', () => {
369
- it('should accept Kysely instance', () => {
370
- const mockDb = {
371
- isTransaction: false,
372
- transaction: vi.fn(),
373
- } as unknown as DatabaseConnection<any>;
374
-
375
- expect(mockDb).toBeDefined();
376
- });
377
-
378
- it('should accept Transaction instance', () => {
379
- const mockTrx = {
380
- isTransaction: true,
381
- } as unknown as DatabaseConnection<any>;
382
-
383
- expect(mockTrx).toBeDefined();
384
- });
385
-
386
- it('should accept ControlledTransaction instance', () => {
387
- const mockControlledTrx = {
388
- isTransaction: true,
389
- } as unknown as DatabaseConnection<any>;
390
-
391
- expect(mockControlledTrx).toBeDefined();
392
- });
393
- });
6
+ describe('withTransaction', () => {
7
+ it('should execute callback within new transaction for Kysely instance', async () => {
8
+ const executeSpy = vi.fn(async (cb) => {
9
+ const mockTrx = { isTransaction: true } as Transaction<any>;
10
+ return cb(mockTrx);
11
+ });
12
+
13
+ const mockDb = {
14
+ isTransaction: false,
15
+ transaction: vi.fn(() => ({
16
+ execute: executeSpy,
17
+ })),
18
+ } as unknown as Kysely<any>;
19
+
20
+ const callback = vi.fn(async (trx) => {
21
+ expect(trx.isTransaction).toBe(true);
22
+ return 'result';
23
+ });
24
+
25
+ const result = await withTransaction(mockDb, callback);
26
+
27
+ expect(mockDb.transaction).toHaveBeenCalledTimes(1);
28
+ expect(executeSpy).toHaveBeenCalledTimes(1);
29
+ expect(callback).toHaveBeenCalledTimes(1);
30
+ expect(result).toBe('result');
31
+ });
32
+
33
+ it('should reuse existing transaction', async () => {
34
+ const mockTrx = {
35
+ isTransaction: true,
36
+ } as Transaction<any>;
37
+
38
+ const callback = vi.fn(async (trx) => {
39
+ expect(trx).toBe(mockTrx);
40
+ return 'reused';
41
+ });
42
+
43
+ const result = await withTransaction(mockTrx, callback);
44
+
45
+ expect(callback).toHaveBeenCalledWith(mockTrx);
46
+ expect(result).toBe('reused');
47
+ });
48
+
49
+ it('should handle ControlledTransaction by reusing it', async () => {
50
+ const mockControlledTrx = {
51
+ isTransaction: true,
52
+ } as any; // ControlledTransaction
53
+
54
+ const callback = vi.fn(async (_trx) => {
55
+ return 'controlled';
56
+ });
57
+
58
+ const result = await withTransaction(mockControlledTrx, callback);
59
+
60
+ expect(callback).toHaveBeenCalledWith(mockControlledTrx);
61
+ expect(result).toBe('controlled');
62
+ });
63
+
64
+ it('should propagate callback return value', async () => {
65
+ const executeSpy = vi.fn(async (cb) => {
66
+ const mockTrx = { isTransaction: true } as Transaction<any>;
67
+ return cb(mockTrx);
68
+ });
69
+
70
+ const mockDb = {
71
+ isTransaction: false,
72
+ transaction: vi.fn(() => ({
73
+ execute: executeSpy,
74
+ })),
75
+ } as unknown as Kysely<any>;
76
+
77
+ const result = await withTransaction(mockDb, async () => {
78
+ return { id: 123, name: 'Test' };
79
+ });
80
+
81
+ expect(result).toEqual({ id: 123, name: 'Test' });
82
+ });
83
+
84
+ it('should propagate errors from callback', async () => {
85
+ const executeSpy = vi.fn(async (cb) => {
86
+ const mockTrx = { isTransaction: true } as Transaction<any>;
87
+ return cb(mockTrx);
88
+ });
89
+
90
+ const mockDb = {
91
+ isTransaction: false,
92
+ transaction: vi.fn(() => ({
93
+ execute: executeSpy,
94
+ })),
95
+ } as unknown as Kysely<any>;
96
+
97
+ const error = new Error('Transaction failed');
98
+
99
+ await expect(
100
+ withTransaction(mockDb, async () => {
101
+ throw error;
102
+ }),
103
+ ).rejects.toThrow('Transaction failed');
104
+ });
105
+
106
+ it('should allow nested transactions with reuse', async () => {
107
+ const outerExecuteSpy = vi.fn(async (cb) => {
108
+ const mockTrx = { isTransaction: true } as Transaction<any>;
109
+ return cb(mockTrx);
110
+ });
111
+
112
+ const mockDb = {
113
+ isTransaction: false,
114
+ transaction: vi.fn(() => ({
115
+ execute: outerExecuteSpy,
116
+ })),
117
+ } as unknown as Kysely<any>;
118
+
119
+ const result = await withTransaction(mockDb, async (outerTrx) => {
120
+ // Inner call should reuse the transaction
121
+ return withTransaction(outerTrx, async (innerTrx) => {
122
+ expect(innerTrx).toBe(outerTrx);
123
+ return 'nested';
124
+ });
125
+ });
126
+
127
+ expect(mockDb.transaction).toHaveBeenCalledTimes(1);
128
+ expect(result).toBe('nested');
129
+ });
130
+
131
+ it('should work with database queries', async () => {
132
+ const executeSpy = vi.fn(async (cb) => {
133
+ const mockTrx = {
134
+ isTransaction: true,
135
+ selectFrom: vi.fn(() => ({
136
+ selectAll: vi.fn(() => ({
137
+ execute: vi.fn(async () => [
138
+ { id: 1, name: 'User 1' },
139
+ { id: 2, name: 'User 2' },
140
+ ]),
141
+ })),
142
+ })),
143
+ } as unknown as Transaction<any>;
144
+ return cb(mockTrx);
145
+ });
146
+
147
+ const mockDb = {
148
+ isTransaction: false,
149
+ transaction: vi.fn(() => ({
150
+ execute: executeSpy,
151
+ })),
152
+ } as unknown as Kysely<any>;
153
+
154
+ const result = await withTransaction(mockDb, async (trx) => {
155
+ const users = await (trx as any)
156
+ .selectFrom('users')
157
+ .selectAll()
158
+ .execute();
159
+ return users;
160
+ });
161
+
162
+ expect(result).toEqual([
163
+ { id: 1, name: 'User 1' },
164
+ { id: 2, name: 'User 2' },
165
+ ]);
166
+ });
167
+
168
+ it('should handle async operations in callback', async () => {
169
+ const executeSpy = vi.fn(async (cb) => {
170
+ const mockTrx = { isTransaction: true } as Transaction<any>;
171
+ return cb(mockTrx);
172
+ });
173
+
174
+ const mockDb = {
175
+ isTransaction: false,
176
+ transaction: vi.fn(() => ({
177
+ execute: executeSpy,
178
+ })),
179
+ } as unknown as Kysely<any>;
180
+
181
+ const result = await withTransaction(mockDb, async (_trx) => {
182
+ await new Promise((resolve) => setTimeout(resolve, 10));
183
+ return 'async-result';
184
+ });
185
+
186
+ expect(result).toBe('async-result');
187
+ });
188
+
189
+ it('should support different return types', async () => {
190
+ const executeSpy = vi.fn(async (cb) => {
191
+ const mockTrx = { isTransaction: true } as Transaction<any>;
192
+ return cb(mockTrx);
193
+ });
194
+
195
+ const mockDb = {
196
+ isTransaction: false,
197
+ transaction: vi.fn(() => ({
198
+ execute: executeSpy,
199
+ })),
200
+ } as unknown as Kysely<any>;
201
+
202
+ // Number
203
+ const numResult = await withTransaction(mockDb, async () => 42);
204
+ expect(numResult).toBe(42);
205
+
206
+ // String
207
+ const strResult = await withTransaction(mockDb, async () => 'test');
208
+ expect(strResult).toBe('test');
209
+
210
+ // Boolean
211
+ const boolResult = await withTransaction(mockDb, async () => true);
212
+ expect(boolResult).toBe(true);
213
+
214
+ // Array
215
+ const arrResult = await withTransaction(mockDb, async () => [1, 2, 3]);
216
+ expect(arrResult).toEqual([1, 2, 3]);
217
+
218
+ // Object
219
+ const objResult = await withTransaction(mockDb, async () => ({
220
+ key: 'value',
221
+ }));
222
+ expect(objResult).toEqual({ key: 'value' });
223
+ });
224
+
225
+ it('should maintain transaction isolation', async () => {
226
+ const executeSpy = vi.fn(async (cb) => {
227
+ const mockTrx = {
228
+ isTransaction: true,
229
+ id: 'trx-123',
230
+ } as unknown as Transaction<any>;
231
+ return cb(mockTrx);
232
+ });
233
+
234
+ const mockDb = {
235
+ isTransaction: false,
236
+ transaction: vi.fn(() => ({
237
+ execute: executeSpy,
238
+ })),
239
+ } as unknown as Kysely<any>;
240
+
241
+ await withTransaction(mockDb, async (trx1) => {
242
+ expect((trx1 as any).id).toBe('trx-123');
243
+
244
+ // Nested transaction reuses same transaction
245
+ await withTransaction(trx1, async (trx2) => {
246
+ expect(trx2).toBe(trx1);
247
+ expect((trx2 as any).id).toBe('trx-123');
248
+ });
249
+ });
250
+ });
251
+
252
+ it('should set isolation level when provided', async () => {
253
+ const executeSpy = vi.fn(async (cb) => {
254
+ const mockTrx = { isTransaction: true } as Transaction<any>;
255
+ return cb(mockTrx);
256
+ });
257
+
258
+ const setIsolationLevelSpy = vi.fn(() => ({
259
+ execute: executeSpy,
260
+ }));
261
+
262
+ const mockDb = {
263
+ isTransaction: false,
264
+ transaction: vi.fn(() => ({
265
+ setIsolationLevel: setIsolationLevelSpy,
266
+ execute: executeSpy,
267
+ })),
268
+ } as unknown as Kysely<any>;
269
+
270
+ const callback = vi.fn(async (_trx) => 'result');
271
+
272
+ const result = await withTransaction(mockDb, callback, {
273
+ isolationLevel: 'serializable',
274
+ });
275
+
276
+ expect(mockDb.transaction).toHaveBeenCalledTimes(1);
277
+ expect(setIsolationLevelSpy).toHaveBeenCalledWith('serializable');
278
+ expect(executeSpy).toHaveBeenCalledTimes(1);
279
+ expect(callback).toHaveBeenCalledTimes(1);
280
+ expect(result).toBe('result');
281
+ });
282
+
283
+ it('should not set isolation level when not provided', async () => {
284
+ const executeSpy = vi.fn(async (cb) => {
285
+ const mockTrx = { isTransaction: true } as Transaction<any>;
286
+ return cb(mockTrx);
287
+ });
288
+
289
+ const setIsolationLevelSpy = vi.fn(() => ({
290
+ execute: executeSpy,
291
+ }));
292
+
293
+ const mockDb = {
294
+ isTransaction: false,
295
+ transaction: vi.fn(() => ({
296
+ setIsolationLevel: setIsolationLevelSpy,
297
+ execute: executeSpy,
298
+ })),
299
+ } as unknown as Kysely<any>;
300
+
301
+ const callback = vi.fn(async (_trx) => 'result');
302
+
303
+ const result = await withTransaction(mockDb, callback);
304
+
305
+ expect(mockDb.transaction).toHaveBeenCalledTimes(1);
306
+ expect(setIsolationLevelSpy).not.toHaveBeenCalled();
307
+ expect(executeSpy).toHaveBeenCalledTimes(1);
308
+ expect(result).toBe('result');
309
+ });
310
+
311
+ it('should support different isolation levels', async () => {
312
+ const executeSpy = vi.fn(async (cb) => {
313
+ const mockTrx = { isTransaction: true } as Transaction<any>;
314
+ return cb(mockTrx);
315
+ });
316
+
317
+ const setIsolationLevelSpy = vi.fn(() => ({
318
+ execute: executeSpy,
319
+ }));
320
+
321
+ const mockDb = {
322
+ isTransaction: false,
323
+ transaction: vi.fn(() => ({
324
+ setIsolationLevel: setIsolationLevelSpy,
325
+ execute: executeSpy,
326
+ })),
327
+ } as unknown as Kysely<any>;
328
+
329
+ const levels = [
330
+ 'read uncommitted',
331
+ 'read committed',
332
+ 'repeatable read',
333
+ 'serializable',
334
+ 'snapshot',
335
+ ] as const;
336
+
337
+ for (const level of levels) {
338
+ setIsolationLevelSpy.mockClear();
339
+
340
+ await withTransaction(mockDb, async () => 'result', {
341
+ isolationLevel: level,
342
+ });
343
+
344
+ expect(setIsolationLevelSpy).toHaveBeenCalledWith(level);
345
+ }
346
+ });
347
+
348
+ it('should not set isolation level for existing transactions', async () => {
349
+ const mockTrx = {
350
+ isTransaction: true,
351
+ setIsolationLevel: vi.fn(),
352
+ } as unknown as Transaction<any>;
353
+
354
+ const callback = vi.fn(async (trx) => {
355
+ expect(trx).toBe(mockTrx);
356
+ return 'reused';
357
+ });
358
+
359
+ const result = await withTransaction(mockTrx, callback, {
360
+ isolationLevel: 'serializable',
361
+ });
362
+
363
+ expect((mockTrx as any).setIsolationLevel).not.toHaveBeenCalled();
364
+ expect(result).toBe('reused');
365
+ });
366
+ });
367
+
368
+ describe('DatabaseConnection type', () => {
369
+ it('should accept Kysely instance', () => {
370
+ const mockDb = {
371
+ isTransaction: false,
372
+ transaction: vi.fn(),
373
+ } as unknown as DatabaseConnection<any>;
374
+
375
+ expect(mockDb).toBeDefined();
376
+ });
377
+
378
+ it('should accept Transaction instance', () => {
379
+ const mockTrx = {
380
+ isTransaction: true,
381
+ } as unknown as DatabaseConnection<any>;
382
+
383
+ expect(mockTrx).toBeDefined();
384
+ });
385
+
386
+ it('should accept ControlledTransaction instance', () => {
387
+ const mockControlledTrx = {
388
+ isTransaction: true,
389
+ } as unknown as DatabaseConnection<any>;
390
+
391
+ expect(mockControlledTrx).toBeDefined();
392
+ });
393
+ });
394
394
  });