Skip to content

Commit

Permalink
feat(core): Add app.teardown functionality (#2570)
Browse files Browse the repository at this point in the history
  • Loading branch information
idaho authored Mar 28, 2022
1 parent c955696 commit fcdf524
Show file tree
Hide file tree
Showing 11 changed files with 186 additions and 4 deletions.
2 changes: 2 additions & 0 deletions packages/adapter-commons/src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,4 +231,6 @@ export class AdapterService<
}

async setup () {}

async teardown () {}
}
1 change: 1 addition & 0 deletions packages/express/src/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface ExpressOverrides<Services> {
listen(port: number, hostname: string, callback?: () => void): Promise<http.Server>;
listen(port: number|string|any, callback?: () => void): Promise<http.Server>;
listen(callback?: () => void): Promise<http.Server>;
close (): Promise<void>;
use: ExpressUseHandler<this, Services>;
}

Expand Down
17 changes: 16 additions & 1 deletion packages/express/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import express, { Express } from 'express';
import { Application as FeathersApplication, defaultServiceMethods } from '@feathersjs/feathers';
import { routing } from '@feathersjs/transport-commons';
import { createDebug } from '@feathersjs/commons';
import http from 'http';

import { Application } from './declarations';

Expand All @@ -26,6 +27,7 @@ export default function feathersExpress<S = any, C = any> (feathersApp?: Feather
const app = expressApp as any as Application<S, C>;
const { use: expressUse, listen: expressListen } = expressApp as any;
const feathersUse = feathersApp.use;
let server:http.Server | undefined;

Object.assign(app, {
use (location: string & keyof S, ...rest: any[]) {
Expand Down Expand Up @@ -69,12 +71,25 @@ export default function feathersExpress<S = any, C = any> (feathersApp?: Feather
},

async listen (...args: any[]) {
const server = expressListen.call(this, ...args);
server = expressListen.call(this, ...args);

await this.setup(server);
debug('Feathers application listening');

return server;
},

async close () {
if ( server ) {
server.close();

await new Promise((resolve) => {
server.on('close', () => { resolve(true) });
})
}

debug('Feathers application closing');
await this.teardown();
}
} as Application<S, C>);

Expand Down
36 changes: 36 additions & 0 deletions packages/express/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,42 @@ describe('@feathersjs/express', () => {
await new Promise(resolve => server.close(() => resolve(server)));
});

it('.close calls .teardown', async () => {
const app = feathersExpress(feathers());
let called = false;

app.use('/myservice', {
async get (id: Id) {
return { id };
},

async teardown (appParam, path) {
assert.strictEqual(appParam, app);
assert.strictEqual(path, 'myservice');
called = true;
}

});

await app.listen(8787);
await app.close();

assert.ok(called);
});

it('.close closes http server', async () => {
const app = feathersExpress(feathers());
let called = false;

const server = await app.listen(8787);
server.on('close', () => {
called = true;
})

await app.close();
assert.ok(called);
});

it('passes middleware as options', () => {
const feathersApp = feathers();
const app = feathersExpress(feathersApp);
Expand Down
22 changes: 22 additions & 0 deletions packages/feathers/src/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,4 +160,26 @@ export class Feathers<Services, Settings> extends EventEmitter implements Feathe
return this;
});
}

teardown () {
let promise = Promise.resolve();

// Teardown each service (pass the app so that they can look up other services etc.)
for (const path of Object.keys(this.services)) {
promise = promise.then(() => {
const service: any = this.service(path as any);

if (typeof service.teardown === 'function') {
debug(`Teardown service for \`${path}\``);

return service.teardown(this, path);
}
});
}

return promise.then(() => {
this._isSetup = false;
return this;
});
}
}
4 changes: 4 additions & 0 deletions packages/feathers/src/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ export interface ServiceMethods<T = any, D = Partial<T>> {
remove (id: NullableId, params?: Params): Promise<T | T[]>;

setup (app: Application, path: string): Promise<void>;

teardown (app: Application, path: string): Promise<void>;
}

export interface ServiceOverloads<T = any, D = Partial<T>> {
Expand Down Expand Up @@ -217,6 +219,8 @@ export interface FeathersApplication<Services = any, Settings = any> {
* @param map The application hook settings.
*/
hooks (map: HookOptions<this, any>): this;

teardown (cb?: () => Promise<void>): Promise<this>;
}

// This needs to be an interface instead of a type
Expand Down
1 change: 1 addition & 0 deletions packages/feathers/src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const protectedMethods = Object.keys(Object.prototype)
'error',
'hooks',
'setup',
'teardown',
'publish'
]);

