Skip to content

Commit cc42b66

Browse files
authored
Merge pull request #58 from tabkram/fix/trace-handler
fix: `@trace()` execution to correctly auto bind the class context to the `handler`
2 parents 79110ac + 835c86b commit cc42b66

File tree

4 files changed

+54
-31
lines changed

4 files changed

+54
-31
lines changed

src/execution/execute.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,19 +57,23 @@ export function execute<INPUT extends unknown[], OUTPUT, RESPONSE = OUTPUT, ERRO
5757
if (isAsync(blockFunction)) {
5858
return blockFunction
5959
.bind(this)(...inputs, ...extraInputs)
60-
.then((output: OUTPUT) => (successCallback ? successCallback(output, true) : (output as unknown as RESPONSE)))
61-
.catch((error) => (errorCallback ? errorCallback(error, true) : error));
60+
.then((output: OUTPUT) =>
61+
successCallback ? (successCallback.bind(this) as typeof successCallback)(output, true) : (output as unknown as RESPONSE)
62+
)
63+
.catch((error) => (errorCallback ? (errorCallback.bind(this) as typeof errorCallback)(error, true) : error));
6264
} else {
6365
try {
6466
const result = blockFunction.bind(this)(...inputs, ...extraInputs);
6567
if (result instanceof Promise) {
6668
return result
67-
.then((output: OUTPUT) => (successCallback ? successCallback(output, true) : (output as unknown as RESPONSE)))
68-
.catch((error) => (errorCallback ? errorCallback(error, true) : error));
69+
.then((output: OUTPUT) =>
70+
successCallback ? (successCallback.bind(this) as typeof successCallback)(output, true) : (output as unknown as RESPONSE)
71+
)
72+
.catch((error) => (errorCallback ? (errorCallback.bind(this) as typeof errorCallback)(error, true) : error));
6973
}
70-
return successCallback ? successCallback(result, false) : (result as unknown as RESPONSE);
74+
return successCallback ? (successCallback.bind(this) as typeof successCallback)(result, false) : (result as unknown as RESPONSE);
7175
} catch (error) {
72-
return errorCallback ? errorCallback(error, false) : error;
76+
return errorCallback ? (errorCallback.bind(this) as typeof errorCallback)(error, false) : error;
7377
}
7478
}
7579
}

src/trace/trace.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { ExecutionTimer } from '../timer/executionTimer';
1111

1212
export interface TraceContext<O> extends ExecutionTrace<Array<unknown>, O> {
1313
metadata: FunctionMetadata;
14+
1415
[key: string]: unknown;
1516
}
1617

