Skip to content
This repository was archived by the owner on Jun 22, 2021. It is now read-only.

Commit 928e7dd

Browse files
authored
feat: Simplifies error handling. (#5)
BREAKING CHANGE: `errorCatcher` removed from `FactoryConfig` in favour of `handleTransaction`.
1 parent 23d7cf5 commit 928e7dd

20 files changed

+205
-118
lines changed

package-lock.json

+14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
},
2727
"dependencies": {
2828
"@js-entity-repos/core": "^6.0.2",
29-
"http-status-codes": "^1.3.0"
29+
"http-status-codes": "^1.3.0",
30+
"uuid": "^3.2.1"
3031
},
3132
"devDependencies": {
3233
"@ht2-labs/semantic-release": "1.0.31",
@@ -37,6 +38,7 @@
3738
"@types/express": "4.11.1",
3839
"@types/mocha": "2.2.48",
3940
"@types/source-map-support": "0.4.0",
41+
"@types/uuid": "3.4.3",
4042
"assert-rejects": "0.1.1",
4143
"axios": "0.18.0",
4244
"dotenv": "5.0.1",

readme.md

+11-6
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,17 @@ const todosFacade = factory<TodoEntity>({
3333
},
3434
// Optional property.
3535
defaultPaginationLimit: 10,
36-
// Optional property that catches errors from handlers.
37-
errorCatcher: (handler) => (req, res) => {
38-
handler(req, res).catch((err) => {
39-
res.status(500).send();
40-
});
41-
},
36+
// Optional property to handle transactions.
37+
handleTransaction: async ({ req, res }, handler) => {
38+
// The transactionId allow items found in logs to be matched with responses to users.
39+
const transactionId = uuid();
40+
try {
41+
await handler({ transactionId });
42+
} catch (err) {
43+
console.error({ err, req, res, transactionId})
44+
res.status(500).send(transactionId);
45+
}
46+
};
4247
service,
4348
});
4449
```

src/FacadeConfig.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import Facade from '@js-entity-repos/core/dist/Facade';
22
import Entity from '@js-entity-repos/core/dist/types/Entity';
33
import Filter from '@js-entity-repos/core/dist/types/Filter';
4-
import ErrorCatcher from './utils/ErrorCatcher';
4+
import TransactionHandler from './utils/TransactionHandler';
55

66
export default interface FacadeConfig<E extends Entity> {
77
readonly constructFilter: (filter: Filter<E>) => any;
88
readonly service: Facade<E>;
9-
readonly errorCatcher: ErrorCatcher;
9+
readonly handleTransaction: TransactionHandler;
1010
readonly defaultPaginationLimit: number;
1111
}

src/FactoryConfig.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import Facade from '@js-entity-repos/core/dist/Facade';
22
import Entity from '@js-entity-repos/core/dist/types/Entity';
33
import { Filter } from '@js-entity-repos/core/dist/types/Filter';
4-
import ErrorCatcher from './utils/ErrorCatcher';
4+
import TransactionHandler from './utils/TransactionHandler';
55

66
export default interface FactoryConfig<E extends Entity> {
77
readonly constructFilter?: (filter: Filter<E>) => any;
88
readonly service: Facade<E>;
9-
readonly errorCatcher?: ErrorCatcher;
9+
readonly handleTransaction?: TransactionHandler;
1010
readonly defaultPaginationLimit?: number;
1111
}

src/factory.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ import patchEntities from './functions/patchEntities';
1111
import removeEntities from './functions/removeEntities';
1212
import removeEntity from './functions/removeEntity';
1313
import replaceEntity from './functions/replaceEntity';
14-
import catchErrors from './utils/catchErrors';
14+
import handleTransaction from './utils/handleTransaction';
1515

1616
export default <E extends Entity>(factoryConfig: FactoryConfig<E>): Router => {
1717
const facadeConfig: FacadeConfig<E> = {
1818
constructFilter: (filter) => filter,
1919
defaultPaginationLimit: 10,
20-
errorCatcher: catchErrors,
20+
handleTransaction,
2121
...factoryConfig,
2222
};
2323
const router = Router();

src/functions/countEntities.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ import Entity from '@js-entity-repos/core/dist/types/Entity';
22
import { Request, Response } from 'express';
33
import { OK } from 'http-status-codes';
44
import FacadeConfig from '../FacadeConfig';
5-
import catchErrors from '../utils/catchErrors';
65
import getJsonQueryParam from '../utils/getJsonQueryParam';
76

87
export default <E extends Entity>(config: FacadeConfig<E>) => {
9-
return catchErrors(async (req: Request, res: Response) => {
10-
const { count } = await config.service.countEntities({
11-
filter: config.constructFilter(getJsonQueryParam(req.query, 'filter')),
8+
return async (req: Request, res: Response) => {
9+
await config.handleTransaction({ req, res }, async () => {
10+
const { count } = await config.service.countEntities({
11+
filter: config.constructFilter(getJsonQueryParam(req.query, 'filter')),
12+
});
13+
res.status(OK).json(count);
1214
});
13-
res.status(OK).json(count);
14-
});
15+
};
1516
};

src/functions/createEntity.ts

+8-7
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ import Entity from '@js-entity-repos/core/dist/types/Entity';
22
import { Request, Response } from 'express';
33
import { OK } from 'http-status-codes';
44
import FacadeConfig from '../FacadeConfig';
5-
import catchErrors from '../utils/catchErrors';
65

76
export default <E extends Entity>(config: FacadeConfig<E>) => {
8-
return catchErrors(async (req: Request, res: Response) => {
9-
const { entity } = await config.service.createEntity({
10-
entity: req.body,
11-
id: req.body.id,
7+
return async (req: Request, res: Response) => {
8+
await config.handleTransaction({ req, res }, async () => {
9+
const { entity } = await config.service.createEntity({
10+
entity: req.body,
11+
id: req.body.id,
12+
});
13+
res.status(OK).json(entity);
1214
});
13-
res.status(OK).json(entity);
14-
});
15+
};
1516
};

src/functions/getEntities.ts

+21-20
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,30 @@ import Entity from '@js-entity-repos/core/dist/types/Entity';
22
import { Request, Response } from 'express';
33
import { OK } from 'http-status-codes';
44
import FacadeConfig from '../FacadeConfig';
5-
import catchErrors from '../utils/catchErrors';
65
import getJsonQueryParam from '../utils/getJsonQueryParam';
76
import getNumberQueryParam from '../utils/getNumberQueryParam';
87

98
export default <E extends Entity>(config: FacadeConfig<E>) => {
10-
return catchErrors(async (req: Request, res: Response) => {
11-
const limit = getNumberQueryParam(req.query, 'limit', config.defaultPaginationLimit);
12-
const result = await config.service.getEntities({
13-
filter: config.constructFilter(getJsonQueryParam(req.query, 'filter')),
14-
pagination: {
15-
cursor: req.query.cursor,
16-
forward: req.query.forward === 'true',
17-
limit,
18-
},
19-
sort: getJsonQueryParam(req.query, 'sort'),
9+
return async (req: Request, res: Response) => {
10+
await config.handleTransaction({ req, res }, async () => {
11+
const limit = getNumberQueryParam(req.query, 'limit', config.defaultPaginationLimit);
12+
const result = await config.service.getEntities({
13+
filter: config.constructFilter(getJsonQueryParam(req.query, 'filter')),
14+
pagination: {
15+
cursor: req.query.cursor,
16+
forward: req.query.forward === 'true',
17+
limit,
18+
},
19+
sort: getJsonQueryParam(req.query, 'sort'),
20+
});
21+
res.status(OK);
22+
if (result.nextCursor !== undefined) {
23+
res.setHeader('x-entities-next-cursor', result.nextCursor);
24+
}
25+
if (result.previousCursor !== undefined) {
26+
res.setHeader('x-entities-previous-cursor', result.previousCursor);
27+
}
28+
res.json(result.entities);
2029
});
21-
res.status(OK);
22-
if (result.nextCursor !== undefined) {
23-
res.setHeader('x-entities-next-cursor', result.nextCursor);
24-
}
25-
if (result.previousCursor !== undefined) {
26-
res.setHeader('x-entities-previous-cursor', result.previousCursor);
27-
}
28-
res.json(result.entities);
29-
});
30+
};
3031
};

src/functions/getEntity.ts

+8-7
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@ import Entity from '@js-entity-repos/core/dist/types/Entity';
22
import { Request, Response } from 'express';
33
import { OK } from 'http-status-codes';
44
import FacadeConfig from '../FacadeConfig';
5-
import catchErrors from '../utils/catchErrors';
65
import getJsonQueryParam from '../utils/getJsonQueryParam';
76

87
export default <E extends Entity>(config: FacadeConfig<E>) => {
9-
return catchErrors(async (req: Request, res: Response) => {
10-
const { entity } = await config.service.getEntity({
11-
filter: config.constructFilter(getJsonQueryParam(req.query, 'filter')),
12-
id: req.params.id,
8+
return async (req: Request, res: Response) => {
9+
await config.handleTransaction({ req, res }, async () => {
10+
const { entity } = await config.service.getEntity({
11+
filter: config.constructFilter(getJsonQueryParam(req.query, 'filter')),
12+
id: req.params.id,
13+
});
14+
res.status(OK).json(entity);
1315
});
14-
res.status(OK).json(entity);
15-
});
16+
};
1617
};

src/functions/patchEntities.ts

+9-8
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@ import Entity from '@js-entity-repos/core/dist/types/Entity';
22
import { Request, Response } from 'express';
33
import { OK } from 'http-status-codes';
44
import FacadeConfig from '../FacadeConfig';
5-
import catchErrors from '../utils/catchErrors';
65
import getJsonQueryParam from '../utils/getJsonQueryParam';
76

87
export default <E extends Entity>(config: FacadeConfig<E>) => {
9-
return catchErrors(async (req: Request, res: Response) => {
10-
const { entity } = await config.service.patchEntity({
11-
filter: config.constructFilter(getJsonQueryParam(req.query, 'filter')),
12-
id: req.params.id,
13-
patch: req.body,
8+
return async (req: Request, res: Response) => {
9+
await config.handleTransaction({ req, res }, async () => {
10+
const { entity } = await config.service.patchEntity({
11+
filter: config.constructFilter(getJsonQueryParam(req.query, 'filter')),
12+
id: req.params.id,
13+
patch: req.body,
14+
});
15+
res.status(OK).json(entity);
1416
});
15-
res.status(OK).json(entity);
16-
});
17+
};
1718
};

src/functions/removeEntities.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ import Entity from '@js-entity-repos/core/dist/types/Entity';
22
import { Request, Response } from 'express';
33
import { NO_CONTENT } from 'http-status-codes';
44
import FacadeConfig from '../FacadeConfig';
5-
import catchErrors from '../utils/catchErrors';
65
import getJsonQueryParam from '../utils/getJsonQueryParam';
76

87
export default <E extends Entity>(config: FacadeConfig<E>) => {
9-
return catchErrors(async (req: Request, res: Response) => {
10-
await config.service.removeEntities({
11-
filter: config.constructFilter(getJsonQueryParam(req.query, 'filter')),
8+
return async (req: Request, res: Response) => {
9+
await config.handleTransaction({ req, res }, async () => {
10+
await config.service.removeEntities({
11+
filter: config.constructFilter(getJsonQueryParam(req.query, 'filter')),
12+
});
13+
res.status(NO_CONTENT).send();
1214
});
13-
res.status(NO_CONTENT).send();
14-
});
15+
};
1516
};

src/functions/removeEntity.ts

+8-7
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@ import Entity from '@js-entity-repos/core/dist/types/Entity';
22
import { Request, Response } from 'express';
33
import { NO_CONTENT } from 'http-status-codes';
44
import FacadeConfig from '../FacadeConfig';
5-
import catchErrors from '../utils/catchErrors';
65
import getJsonQueryParam from '../utils/getJsonQueryParam';
76

87
export default <E extends Entity>(config: FacadeConfig<E>) => {
9-
return catchErrors(async (req: Request, res: Response) => {
10-
await config.service.removeEntity({
11-
filter: config.constructFilter(getJsonQueryParam(req.query, 'filter')),
12-
id: req.params.id,
8+
return async (req: Request, res: Response) => {
9+
await config.handleTransaction({ req, res }, async () => {
10+
await config.service.removeEntity({
11+
filter: config.constructFilter(getJsonQueryParam(req.query, 'filter')),
12+
id: req.params.id,
13+
});
14+
res.status(NO_CONTENT).send();
1315
});
14-
res.status(NO_CONTENT).send();
15-
});
16+
};
1617
};

src/functions/replaceEntity.ts

+9-8
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@ import Entity from '@js-entity-repos/core/dist/types/Entity';
22
import { Request, Response } from 'express';
33
import { OK } from 'http-status-codes';
44
import FacadeConfig from '../FacadeConfig';
5-
import catchErrors from '../utils/catchErrors';
65
import getJsonQueryParam from '../utils/getJsonQueryParam';
76

87
export default <E extends Entity>(config: FacadeConfig<E>) => {
9-
return catchErrors(async (req: Request, res: Response) => {
10-
const { entity } = await config.service.replaceEntity({
11-
entity: req.body,
12-
filter: config.constructFilter(getJsonQueryParam(req.query, 'filter')),
13-
id: req.params.id,
8+
return async (req: Request, res: Response) => {
9+
await config.handleTransaction({ req, res }, async () => {
10+
const { entity } = await config.service.replaceEntity({
11+
entity: req.body,
12+
filter: config.constructFilter(getJsonQueryParam(req.query, 'filter')),
13+
id: req.params.id,
14+
});
15+
res.status(OK).json(entity);
1416
});
15-
res.status(OK).json(entity);
16-
});
17+
};
1718
};

src/utils/ErrorCatcher.ts

-7
This file was deleted.

src/utils/ErrorHandler.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Request, Response } from 'express';
2+
3+
export interface Opts {
4+
readonly req: Request;
5+
readonly res: Response;
6+
readonly err: any;
7+
readonly transactionId: string;
8+
}
9+
10+
type ErrorHandler = (opts: Opts) => void;
11+
12+
export default ErrorHandler;

src/utils/TransactionHandler.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Response } from 'express';
2+
import { Request } from 'express-serve-static-core';
3+
4+
export interface Opts {
5+
readonly req: Request;
6+
readonly res: Response;
7+
}
8+
9+
export interface HandlerOpts {
10+
readonly transactionId: string;
11+
}
12+
13+
export type Handler = (opts: HandlerOpts) => Promise<void>;
14+
15+
type TransactionHandler = (opts: Opts, handler: Handler) => Promise<void>;
16+
17+
export default TransactionHandler;

0 commit comments

Comments
 (0)