Expand Down
50 changes: 49 additions & 1 deletion packages/feathers/test/application.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ describe('Feathers application', () => {
this.path = path;
},

async teardown (this: any, _app: any, path: string) {
this.path = path;
},

async create (data: any) {
return data;
}
Expand All @@ -114,7 +118,9 @@ describe('Feathers application', () => {
async removeListener (data: any) {
return data;
},
async setup () {}
async setup () {},

async teardown () {}
};

assert.throws(() => feathers().use('/dummy', dummyService, {
Expand All @@ -127,6 +133,11 @@ describe('Feathers application', () => {
}), {
message: '\'setup\' on service \'dummy\' is not allowed as a custom method name'
});
assert.throws(() => feathers().use('/dummy', dummyService, {
methods: ['create', 'teardown']
}), {
message: '\'teardown\' on service \'dummy\' is not allowed as a custom method name'
});
});

it('can use a root level service', async () => {
Expand Down Expand Up @@ -331,6 +342,43 @@ describe('Feathers application', () => {
});
});

describe('.teardown', () => {
it('app.teardown calls .teardown on all services', async () => {
const app = feathers();
let teardownCount = 0;

app.use('/dummy', {
async setup () {},
async teardown (appRef: any, path: any) {
teardownCount++;
assert.strictEqual(appRef, app);
assert.strictEqual(path, 'dummy');
}
});

app.use('/simple', {
get (id: string) {
return Promise.resolve({ id });
}
});

app.use('/dummy2', {
async setup () {},
async teardown (appRef: any, path: any) {
teardownCount++;
assert.strictEqual(appRef, app);
assert.strictEqual(path, 'dummy2');
}
});

await app.setup();
await app.teardown();

assert.equal((app as any)._isSetup, false);
assert.strictEqual(teardownCount, 2);
});
});

describe('mixins', () => {
class Dummy {
dummy = true;
Expand Down
1 change: 1 addition & 0 deletions packages/koa/src/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import '@feathersjs/authentication';

export type ApplicationAddons = {
listen (port?: number, ...args: any[]): Promise<Server>;
close (): Promise<void>;
}

export type Application<T = any, C = any> =
Expand Down
18 changes: 17 additions & 1 deletion packages/koa/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import koaQs from 'koa-qs';
import { Application as FeathersApplication } from '@feathersjs/feathers';
import { routing } from '@feathersjs/transport-commons';
import { createDebug } from '@feathersjs/commons';
import http from 'http';

import { Application } from './declarations';

Expand All @@ -28,6 +29,7 @@ export function koa<S = any, C = any> (feathersApp?: FeathersApplication<S, C>,
const app = feathersApp as any as Application<S, C>;
const { listen: koaListen, use: koaUse } = koaApp;
const feathersUse = feathersApp.use as any;
let server:http.Server | undefined;

Object.assign(app, {
use (location: string|Koa.Middleware, ...args: any[]) {
Expand All @@ -39,12 +41,26 @@ export function koa<S = any, C = any> (feathersApp?: FeathersApplication<S, C>,
},

async listen (port?: number, ...args: any[]) {
const server = koaListen.call(this, port, ...args);
server = koaListen.call(this, port, ...args);

await this.setup(server);
debug('Feathers application listening');

return server;
},

async close () {
if ( server ) {
server.close();

await new Promise((resolve) => {
server.on('close', () => { resolve(true) });
})
}

debug('Feathers server closed');

await this.teardown();
}
} as Application);

Expand Down
38 changes: 37 additions & 1 deletion packages/koa/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ describe('@feathersjs/koa', () => {

it('Koa wrapped and context.app are the same', async () => {
const app = koa(feathers());

app.use('/test', {
async get (id: Id) {
return { id };
Expand Down Expand Up @@ -140,6 +140,42 @@ describe('@feathersjs/koa', () => {
});
});

it('.close calls .teardown', async () => {
const app = koa(feathers());
let called = false;

app.use('/myservice', {
async get (id: Id) {
return { id };
},

async teardown (appParam, path) {
assert.strictEqual(appParam, app);
assert.strictEqual(path, 'myservice');
called = true;
}

});

await app.listen(8787);
await app.close();

assert.ok(called);
});

it('.close closes http server', async () => {
const app = koa(feathers());
let called = false;

const server = await app.listen(8787);
server.on('close', () => {
called = true;
})

await app.close();
assert.ok(called);
});

restTests('Services', 'todo', 8465);
restTests('Root service', '/', 8465);
});

0 comments on commit fcdf524

Please sign in to comment.