@@ -78,7 +79,7 @@ export function executionTrace<O>(
7879
blockFunction.bind(this),
7980
inputs,
8081
[traceContext],
81-
(outputs: O, isPromise: boolean): Promise<ExecutionTrace<Array<unknown>, Awaited<O>>> | ExecutionTrace<Array<unknown>, O> => {
82+
function (outputs: O, isPromise: boolean): Promise<ExecutionTrace<Array<unknown>, Awaited<O>>> | ExecutionTrace<Array<unknown>, O> {
8283
const { startTime, ...traceContextWithoutStartTime } = traceContext;
8384
traceContext = {
8485
...traceContextWithoutStartTime,
@@ -92,7 +93,7 @@ export function executionTrace<O>(
9293
}
9394
return traceContext;
9495
},
95-
(e, isPromise: boolean): Promise<ExecutionTrace<Array<unknown>, Awaited<O>>> | ExecutionTrace<Array<unknown>, O> => {
96+
function (e, isPromise: boolean): Promise<ExecutionTrace<Array<unknown>, Awaited<O>>> | ExecutionTrace<Array<unknown>, O> {
9697
const { startTime, ...traceContextWithoutStartTime } = traceContext;
9798
traceContext = {
9899
...traceContextWithoutStartTime,

src/trace/traceDecorator.spec.ts

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,27 @@ describe('trace decorator', () => {
2020
errors: expect.anything()
2121
};
2222

23-
describe('Synchronous functions', () => {
24-
const helloWorldTraceHandlerMock = jest.fn();
23+
describe('Tracing synchronous functions', () => {
24+
const traceHandlerMock = jest.fn();
2525

2626
class SyncClass {
27-
@trace(helloWorldTraceHandlerMock)
27+
greetingFrom = ['hello from class'];
28+
29+
@trace(function (...args) {
30+
this.greetingFrom.push('hello from trace handler');
31+
traceHandlerMock(...args);
32+
expect(this.greetingFrom).toEqual(['hello from class', 'hello from method', 'hello from trace handler']);
33+
})
2834
helloWorld(): string {
35+
this.greetingFrom.push('hello from method');
2936
return 'Hello World';
3037
}
3138
}
3239

33-
it('should trace a synchronous function', () => {
40+
it('should trace a synchronous function and verify context', () => {
3441
const instance = new SyncClass();
35-
const response = instance.helloWorld();
36-
37-
expect(response).toEqual('Hello World');
38-
expect(helloWorldTraceHandlerMock).toHaveBeenCalledWith(
42+
expect(instance.helloWorld()).toBe('Hello World');
43+
expect(traceHandlerMock).toHaveBeenCalledWith(
3944
expect.objectContaining({
4045
metadata: expect.objectContaining({
4146
class: 'SyncClass',
@@ -52,11 +57,18 @@ describe('trace decorator', () => {
5257
});
5358

5459
describe('Asynchronous functions', () => {
55-
const helloWorldAsyncTraceHandlerMock = jest.fn();
60+
const asyncTraceHandlerMock = jest.fn();
5661

5762
class AsyncClass {
58-
@trace(helloWorldAsyncTraceHandlerMock)
63+
greetingFrom = ['hello from class'];
64+
65+
@trace(function (...args) {
66+
this.greetingFrom.push('hello from async trace handler');
67+
asyncTraceHandlerMock(...args);
68+
expect(this.greetingFrom).toEqual(['hello from class', 'hello from async method', 'hello from async trace handler']);
69+
})
5970
async helloWorldAsync(): Promise<string> {
71+
this.greetingFrom.push('hello from async method');
6072
return 'Hello World async';
6173
}
6274
}
@@ -66,7 +78,7 @@ describe('trace decorator', () => {
6678
const response = await instance.helloWorldAsync();
6779

6880
expect(response).toEqual('Hello World async');
69-
expect(helloWorldAsyncTraceHandlerMock).toHaveBeenCalledWith(
81+
expect(asyncTraceHandlerMock).toHaveBeenCalledWith(
7082
expect.objectContaining({
7183
metadata: expect.objectContaining({
7284
class: 'AsyncClass',
@@ -126,13 +138,21 @@ describe('trace decorator', () => {
126138

127139
@trace(traceHandlerFetchDataMock, traceContextFetchData)
128140
@empty()
129-
async fetchDataFunctionOnAnotherDecorator(url: string, traceContext: Record<string, unknown> = {}): Promise<{ data: string }> {
141+
async fetchDataFunctionOnAnotherDecorator(
142+
url: string,
143+
traceContext: Record<string, unknown> = {}
144+
): Promise<{
145+
data: string;
146+
}> {
130147
return this.fetchDataFunction(url, traceContext);
131148
}
132149

133150
@trace(traceHandlerFetchDataMock, traceContextFetchData, { errorStrategy: 'catch' })
134151
@empty()
135-
async fetchDataFunctionOnAnotherDecoratorCoughtError(url: string, traceContext: Record<string, unknown> = {}): Promise<{ data: string }> {
152+
async fetchDataFunctionOnAnotherDecoratorCoughtError(
153+
url: string,
154+
traceContext: Record<string, unknown> = {}
155+
): Promise<{ data: string }> {
136156
return this.fetchDataFunction(url, traceContext);
137157
}
138158
}
@@ -245,10 +265,10 @@ describe('trace decorator', () => {
245265
expect(response).toEqual(0.5);
246266
});
247267

248-
249268
it('should async trace error divisionFunctionOnAnotherDecorator', async () => {
250269
expect(() => new MyClass().divisionFunctionOnAnotherDecorator(1, 0)).toThrow('Throwing because division by zero is not allowed.');
251-
expect(traceHandlerDivisionMock).toHaveBeenNthCalledWith(2,
270+
expect(traceHandlerDivisionMock).toHaveBeenNthCalledWith(
271+
2,
252272
expect.objectContaining({
253273
metadata: expect.objectContaining({
254274
class: 'MyClass',
@@ -288,7 +308,6 @@ describe('trace decorator', () => {
288308
expect(response).toMatchObject({ data: 'Success' });
289309
});
290310

291-
292311
it('should async trace error fetchDataFunctionOnAnotherDecorator', async () => {
293312
await expect(new MyClass().fetchDataFunctionOnAnotherDecorator('invalid-url')).rejects.toThrow('Invalid URL provided.');
294313
expect(traceHandlerFetchDataMock).toHaveBeenNthCalledWith(
@@ -311,7 +330,7 @@ describe('trace decorator', () => {
311330
});
312331

313332
it('should async trace error fetchDataFunctionOnAnotherDecorator cought', async () => {
314-
const response= await new MyClass().fetchDataFunctionOnAnotherDecoratorCoughtError('invalid-url');
333+
const response = await new MyClass().fetchDataFunctionOnAnotherDecoratorCoughtError('invalid-url');
315334
expect(traceHandlerFetchDataMock).toHaveBeenNthCalledWith(
316335
2,
317336
expect.objectContaining({
@@ -330,7 +349,7 @@ describe('trace decorator', () => {
330349
})
331350
);
332351

333-
expect(response).toMatchObject([{ 'code': undefined, 'message': 'Invalid URL provided.', 'name': 'Error' }]);
352+
expect(response).toMatchObject([{ code: undefined, message: 'Invalid URL provided.', name: 'Error' }]);
334353
});
335354
});
336355
});

src/trace/traceDecorator.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,24 +41,23 @@ export function trace<O>(
4141
originalMethod.bind(this),
4242
args,
4343
(traceContext) => {
44-
return traceHandler({ ...traceContext, ...thisTraceContext });
44+
return (traceHandler.bind(this) as typeof traceHandler)({ ...traceContext, ...thisTraceContext });
4545
},
4646
options
47-
)?.then((r) => options?.errorStrategy === 'catch' ? r.errors : r.outputs);
47+
)?.then((r) => (options?.errorStrategy === 'catch' ? r.errors : r.outputs));
4848
} else {
4949
const result = (executionTrace.bind(this) as typeof executionTrace<O>)(
5050
originalMethod.bind(this) as () => O,
5151
args,
5252
(traceContext) => {
53-
return traceHandler({ ...traceContext, ...thisTraceContext });
53+
return (traceHandler.bind(this) as typeof traceHandler)({ ...traceContext, ...thisTraceContext });
5454
},
5555
options
5656
);
5757
if (result instanceof Promise) {
58-
return result.then((r) => options?.errorStrategy === 'catch' ? r.errors : r.outputs);
58+
return result.then((r) => (options?.errorStrategy === 'catch' ? r.errors : r.outputs));
5959
}
6060
return result?.outputs;
61-
6261
}
6362
};
6463
};

0 commit comments

Comments
 (0)