Skip to content

Commit

Permalink
feat(typescript): Improve type indexes for stricter object and class …
Browse files Browse the repository at this point in the history
…hooks
  • Loading branch information
daffl committed May 27, 2020
1 parent 54494ba commit 699f2fd
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 63 deletions.
32 changes: 27 additions & 5 deletions packages/hooks/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,44 @@ export interface WrapperAddon<F> {

export type WrappedFunction<F, T> = F&((...rest: any[]) => Promise<T>|Promise<HookContext>)&WrapperAddon<F>;

export function middleware (mw: Middleware[]) {
/**
* Initializes a hook settings object with the given middleware.
* @param mw The list of middleware
*/
export function middleware (mw: Middleware[] = []) {
const manager = new HookManager();

return manager.middleware(mw);
}

// hooks(fn, hookOptions)
/**
* Returns a new function that wraps an existing async function
* with hooks.
*
* @param fn The async function to add hooks to.
* @param manager An array of middleware or hook settings
* (`middleware([]).params()` etc.)
*/
export function hooks<F, T = any> (
fn: F, manager: HookManager
): WrappedFunction<F, T>;
// hooks(object, hookMap)
export function hooks<O> (obj: O, hookMap: HookMap|Middleware[]): O;
// @hooks(hookOptions)

/**
* Add hooks to one or more methods on an object or class.
* @param obj The object to add hooks to
* @param hookMap A map of middleware settings where the
* key is the method name.
*/
export function hooks<O> (obj: O|(new (...args: any[]) => O), hookMap: HookMap<O>|Middleware[]): O;

/**
* Decorate a class method with hooks.
* @param _manager The hooks settings
*/
export function hooks<T = any> (
_manager?: HookOptions
): any;

// Fallthrough to actual implementation
export function hooks (...args: any[]) {
const [ target, _hooks ] = args;
Expand Down
4 changes: 2 additions & 2 deletions packages/hooks/src/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { Middleware } from './compose';
import { functionHooks } from './function';
import { setMiddleware, convertOptions, HookOptions } from './base';

export interface HookMap {
[key: string]: HookOptions;
export type HookMap<O = any> = {
[L in keyof O]?: HookOptions;
}

export function objectHooks (_obj: any, hooks: HookMap|Middleware[]) {
Expand Down
100 changes: 50 additions & 50 deletions packages/hooks/test/function.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,24 +179,6 @@ describe('functionHooks', () => {
assert.equal(await fn('Dave'), 'Hello Changed');
});

// it('creates context with default params', async () => {
// const fn = hooks(hello, {
// middleware: [
// async (ctx, next) => {
// assert.equal(ctx.name, 'Dave');
// assert.deepEqual(ctx.params, {});

// ctx.name = 'Changed';

// await next();
// }
// ],
// context: withParams('name', ['params', {}])
// });

// assert.equal(await fn('Dave'), 'Hello Changed');
// });

it('assigns props to context', async () => {
const fn = hooks(hello, middleware([
async (ctx, next) => {
Expand Down Expand Up @@ -282,38 +264,6 @@ describe('functionHooks', () => {
assert.equal(called, 1);
});

// it('is chainable with .params on function', async () => {
// const hook = async function (this: any, context: HookContext, next: NextFunction) {
// await next();
// context.result += '!';
// };
// const exclamation = hooks(hello, middleware([hook]).params(['name', 'Dave']));

// const result = await exclamation();

// assert.equal(result, 'Hello Dave!');
// });

// it('is chainable with .params on object', async () => {
// const hook = async function (this: any, context: HookContext, next: NextFunction) {
// await next();
// context.result += '!';
// };
// const obj = {
// sayHi (name: any) {
// return `Hi ${name}`;
// }
// };

// hooks(obj, {
// sayHi: hooks([hook]).params('name')
// });

// const result = await obj.sayHi('Dave');

// assert.equal(result, 'Hi Dave!');
// });

it('conserves method properties', async () => {
const TEST = Symbol('test');
const hello = (name: any) => `Hi ${name}`;
Expand Down Expand Up @@ -358,4 +308,54 @@ describe('functionHooks', () => {

assert.deepEqual(Object.keys(resultContext), ['message', 'name', 'arguments', 'result']);
});

// it('creates context with default params', async () => {
// const fn = hooks(hello, {
// middleware: [
// async (ctx, next) => {
// assert.equal(ctx.name, 'Dave');
// assert.deepEqual(ctx.params, {});

// ctx.name = 'Changed';

// await next();
// }
// ],
// context: withParams('name', ['params', {}])
// });

// assert.equal(await fn('Dave'), 'Hello Changed');
// });

// it('is chainable with .params on function', async () => {
// const hook = async function (this: any, context: HookContext, next: NextFunction) {
// await next();
// context.result += '!';
// };
// const exclamation = hooks(hello, middleware([hook]).params(['name', 'Dave']));

// const result = await exclamation();

// assert.equal(result, 'Hello Dave!');
// });

// it('is chainable with .params on object', async () => {
// const hook = async function (this: any, context: HookContext, next: NextFunction) {
// await next();
// context.result += '!';
// };
// const obj = {
// sayHi (name: any) {
// return `Hi ${name}`;
// }
// };

// hooks(obj, {
// sayHi: hooks([hook]).params('name')
// });

// const result = await obj.sayHi('Dave');

// assert.equal(result, 'Hi Dave!');
// });
});
23 changes: 17 additions & 6 deletions packages/hooks/test/object.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
import { strict as assert } from 'assert';
import { hooks, middleware, HookContext, NextFunction } from '../src';

interface HookableObject {
test: string;
sayHi (name: string): Promise<string>;
addOne (number: number): Promise<number>;
}

interface Dummy {
sayHi (name: string): Promise<string>;
addOne (number: number): Promise<number>;
}

describe('objectHooks', () => {
let obj: any;
let DummyClass: any;
let obj: HookableObject;
let DummyClass: new () => Dummy;

beforeEach(() => {
obj = {
Expand All @@ -18,7 +29,7 @@ describe('objectHooks', () => {
}
};

DummyClass = class DummyClass {
DummyClass = class DummyClass implements Dummy {
async sayHi (name: string) {
return `Hi ${name}`;
}
Expand All @@ -33,7 +44,7 @@ describe('objectHooks', () => {
const hookedObj = hooks(obj, {
sayHi: middleware([async (ctx: HookContext, next: NextFunction) => {
assert.equal(ctx.method, 'sayHi');
assert.deepEqual(ctx, new obj.sayHi.Context({
assert.deepEqual(ctx, new (obj.sayHi as any).Context({
arguments: [ 'David' ],
method: 'sayHi',
self: obj
Expand All @@ -58,7 +69,7 @@ describe('objectHooks', () => {
it('hooks object and allows to customize context for method', async () => {
const hookedObj = hooks(obj, {
sayHi: middleware([async (ctx: HookContext, next: NextFunction) => {
assert.deepStrictEqual(ctx, new obj.sayHi.Context({
assert.deepStrictEqual(ctx, new (obj.sayHi as any).Context({
arguments: ['David'],
method: 'sayHi',
name: 'David',
Expand Down Expand Up @@ -148,7 +159,7 @@ describe('objectHooks', () => {
it('works with inheritance', async () => {
hooks(DummyClass, {
sayHi: middleware([async (ctx: HookContext, next: NextFunction) => {
assert.deepStrictEqual(ctx, new OtherDummy.prototype.sayHi.Context({
assert.deepStrictEqual(ctx, new (OtherDummy.prototype.sayHi as any).Context({
arguments: [ 'David' ],
method: 'sayHi',
self: instance
Expand Down

0 comments on commit 699f2fd

Please sign in to comment.