Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit8c1c907

Browse files
authored
feat: add transaction context store (#10211)
Create transaction context, that generates a random ID for eachtransaction, but also allows you to define your own id, for examplechange-request.
1 parent16c0f31 commit8c1c907

File tree

2 files changed

+293
-4
lines changed

2 files changed

+293
-4
lines changed

‎src/lib/db/transaction.test.ts‎

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
import{
2+
withTransactional,
3+
withRollbackTransaction,
4+
withFakeTransactional,
5+
inTransaction,
6+
typeTransactionUserParams,
7+
}from'./transaction.js';
8+
import{typeMock,vi}from'vitest';
9+
10+
interfaceMockService{
11+
getData:()=>string;
12+
saveData:(data:string)=>Promise<void>;
13+
}
14+
15+
describe('transaction utilities',()=>{
16+
letmockDb:any;
17+
letmockTransaction:any;
18+
letmockServiceFactory:Mock;
19+
letmockService:MockService;
20+
21+
beforeEach(()=>{
22+
mockTransaction={
23+
commit:vi.fn(),
24+
rollback:vi.fn(),
25+
isTransaction:true,
26+
select:vi.fn(),
27+
insert:vi.fn(),
28+
update:vi.fn(),
29+
userParams:undefined,
30+
};
31+
32+
mockDb={
33+
transaction:vi
34+
.fn()
35+
.mockImplementation((callback)=>callback(mockTransaction)),
36+
isTransaction:false,
37+
};
38+
39+
mockService={
40+
getData:vi.fn().mockReturnValue('test-data'),
41+
saveData:vi.fn().mockResolvedValue(undefined),
42+
};
43+
44+
mockServiceFactory=vi.fn().mockReturnValue(mockService);
45+
});
46+
47+
describe('withTransactional',()=>{
48+
it('should add transactional method to service',()=>{
49+
constserviceWithTransactional=withTransactional(
50+
mockServiceFactory,
51+
mockDb,
52+
);
53+
54+
expect(typeofserviceWithTransactional.transactional).toBe(
55+
'function',
56+
);
57+
expect(serviceWithTransactional.getData).toBe(mockService.getData);
58+
expect(serviceWithTransactional.saveData).toBe(
59+
mockService.saveData,
60+
);
61+
});
62+
63+
it('should execute callback within database transaction',async()=>{
64+
constserviceWithTransactional=withTransactional(
65+
mockServiceFactory,
66+
mockDb,
67+
);
68+
69+
constresult=awaitserviceWithTransactional.transactional(
70+
(service)=>{
71+
returnservice.getData();
72+
},
73+
);
74+
75+
expect(mockDb.transaction).toHaveBeenCalledTimes(1);
76+
expect(result).toBe('test-data');
77+
});
78+
79+
it('should set default userParams when no transactionContext provided',async()=>{
80+
constserviceWithTransactional=withTransactional(
81+
mockServiceFactory,
82+
mockDb,
83+
);
84+
85+
awaitserviceWithTransactional.transactional((service)=>{
86+
returnservice.getData();
87+
});
88+
89+
expect(mockTransaction.userParams).toBeDefined();
90+
expect(mockTransaction.userParams.type).toBe('transaction');
91+
expect(mockTransaction.userParams.value).toBeDefined();
92+
expect(typeofmockTransaction.userParams.value).toBe('number');
93+
});
94+
95+
it('should use provided transactionContext when given',async()=>{
96+
constserviceWithTransactional=withTransactional(
97+
mockServiceFactory,
98+
mockDb,
99+
);
100+
constcustomContext:TransactionUserParams={
101+
type:'change-request',
102+
value:42,
103+
};
104+
105+
awaitserviceWithTransactional.transactional((service)=>{
106+
returnservice.getData();
107+
},customContext);
108+
109+
expect(mockTransaction.userParams).toEqual(customContext);
110+
expect(mockTransaction.userParams.type).toBe('change-request');
111+
expect(mockTransaction.userParams.value).toBe(42);
112+
});
113+
114+
it('should generate unique numeric IDs for default context',async()=>{
115+
constserviceWithTransactional=withTransactional(
116+
mockServiceFactory,
117+
mockDb,
118+
);
119+
constuserParamsValues:number[]=[];
120+
121+
for(leti=0;i<3;i++){
122+
awaitserviceWithTransactional.transactional((service)=>{
123+
userParamsValues.push(mockTransaction.userParams.value);
124+
returnservice.getData();
125+
});
126+
}
127+
128+
expect(userParamsValues).toHaveLength(3);
129+
expect(userParamsValues.every((id)=>typeofid==='number')).toBe(
130+
true,
131+
);
132+
expect(newSet(userParamsValues).size).toBe(3);
133+
});
134+
135+
it('should create transactional service with transaction instance',async()=>{
136+
constserviceWithTransactional=withTransactional(
137+
mockServiceFactory,
138+
mockDb,
139+
);
140+
141+
awaitserviceWithTransactional.transactional((service)=>{
142+
returnservice.getData();
143+
});
144+
145+
expect(mockServiceFactory).toHaveBeenCalledWith(mockTransaction);
146+
});
147+
148+
it('should handle promise-based callbacks',async()=>{
149+
constserviceWithTransactional=withTransactional(
150+
mockServiceFactory,
151+
mockDb,
152+
);
153+
154+
constresult=awaitserviceWithTransactional.transactional(
155+
async(service)=>{
156+
awaitservice.saveData('new-data');
157+
return'success';
158+
},
159+
);
160+
161+
expect(result).toBe('success');
162+
expect(mockService.saveData).toHaveBeenCalledWith('new-data');
163+
});
164+
165+
it('should propagate errors from callback',async()=>{
166+
constserviceWithTransactional=withTransactional(
167+
mockServiceFactory,
168+
mockDb,
169+
);
170+
consterror=newError('Test error');
171+
172+
awaitexpect(
173+
serviceWithTransactional.transactional(()=>{
174+
throwerror;
175+
}),
176+
).rejects.toThrow('Test error');
177+
});
178+
});
179+
180+
describe('withRollbackTransaction',()=>{
181+
beforeEach(()=>{
182+
mockDb.transaction=vi.fn().mockResolvedValue(mockTransaction);
183+
});
184+
185+
it('should add rollbackTransaction method to service',()=>{
186+
constserviceWithRollback=withRollbackTransaction(
187+
mockServiceFactory,
188+
mockDb,
189+
);
190+
191+
expect(typeofserviceWithRollback.rollbackTransaction).toBe(
192+
'function',
193+
);
194+
expect(serviceWithRollback.getData).toBe(mockService.getData);
195+
expect(serviceWithRollback.saveData).toBe(mockService.saveData);
196+
});
197+
198+
it('should execute callback and rollback transaction',async()=>{
199+
constserviceWithRollback=withRollbackTransaction(
200+
mockServiceFactory,
201+
mockDb,
202+
);
203+
204+
constresult=awaitserviceWithRollback.rollbackTransaction(
205+
(service)=>{
206+
returnservice.getData();
207+
},
208+
);
209+
210+
expect(mockDb.transaction).toHaveBeenCalledTimes(1);
211+
expect(mockTransaction.rollback).toHaveBeenCalledTimes(1);
212+
expect(result).toBe('test-data');
213+
});
214+
});
215+
216+
describe('withFakeTransactional',()=>{
217+
it('should add transactional method to service',()=>{
218+
constserviceWithFakeTransactional=
219+
withFakeTransactional(mockService);
220+
221+
expect(typeofserviceWithFakeTransactional.transactional).toBe(
222+
'function',
223+
);
224+
expect(serviceWithFakeTransactional.getData).toBe(
225+
mockService.getData,
226+
);
227+
expect(serviceWithFakeTransactional.saveData).toBe(
228+
mockService.saveData,
229+
);
230+
});
231+
232+
it('should execute callback directly without transaction',async()=>{
233+
constserviceWithFakeTransactional=
234+
withFakeTransactional(mockService);
235+
236+
constresult=awaitserviceWithFakeTransactional.transactional(
237+
(service)=>{
238+
returnservice.getData();
239+
},
240+
);
241+
242+
expect(result).toBe('test-data');
243+
});
244+
});
245+
246+
describe('inTransaction',()=>{
247+
it('should execute callback directly when db is already a transaction',async()=>{
248+
consttransactionDb={ ...mockDb,isTransaction:true};
249+
constcallback=vi.fn().mockReturnValue('result');
250+
251+
constresult=awaitinTransaction(transactionDb,callback);
252+
253+
expect(result).toBe('result');
254+
expect(callback).toHaveBeenCalledWith(transactionDb);
255+
expect(transactionDb.transaction).not.toHaveBeenCalled();
256+
});
257+
258+
it('should create new transaction when db is not a transaction',async()=>{
259+
constcallback=vi.fn().mockReturnValue('result');
260+
261+
constresult=awaitinTransaction(mockDb,callback);
262+
263+
expect(result).toBe('result');
264+
expect(mockDb.transaction).toHaveBeenCalledTimes(1);
265+
expect(callback).toHaveBeenCalledWith(mockTransaction);
266+
});
267+
});
268+
});

‎src/lib/db/transaction.ts‎

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
importtype{Knex}from'knex';
22
importtype{IUnleashConfig}from'../types/index.ts';
33

4+
exportinterfaceTransactionUserParams{
5+
type:'change-request'|'transaction';
6+
value:number;
7+
}
8+
9+
functiongenerateNumericTransactionId():number{
10+
consttimestamp=Date.now();
11+
constrandom=Math.floor(Math.random()*1000);
12+
returntimestamp*1000+random;
13+
}
14+
415
exporttypeKnexTransaction=Knex.Transaction;
516

617
exporttypeMockTransaction=null;
@@ -38,7 +49,10 @@ export type ServiceFactory<S> = (
3849
)=>DeferredServiceFactory<S>;
3950

4051
exporttypeWithTransactional<S>=S&{
41-
transactional:<R>(fn:(service:S)=>R)=>Promise<R>;
52+
transactional:<R>(
53+
fn:(service:S)=>R,
54+
transactionContext?:TransactionUserParams,
55+
)=>Promise<R>;
4256
};
4357

4458
exporttypeWithRollbackTransaction<S>=S&{
@@ -75,10 +89,17 @@ export function withTransactional<S>(
7589
):WithTransactional<S>{
7690
constservice=serviceFactory(db)asWithTransactional<S>;
7791

78-
service.transactional=async<R>(fn:(service:S)=>R)=>
79-
// Maybe: inTransaction(db, async (trx: Knex.Transaction) => fn(serviceFactory(trx)));
80-
// this assumes that the caller didn't start a transaction already and opens a new one.
92+
service.transactional=async<R>(
93+
fn:(service:S)=>R,
94+
transactionContext?:TransactionUserParams,
95+
)=>
8196
db.transaction(async(trx:Knex.Transaction)=>{
97+
constdefaultContext:TransactionUserParams={
98+
type:'transaction',
99+
value:generateNumericTransactionId(),
100+
};
101+
102+
trx.userParams=transactionContext||defaultContext;
82103
consttransactionalService=serviceFactory(trx);
83104
returnfn(transactionalService);
84105
});

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp