Skip to content

Commit c3fb638

Browse files
committed
refactoring
1 parent de810f8 commit c3fb638

15 files changed

+267
-183
lines changed

readme.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ It supports sync and async functions/methods
2121

2222
```ts
2323
/// track.ts
24-
export const tracker = new TryCatchFinallyHooksBuilder()
24+
export const hooks = new Hooks()
2525
.add(callStack)
2626
.add(measureDuration)
2727
.add(ctx => {
@@ -44,13 +44,12 @@ export const tracker = new TryCatchFinallyHooksBuilder()
4444
};
4545
})
4646

47-
export const trackFn = tracker.asFunctionWrapper.bind(tracker)
48-
export const trackMethod = tracker.asDecorator.bind(tracker)
49-
47+
export const track = hooks.create()
5048
// myClass.ts
5149

5250
class MyClass{
53-
@trackMethod({name:'Hello world'})
51+
@track({name:'Hello world'})
52+
//@hooks.decor({name:'Hello world'}) //alternative
5453
async helloWorld(url:string)
5554
{
5655
await doing()
@@ -61,11 +60,13 @@ class MyClass{
6160

6261
// myFunc.ts
6362

64-
export const myFunc = trackFn({name: 'My function'})((param1:number)=>{
63+
64+
export const myFunc = track({name: 'My function'}, (param1:number)=>{ //alternative: myFunc = hooks.wrap({name:'Hello world'}, (param1...)=>{...})
65+
6566
// doing something long running and risky
6667
step1()
6768
step2()
68-
const res = tracker.scope({name:'important step 3'},()=>{
69+
const res = track.scope({name:'important step 3'},()=>{
6970
return step3(param1)
7071
})
7172
return res

specs/combine-hooks.spec.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { callStack } from "@/callStack";
22
import { measureDuration } from "@/measureDuration";
3-
import { TryCatchFinallyHooksBuilder, ITryCatchFinallyHook } from "@/TryCatchFinallyHooks";
3+
import { ITryCatchFinallyHook } from "@/TryCatchFinallyHook";
4+
import { Hooks } from "@/Hooks";
45

56

67
export const logOnFinally: ITryCatchFinallyHook<{ args: { name?: string; }; name:string }> = {
@@ -14,7 +15,7 @@ export const logOnFinally: ITryCatchFinallyHook<{ args: { name?: string; }; name
1415
}};
1516

1617

17-
const track = new TryCatchFinallyHooksBuilder()
18+
const track = new Hooks()
1819
.add(callStack)
1920
.add(measureDuration)
2021
.add(logOnFinally)
@@ -41,16 +42,16 @@ const track = new TryCatchFinallyHooksBuilder()
4142
}
4243
}
4344
}
44-
})
45+
}).create()
4546
;
46-
const myTrackedFunction = track.asFunctionWrapper({ name: 'MyAction' })(
47+
const myTrackedFunction = track({ name: 'MyAction' },
4748
function myTrackedFunction(a: number, b: number) {
48-
track.current!.defer((op) => {
49+
track.hooks.context!.defer((op) => {
4950
console.log('Defered action invoked at finally section of MyAction! Outcome:', op.funcOutcome);
5051
});
5152

5253

53-
const res = track.asScope({ name: 'Some running operation...' }, () => {
54+
const res = track.scope({ name: 'Some running operation...' }, () => {
5455
return a + b;
5556
});
5657

specs/hooks.decorator.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { TryCatchFinallyHooksBuilder } from '@/TryCatchFinallyHooks';
1+
import { Hooks } from '@/Hooks';
22

33
test("log try finally asMethodDecorator", () => {
44
const log = jest.fn();
5-
const track = new TryCatchFinallyHooksBuilder().add<{ args: { name: string; }; }>({
5+
const track = new Hooks().add<{ args: { name: string; }; }>({
66
onTry(ctx) {
77
log("onTry", ctx.args.name);
88
return {
@@ -14,14 +14,14 @@ test("log try finally asMethodDecorator", () => {
1414
}
1515
};
1616
},
17-
}).createDecorator();
17+
});
1818

1919

2020

2121
const myFuncOrig = jest.fn((a, b) => a + b);
2222

2323
class MyClass {
24-
@track({ name: "MyAction" })
24+
@track.decor({ name: "MyAction" })
2525
myFunc(a: number, b: number) {
2626
return myFuncOrig(a, b);
2727
}

specs/hooks.function.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { TryCatchFinallyHooksBuilder } from '@/TryCatchFinallyHooks';
1+
import { Hooks } from '@/Hooks';
22

33
test("log try finally asFunction", () => {
44
const log = jest.fn();
5-
const track = new TryCatchFinallyHooksBuilder().add<{ args: { name: string; }; }>({
5+
const track = new Hooks().add<{ args: { name: string; }; }>({
66
onTry(ctx) {
77
log("onTry", ctx.args.name);
88
return {
@@ -17,7 +17,7 @@ test("log try finally asFunction", () => {
1717
});
1818

1919
const myFuncOrig = jest.fn((a, b) => a + b);
20-
const myFunc = track.asFunctionWrapper({ name: "MyAction" })(myFuncOrig);
20+
const myFunc = track.wrap({ name: "MyAction" }, myFuncOrig);
2121
const res = myFunc(11, 22);
2222

2323
expect(res).toBe(11 + 22);

specs/hooks.scope.spec.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import 'jest-extended'
2-
import { TryCatchFinallyHooksBuilder } from '@/TryCatchFinallyHooks'
2+
import { Hooks } from '@/Hooks'
33

44
test("log onTry asScope", () => {
55
const log = jest.fn();
6-
const track = new TryCatchFinallyHooksBuilder().add<{ args: { name: string; }; }>({
6+
const track = new Hooks().add<{ args: { name: string; }; }>({
77
onTry(ctx) {
88
log("onTry", ctx.args.name);
99
},
1010
});
1111

1212
const myFunc = jest.fn((a, b) => a + b);
13-
const res = track.asScope({ name: "MyAction" }, () => {
13+
const res = track.scope({ name: "MyAction" }, () => {
1414
return myFunc(11, 22);
1515
});
1616

@@ -23,7 +23,7 @@ test("log onTry asScope", () => {
2323

2424
test("log try finally asScope",()=>{
2525
const log = jest.fn()
26-
const track = new TryCatchFinallyHooksBuilder().add<{args:{name: string}}>({
26+
const track = new Hooks().add<{args:{name: string}}>({
2727
onTry(ctx) {
2828
log("onTry",ctx.args.name)
2929
return {
@@ -38,7 +38,7 @@ test("log try finally asScope",()=>{
3838
})
3939

4040
const myFunc = jest.fn((a,b)=>a+b)
41-
const res = track.asScope({name:"MyAction"},()=>{
41+
const res = track.scope({name:"MyAction"},()=>{
4242
return myFunc(11,22)
4343
})
4444

@@ -54,7 +54,7 @@ test("log try finally asScope",()=>{
5454
test("async log onTry asScope",async ()=>{
5555
const logTry = jest.fn()
5656
const logFinally = jest.fn()
57-
const track = new TryCatchFinallyHooksBuilder().add<{args:{name: string}}>({
57+
const track = new Hooks().add<{args:{name: string}}>({
5858
onTry(ctx) {
5959
logTry("onTry",ctx.args.name)
6060
return {
@@ -71,7 +71,7 @@ test("async log onTry asScope",async ()=>{
7171
await delay(500)
7272
return a+b
7373
})
74-
const res = await track.asScope({name:"MyAction"},async ()=>{
74+
const res = await track.scope({name:"MyAction"},async ()=>{
7575
return await myFunc(11,22)
7676
})
7777

src/Hooks.ts

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { ITryCatchFinallyHook, TryCatchFinallyHook, DecoratorArgsOf, ITryCatchFinallyHooksCollection, IHooksFunc } from "./TryCatchFinallyHook";
2+
import { FunctionContext, WrappableFunction, createTryCatchFinally } from "./tryCatchFinally";
3+
4+
5+
/** @inheritDoc {@link ITryCatchFinallyHooksCollection} */
6+
export class Hooks<FullHookContext extends FunctionContext = FunctionContext> implements ITryCatchFinallyHooksCollection<FullHookContext>
7+
{
8+
private hooks:ITryCatchFinallyHook<any, any>[] = []
9+
10+
add<TNewContext extends {}={}, TryReturnContext={}>(hook: TryCatchFinallyHook<FullHookContext & TNewContext, TryReturnContext>): Hooks<FullHookContext & TNewContext & TryReturnContext>
11+
{
12+
if(hook instanceof Function)
13+
return this.add({onTry: hook} as any)
14+
15+
this.hooks.push(hook);
16+
return this as any;
17+
}
18+
19+
wrap<TFunc extends WrappableFunction>(args: DecoratorArgsOf<FullHookContext>|undefined, func:TFunc): TFunc
20+
wrap<TFunc extends WrappableFunction>(func:TFunc):TFunc
21+
//wrap(args?: DecoratorArgsOf<FullHookContext>): <TFunc extends WrappableFunction>(func:TFunc)=>TFunc // conflicts with decor
22+
wrap()
23+
{
24+
const _this = this
25+
const beforeHooksTry = this.beforeHooksTry.bind(this)
26+
const afterHooksTry = this.afterHooksTry?.bind(this)
27+
const [args, func] = arguments.length >= 2
28+
? [arguments[0] as DecoratorArgsOf<FullHookContext>|undefined, arguments[1] as WrappableFunction]
29+
: arguments[0] instanceof Function? [undefined, arguments[0] as WrappableFunction]
30+
: [arguments[0] as DecoratorArgsOf<FullHookContext>|undefined, undefined]
31+
32+
if(!func)
33+
return (func: WrappableFunction)=> this.wrap(args, func)
34+
35+
return createTryCatchFinally<typeof func, {args?: DecoratorArgsOf<FullHookContext>}>(func, {
36+
onTry(ctxTry) {
37+
if(args) (ctxTry as any).args = args
38+
const hooksSorted = _this.hooks
39+
const bht = beforeHooksTry(ctxTry as any)
40+
let onTryHooks = hooksSorted.map(hook=> hook.onTry(ctxTry)!).filter(h=>h)
41+
42+
onTryHooks = onTryHooks.reduce(
43+
([first,last], h)=>('lastInQueue' in h && h.lastInQueue? last.push(h): first.push(h), [first, last]),
44+
[[] as typeof onTryHooks,[] as typeof onTryHooks]
45+
).flat()
46+
47+
return {
48+
onFinally(ctxFinally) {
49+
for (const hr of onTryHooks) {
50+
hr.onFinally?.(ctxFinally)
51+
}
52+
bht.onFinally?.()
53+
},
54+
onCatch(ctxCatch) {
55+
for (const hr of onTryHooks) {
56+
hr.onCatch?.(ctxCatch)
57+
}
58+
bht.onCatch?.()
59+
}
60+
}
61+
},
62+
})
63+
}
64+
65+
/**
66+
* @param args
67+
* @returns
68+
*/
69+
decor(args?: DecoratorArgsOf<FullHookContext>): MethodDecorator & (<TFunc extends WrappableFunction>(func:TFunc)=>TFunc)
70+
{
71+
return ((_target:any, _propertyKey, descriptor) => {
72+
if(arguments.length==1 && typeof _target === 'function')
73+
return this.wrap(args, _target as any)
74+
const originalMethod = descriptor.value as any;
75+
descriptor.value = this.wrap(args, originalMethod)
76+
}) as MethodDecorator as any
77+
}
78+
79+
scope<TResult>(args: DecoratorArgsOf<FullHookContext>, scope:()=>TResult):TResult
80+
{
81+
return this.wrap(args, scope)()
82+
}
83+
84+
protected beforeHooksTry(ctx: FullHookContext):{
85+
onCatch?(): void
86+
onFinally?(): void
87+
}
88+
{
89+
const _this = this
90+
const prevContext = _this._currentContext
91+
this._currentContext = ctx as any
92+
93+
return {
94+
onFinally() {
95+
_this._currentContext = prevContext
96+
},
97+
}
98+
}
99+
protected afterHooksTry?(ctx: FullHookContext):{
100+
onCatch?(): void
101+
onFinally?(): void
102+
}
103+
104+
private _currentContext:(FullHookContext & FunctionContext)|undefined = undefined
105+
106+
107+
/**
108+
* Only safe to use in sync functions or in async functions before any awaited code.
109+
* Otherwise, the context may be changed by another async function - in that case use callStack hook instead
110+
*/
111+
get context(){
112+
return this._currentContext
113+
}
114+
115+
create():IHooksFunc<this>
116+
{
117+
const hookFunc:IHooksFunc<Hooks<FullHookContext>> = function()
118+
{
119+
const hooks = hookFunc.hooks
120+
121+
if(arguments[0] instanceof Function || arguments[1] instanceof Function)
122+
return hooks.wrap.apply(hooks, arguments as any)
123+
//decor or wrap
124+
return hooks.decor.apply(hooks, arguments as any)
125+
} as any
126+
hookFunc.hooks = this
127+
hookFunc.scope = this.scope.bind(this)
128+
Object.defineProperty(hookFunc, 'context', {get: ()=>hookFunc.hooks.context})
129+
return hookFunc as any //IHooksFunc<Hooks<FullHookContext>>
130+
}
131+
}

src/TryCatchFinallyHook.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { FunctionContext, WrappableFunction } from "./tryCatchFinally";
2+
3+
export interface TryCatchFinallyHookFunc<TParentContext = FunctionContext, TryReturnContext = {}> {
4+
(ctx: TParentContext & FunctionContext): void | {
5+
context?: TryReturnContext;
6+
onCatch?(ctx: FunctionContext & TParentContext & TryReturnContext): void;
7+
onFinally?(ctx: FunctionContext & TParentContext & TryReturnContext): void;
8+
/**
9+
* If true, the hook will be executed after all other hooks
10+
*/
11+
lastInQueue?: boolean;
12+
};
13+
}
14+
15+
export interface ITryCatchFinallyHook<TParentContext = FunctionContext, TrtReturnContext = {}> {
16+
onTry: TryCatchFinallyHookFunc<TParentContext, TrtReturnContext>;
17+
}
18+
19+
export type TryCatchFinallyHook<TParentContext = FunctionContext, TryReturnContext = {}> = TryCatchFinallyHookFunc<TParentContext, TryReturnContext> | ITryCatchFinallyHook<TParentContext, TryReturnContext>;
20+
21+
22+
export type HookContextOf<THook> = THook extends ITryCatchFinallyHook<infer T1, infer T2> ? T1 & T2 : never;
23+
24+
export type DecoratorArgsOf<THookContext> = THookContext extends { args?: infer TArgs extends {}; } ? TArgs : {};
25+
26+
export interface ITryCatchFinallyHooksCollection<THookContext extends FunctionContext=FunctionContext>
27+
{
28+
/**
29+
* @example
30+
* ```ts
31+
* class MyClass{
32+
* @track.decor({name: 'myMethod'})
33+
* myMethod(p1,p2){
34+
* ...doSomething...
35+
* }
36+
* }
37+
* ```
38+
*/
39+
decor(args?: DecoratorArgsOf<THookContext>): MethodDecorator & (<TFunc extends WrappableFunction>(func:TFunc)=>TFunc)
40+
41+
/**
42+
* @example
43+
* ```ts
44+
* const myFunc = track.wrap({name: 'myFunc'},(p1,p2)=>{
45+
* ...doSomething...
46+
* })
47+
* ```
48+
*/
49+
wrap<TFunc extends WrappableFunction>(args: DecoratorArgsOf<THookContext>, func:TFunc):TFunc
50+
wrap<TFunc extends WrappableFunction>(func:TFunc):TFunc
51+
52+
/**
53+
* @example
54+
* ```ts
55+
* const result = track.scope({name: 'some long running code block'},()=>{
56+
* ...doSomething...
57+
* })
58+
* ```
59+
*/
60+
scope<T>(args: DecoratorArgsOf<THookContext>, scopeFn:()=>T):T;
61+
62+
/**
63+
* add a hook to the collection, return this collection with upgraded type
64+
* @param hook
65+
*/
66+
add<TNewContext extends {}={}, TExtra={}>(hook: TryCatchFinallyHook<THookContext & TNewContext, TExtra>):ITryCatchFinallyHooksCollection<THookContext & TNewContext & TExtra>
67+
68+
readonly context: THookContext | undefined
69+
70+
//create(): IHooksFunc<ITryCatchFinallyHooksCollection<THookContext>>
71+
}
72+
73+
export type ContextOfHooks<THooksCollection extends ITryCatchFinallyHooksCollection<any>> = THooksCollection extends ITryCatchFinallyHooksCollection<infer T> ? T : never;
74+
75+
export type IHooksFunc<THooksCollection extends ITryCatchFinallyHooksCollection<any>>
76+
= THooksCollection['wrap']
77+
& THooksCollection['decor']
78+
& {
79+
scope: THooksCollection['scope']
80+
hooks: THooksCollection
81+
readonly context: ContextOfHooks<THooksCollection>
82+
}

0 commit comments

Comments
 (0)