Skip to content

Add basic hooks support (v4) #115

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed

Conversation

hossam-nasr
Copy link
Contributor

This PR lays the groundwork to add basic support for hooks in v4. This is mostly a thin wrapper over the existing hooks Core API.

Hooks supported

  1. App Start hooks
  2. App terminate hooks
  3. Pre-Invocation hooks
  4. Post-Invocation hooks

Registering hooks

You can register the hook you desire as below:

const onStartHandler: AppStartHandler = async (_context: AppStartContext) => {
    console.log('this hook will be run at the start of the app.');
}
app.onStart(onStartHandler);

const onTerminateHandler: AppTerminateHandler = async (_context: AppTerminateContext) => {
    console.log('This hook will be run before grateful shutdown of the app.');
};
app.onTerminate(onTerminateHandler);

const preInvocHandler: PreInvocationHandler = async (_context: PreInvocationContext) => {
    console.log("This hook will be run before every invocation.");
}
app.onPreInvocation(preInvocHandler);

const postInvocHandler: PostInvocationHandler = async (_context: PostInvocationContext) => {
    console.log("This hook will be run after every invocation.");
}
app.onPostInvocation(postInvocHandler);

Sharing data between hooks

The hookData and appHookData from the core API are replaced with context.get, context.set (replacing hookData), and context.getGlobal and context.setGlobal (replacing appHookData):

const onStartFunc: AppStartHandler = async (context: AppStartContext) => {
    // this will be available to all hooks
    context.setGlobal('globalKey', 'globalValue');
    context.set('localKey', 'localValue');
}
app.onStart(onStartFunc);

const preInvocHook: PreInvocationHandler = async (context: PreInvocationContext) => {
    assert(context.getGlobal('globalKey') === 'globalValue');
    assert(typeof context.get('appOnlyKey') === 'undefined');

    context.set('invocationOnlyKey', 'invocationOnlyValue');

}
app.onPreInvocation(preInvocHook);

const postInvocHook: PostInvocationHandler = async (context: PostInvocationContext) => {
    assert(context.getGlobal('globalKey') === 'globalValue');
    assert(typeof context.get('appOnlyKey') === 'undefined');
    assert(context.get('invocationOnlyKey') === 'invocationOnlyValue');
}
app.onPostInvocation(postInvocHook);

const onTerminateHook: AppTerminateHandler = async (context: AppTerminateContext) => {
    assert(context.get('appOnlyKey') === 'appOnlyValue');
    assert(context.getGlobal('globalKey') === 'globalValue');
    assert(typeof context.get('invocationOnlyKey') === 'undefined');
};
app.onTerminate(onTerminateHook);

/**
* Represents a type which can release resources, such as event listening or a timer.
*/
export declare class Disposable {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disposable is not intended to be unique to hooks. You could give it its own file, but since it's a simple class I'd probably just move it directly into index.d.ts

* @param handler the handler for the event
* @returns a `Disposable` object that can be used to unregister the hook
*/
export function onStart(handler: AppStartHandler): Disposable;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm leaning towards this organization:

app.hook.start
app.hook.terminate
app.hook.preInvocation
app.hook.postInvocation

I'm not a big of "on". I feel like we could've easily done that for the trigger registrations (app.onHttp, app.onTimer), and I don't want to be inconsistent and start using "on" for hooks. That being said, I still like grouping all the hooks together to make the Intellisense cleaner. Rather than group them with the "on" prefix, I think a more explicit "hook" group would make sense.

It's a bit weird to have hook on app when input/output/trigger aren't on app, but I feel like it's okay because the latter don't actually register anything.

* @param propertyName The name of the property to set
* @param value The value to set
*/
setGlobal(propertyName: string, value: unknown): void;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I don't want us to feel obligated to follow the core api, especially since we know we want to clean up the hook data. Taking a step back, how is setGlobal and getGlobal better than just using a global variable?

Aka rather than this:

const onStartFunc: AppStartHandler = async (context: AppStartContext) => {
    context.setGlobal('globalKey', 'test123');
}
app.onStart(onStartFunc);

const onTerminateHook: AppTerminateHandler = async (context: AppTerminateContext) => {
    assert(context.getGlobal('globalKey') === 'test123');
};
app.onTerminate(onTerminateHook);

users could do this:

let globalValue: string;

const onStartFunc: AppStartHandler = async (context: AppStartContext) => {
    globalValue = 'test123';
}
app.onStart(onStartFunc);

const onTerminateHook: AppTerminateHandler = async (context: AppTerminateContext) => {
    assert(globalValue === 'test123');
};
app.onTerminate(onTerminateHook);

If the user is defining their own global variables, they can more easily set the type (aka string instead of unknown), and they don't have to deal with the key (mispelling, conflicting with another key, etc.).

Lastly, I think it would simplify our lives if we only need to worry about invocation-scoped hook data, which IMO is something users can't accomplish on their own so it's much more valuable.

* Object passed to PreInvocationContext constructors.
* For testing purposes only
*/
export interface PreInvocationContextInit extends InvocationHookContextInit {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like you told me this already, but I'm confused why we have both PreInvocationContextInit and PreInvocationCoreContext, with "core" nested into "init". Can we have just one object without duplication/nesting?

@ejizba
Copy link
Contributor

ejizba commented Oct 31, 2023

Closing due to lack of activity

@ejizba ejizba closed this Oct 31, 2023
@ejizba ejizba added hooks and removed hooks labels Nov 1, 2023
@ejizba ejizba mentioned this pull request Nov 1, 2023
@ejizba ejizba deleted the hossamnasr/hooks-basic branch May 29, 2024 21:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants