Skip to content
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

feat(core): Add app.teardown functionality #2570

Merged
merged 2 commits into from
Mar 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
});