@autofleet/node-common 2.0.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ 22.9.0
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # AutoFleet Node Common
2
- This respostory is made in order have as much of common code as we can.
2
+ This repository is made in order have as much of common code as we can.
3
3
 
4
- Each line of code used in wrriten in this repo will be used in the enetire AutoFleet system.
4
+ Each line of code used in written in this repo will be used in the entire AutoFleet system.
5
5
 
6
6
  Make sure you have:
7
7
  * Tests
@@ -9,7 +9,7 @@ Make sure you have:
9
9
 
10
10
  ## Consts
11
11
 
12
- Currently we suppurt:
12
+ Currently we support:
13
13
  ```
14
14
  {
15
15
  'OK'
@@ -18,55 +18,11 @@ Currently we suppurt:
18
18
  }
19
19
  ```
20
20
 
21
- ## Network
22
- Server 2 Servers communication.
23
-
24
- Implemented:
25
- * Retriving service urls from environment
26
- * Retry - Using https://github.com/softonic/axios-retry
27
- * Caching - TBD
28
- * Syntatic response for fail - TBD
29
- * Circuit Breaking - TBD
30
-
31
- The API is just like [axios](https://github.com/axios/axios) api but the creation of new instance **must** have either `serviceName` or `serviceUrl` in options.
32
-
33
- In case `serviceName` used the constractor will look for an environment varible with the the name `<SERVICE_NAME>_SERVICE_HOST`.
34
-
35
- For Example:
36
- ```
37
- const { Network } = require('@autofleet/node-common');
38
-
39
- n = new Network({ serviceName: 'TEST' });
40
-
41
- n.get('/posts/1');
42
- ```
43
- .env file:
44
- ```
45
- RIDE_SERVICE_HOST=jsonplaceholder.typicode.com
46
- ```
47
-
48
- To learn more [click here](https://blog.risingstack.com/designing-microservices-architecture-for-failure/).
49
-
50
- ## Settings
51
-
52
- ### Adding settings
53
- For adding new setting you need to add it to the map.js file, please specify
54
- * name - descriptive name
55
- * description - few words about what it does + unit
56
- * type - supportable types: 'number', 'string', 'json'
57
- * defaultValue - default value
58
- * context - 'security' and 'operation' will not show in the simulator configuration
59
-
60
- See example.
61
-
62
- ## DeLorean
63
-
64
- Use this model to mock time on server - more info TBD.
65
-
66
21
  ## Publish package
67
22
 
68
23
  bump the version number in package.json
69
24
  and run
70
25
  ```
26
+ node --run build
71
27
  npm publish
72
28
  ```
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ 'use strict';var express=require('express'),i=require('@autofleet/logger');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var i__default=/*#__PURE__*/_interopDefault(i);var R=["all","get","post","put","delete","patch","options","head"],f=(n,s)=>async(r,o,e)=>{try{await s(r,o,e);}catch(a){let t=a;if(n.error(t.message),t.statusCode&&t.statusCode<500){o.status(400).json({error:t.message,status:"ERROR"});return}e(t);}},u=({logger:n=i__default.default(),...s})=>{let r=express.Router({mergeParams:true,...s});return R.forEach(e=>{let a=r[e].bind(r),t=(...p)=>a(...p.map(o));r[e]=t;}),r;function o(e){return Array.isArray(e)?e.map(o):typeof e=="function"?f(n,e):e}};var m=Object.freeze({ok:"OK",error:"ERROR",fail:"FAIL"});var x=u;exports.AfRouter=u;exports.Router=x;exports.consts=m;//# sourceMappingURL=index.cjs.map
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/router/index.ts","../src/consts/index.ts","../src/index.ts"],"names":["METHODS","AfEntryPoint","logger","func","req","res","next","err","e","AfRouter","Logger","options","myRouter","Router","method","internalMethod","newMethodHandler","args","argMapper","consts"],"mappings":"2LAKaA,IAAAA,CAAAA,CAAU,CACrB,KAAA,CACA,MACA,MACA,CAAA,KAAA,CACA,QACA,CAAA,OAAA,CACA,UACA,MACF,CAAA,CAEMC,CAAe,CAAA,CAACC,EAA+BC,CAAmG,GAAA,MAAOC,CAAKC,CAAAA,CAAAA,CAAKC,IAAS,CAChL,GAAI,CACF,MAAMH,EAAKC,CAAKC,CAAAA,CAAAA,CAAKC,CAAI,EAC3B,CAAA,MAASC,EAAK,CACZ,IAAMC,CAAID,CAAAA,CAAAA,CAEV,GADAL,CAAO,CAAA,KAAA,CAAMM,CAAE,CAAA,OAAO,EAClBA,CAAE,CAAA,UAAA,EAAcA,CAAE,CAAA,UAAA,CAAa,IAAK,CACtCH,CAAAA,CAAI,MAAO,CAAA,GAAG,EAAE,IAAK,CAAA,CAAE,KAAOG,CAAAA,CAAAA,CAAE,QAAS,MAAQ,CAAA,OAAQ,CAAC,CAAA,CAC1D,MACF,CACAF,CAAAA,CAAKE,CAAC,EACR,CACF,CAGaC,CAAAA,CAAAA,CAAW,CAAC,CAAE,MAAA,CAAAP,EAASQ,kBAAO,EAAA,CAAG,GAAGC,CAAQ,IAAiE,CACxH,IAAMC,CAAWC,CAAAA,cAAAA,CAAO,CAAE,WAAa,CAAA,IAAA,CAAM,GAAGF,CAAQ,CAAC,CACzD,CAAA,OAAAX,CAAQ,CAAA,OAAA,CAASc,GAAW,CAC1B,IAAMC,CAAiBH,CAAAA,CAAAA,CAASE,CAAM,CAAE,CAAA,IAAA,CAAKF,CAAQ,CAAA,CAC/CI,EAA2C,CAAIC,GAAAA,CAAAA,GAAyBF,CAAe,CAAA,GAAGE,EAAK,GAAIC,CAAAA,CAAS,CAAa,CAC/HN,CAAAA,CAAAA,CAASE,CAAM,CAAIE,CAAAA,EACrB,CAAC,CAAA,CACMJ,EAEP,SAASM,CAAAA,CAAUD,CAAwB,CAAA,CACzC,OAAI,KAAM,CAAA,OAAA,CAAQA,CAAI,CAAA,CAAUA,EAAK,GAAIC,CAAAA,CAAS,EAC3C,OAAOD,CAAAA,EAAS,WAAahB,CAAaC,CAAAA,CAAAA,CAAQe,CAAe,CAAA,CAAIA,CAC9E,CACF,EC5CaE,IAAAA,CAAAA,CAAS,OAAO,MAAO,CAAA,CAClC,EAAI,CAAA,IAAA,CACJ,MAAO,OACP,CAAA,IAAA,CAAM,MACR,CAAC,MCCYN,CAASJ,CAAAA","file":"index.cjs","sourcesContent":["import {\n Router, type Request, type Response, type NextFunction, type Handler, type RouterOptions, type IRouterMatcher,\n} from 'express';\nimport Logger, { type LoggerInstanceManager } from '@autofleet/logger';\n\nexport const METHODS = [\n 'all',\n 'get',\n 'post',\n 'put',\n 'delete',\n 'patch',\n 'options',\n 'head',\n] as const;\n\nconst AfEntryPoint = (logger: LoggerInstanceManager, func: (req: Request, res: Response, nextFn: NextFunction) => void | PromiseLike<void>): Handler => async (req, res, next) => {\n try {\n await func(req, res, next);\n } catch (err) {\n const e = err as Error & { statusCode?: number };\n logger.error(e.message);\n if (e.statusCode && e.statusCode < 500) {\n res.status(400).json({ error: e.message, status: 'ERROR' });\n return;\n }\n next(e);\n }\n};\n\n/** @returns a monkey-patched express router that will handle async routes (and force a 400 status for codes <500) */\nexport const AfRouter = ({ logger = Logger(), ...options }: RouterOptions & { logger: LoggerInstanceManager }): Router => {\n const myRouter = Router({ mergeParams: true, ...options });\n METHODS.forEach((method) => {\n const internalMethod = myRouter[method].bind(myRouter);\n const newMethodHandler: IRouterMatcher<Router> = (...args: [...unknown[]]) => internalMethod(...args.map(argMapper) as [string]);\n myRouter[method] = newMethodHandler;\n });\n return myRouter;\n\n function argMapper(args: unknown): unknown {\n if (Array.isArray(args)) return args.map(argMapper);\n return typeof args === 'function' ? AfEntryPoint(logger, args as Handler) : args;\n }\n};\n","export const consts = Object.freeze({\n ok: 'OK',\n error: 'ERROR',\n fail: 'FAIL',\n});\n","import { AfRouter } from './router';\n\nexport { AfRouter } from './router';\nexport { consts } from './consts';\n\nexport const Router = AfRouter;\n"]}
@@ -0,0 +1,21 @@
1
+ import * as _autofleet_logger from '@autofleet/logger';
2
+ import { LoggerInstanceManager } from '@autofleet/logger';
3
+ import * as express from 'express';
4
+ import { RouterOptions, Router as Router$1 } from 'express';
5
+
6
+ /** @returns a monkey-patched express router that will handle async routes (and force a 400 status for codes <500) */
7
+ declare const AfRouter: ({ logger, ...options }: RouterOptions & {
8
+ logger: LoggerInstanceManager;
9
+ }) => Router$1;
10
+
11
+ declare const consts: Readonly<{
12
+ ok: "OK";
13
+ error: "ERROR";
14
+ fail: "FAIL";
15
+ }>;
16
+
17
+ declare const Router: ({ logger, ...options }: express.RouterOptions & {
18
+ logger: _autofleet_logger.LoggerInstanceManager;
19
+ }) => express.Router;
20
+
21
+ export { AfRouter, Router, consts };
@@ -0,0 +1,21 @@
1
+ import * as _autofleet_logger from '@autofleet/logger';
2
+ import { LoggerInstanceManager } from '@autofleet/logger';
3
+ import * as express from 'express';
4
+ import { RouterOptions, Router as Router$1 } from 'express';
5
+
6
+ /** @returns a monkey-patched express router that will handle async routes (and force a 400 status for codes <500) */
7
+ declare const AfRouter: ({ logger, ...options }: RouterOptions & {
8
+ logger: LoggerInstanceManager;
9
+ }) => Router$1;
10
+
11
+ declare const consts: Readonly<{
12
+ ok: "OK";
13
+ error: "ERROR";
14
+ fail: "FAIL";
15
+ }>;
16
+
17
+ declare const Router: ({ logger, ...options }: express.RouterOptions & {
18
+ logger: _autofleet_logger.LoggerInstanceManager;
19
+ }) => express.Router;
20
+
21
+ export { AfRouter, Router, consts };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import {Router}from'express';import i from'@autofleet/logger';var R=["all","get","post","put","delete","patch","options","head"],f=(n,s)=>async(r,o,e)=>{try{await s(r,o,e);}catch(a){let t=a;if(n.error(t.message),t.statusCode&&t.statusCode<500){o.status(400).json({error:t.message,status:"ERROR"});return}e(t);}},u=({logger:n=i(),...s})=>{let r=Router({mergeParams:true,...s});return R.forEach(e=>{let a=r[e].bind(r),t=(...p)=>a(...p.map(o));r[e]=t;}),r;function o(e){return Array.isArray(e)?e.map(o):typeof e=="function"?f(n,e):e}};var m=Object.freeze({ok:"OK",error:"ERROR",fail:"FAIL"});var x=u;export{u as AfRouter,x as Router,m as consts};//# sourceMappingURL=index.js.map
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/router/index.ts","../src/consts/index.ts","../src/index.ts"],"names":["METHODS","AfEntryPoint","logger","func","req","res","next","err","e","AfRouter","Logger","options","myRouter","Router","method","internalMethod","newMethodHandler","args","argMapper","consts"],"mappings":"8DAKaA,IAAAA,CAAAA,CAAU,CACrB,KAAA,CACA,MACA,MACA,CAAA,KAAA,CACA,QACA,CAAA,OAAA,CACA,UACA,MACF,CAAA,CAEMC,CAAe,CAAA,CAACC,EAA+BC,CAAmG,GAAA,MAAOC,CAAKC,CAAAA,CAAAA,CAAKC,IAAS,CAChL,GAAI,CACF,MAAMH,EAAKC,CAAKC,CAAAA,CAAAA,CAAKC,CAAI,EAC3B,CAAA,MAASC,EAAK,CACZ,IAAMC,CAAID,CAAAA,CAAAA,CAEV,GADAL,CAAO,CAAA,KAAA,CAAMM,CAAE,CAAA,OAAO,EAClBA,CAAE,CAAA,UAAA,EAAcA,CAAE,CAAA,UAAA,CAAa,IAAK,CACtCH,CAAAA,CAAI,MAAO,CAAA,GAAG,EAAE,IAAK,CAAA,CAAE,KAAOG,CAAAA,CAAAA,CAAE,QAAS,MAAQ,CAAA,OAAQ,CAAC,CAAA,CAC1D,MACF,CACAF,CAAAA,CAAKE,CAAC,EACR,CACF,CAGaC,CAAAA,CAAAA,CAAW,CAAC,CAAE,MAAA,CAAAP,EAASQ,CAAO,EAAA,CAAG,GAAGC,CAAQ,IAAiE,CACxH,IAAMC,CAAWC,CAAAA,MAAAA,CAAO,CAAE,WAAa,CAAA,IAAA,CAAM,GAAGF,CAAQ,CAAC,CACzD,CAAA,OAAAX,CAAQ,CAAA,OAAA,CAASc,GAAW,CAC1B,IAAMC,CAAiBH,CAAAA,CAAAA,CAASE,CAAM,CAAE,CAAA,IAAA,CAAKF,CAAQ,CAAA,CAC/CI,EAA2C,CAAIC,GAAAA,CAAAA,GAAyBF,CAAe,CAAA,GAAGE,EAAK,GAAIC,CAAAA,CAAS,CAAa,CAC/HN,CAAAA,CAAAA,CAASE,CAAM,CAAIE,CAAAA,EACrB,CAAC,CAAA,CACMJ,EAEP,SAASM,CAAAA,CAAUD,CAAwB,CAAA,CACzC,OAAI,KAAM,CAAA,OAAA,CAAQA,CAAI,CAAA,CAAUA,EAAK,GAAIC,CAAAA,CAAS,EAC3C,OAAOD,CAAAA,EAAS,WAAahB,CAAaC,CAAAA,CAAAA,CAAQe,CAAe,CAAA,CAAIA,CAC9E,CACF,EC5CaE,IAAAA,CAAAA,CAAS,OAAO,MAAO,CAAA,CAClC,EAAI,CAAA,IAAA,CACJ,MAAO,OACP,CAAA,IAAA,CAAM,MACR,CAAC,MCCYN,CAASJ,CAAAA","file":"index.js","sourcesContent":["import {\n Router, type Request, type Response, type NextFunction, type Handler, type RouterOptions, type IRouterMatcher,\n} from 'express';\nimport Logger, { type LoggerInstanceManager } from '@autofleet/logger';\n\nexport const METHODS = [\n 'all',\n 'get',\n 'post',\n 'put',\n 'delete',\n 'patch',\n 'options',\n 'head',\n] as const;\n\nconst AfEntryPoint = (logger: LoggerInstanceManager, func: (req: Request, res: Response, nextFn: NextFunction) => void | PromiseLike<void>): Handler => async (req, res, next) => {\n try {\n await func(req, res, next);\n } catch (err) {\n const e = err as Error & { statusCode?: number };\n logger.error(e.message);\n if (e.statusCode && e.statusCode < 500) {\n res.status(400).json({ error: e.message, status: 'ERROR' });\n return;\n }\n next(e);\n }\n};\n\n/** @returns a monkey-patched express router that will handle async routes (and force a 400 status for codes <500) */\nexport const AfRouter = ({ logger = Logger(), ...options }: RouterOptions & { logger: LoggerInstanceManager }): Router => {\n const myRouter = Router({ mergeParams: true, ...options });\n METHODS.forEach((method) => {\n const internalMethod = myRouter[method].bind(myRouter);\n const newMethodHandler: IRouterMatcher<Router> = (...args: [...unknown[]]) => internalMethod(...args.map(argMapper) as [string]);\n myRouter[method] = newMethodHandler;\n });\n return myRouter;\n\n function argMapper(args: unknown): unknown {\n if (Array.isArray(args)) return args.map(argMapper);\n return typeof args === 'function' ? AfEntryPoint(logger, args as Handler) : args;\n }\n};\n","export const consts = Object.freeze({\n ok: 'OK',\n error: 'ERROR',\n fail: 'FAIL',\n});\n","import { AfRouter } from './router';\n\nexport { AfRouter } from './router';\nexport { consts } from './consts';\n\nexport const Router = AfRouter;\n"]}
package/package.json CHANGED
@@ -1,16 +1,34 @@
1
1
  {
2
2
  "name": "@autofleet/node-common",
3
- "version": "2.0.1",
3
+ "version": "4.0.0",
4
4
  "description": "",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ "import": {
11
+ "default": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ },
14
+ "require": {
15
+ "default": "./dist/index.cjs",
16
+ "types": "./dist/index.d.cts"
17
+ }
18
+ },
5
19
  "scripts": {
6
- "coverage": "jest --coverage --forceExit --runInBand",
7
- "test": "jest --forceExit --runInBand",
8
- "test-auto": "jest --watch --runInBand",
9
- "linter": "./node_modules/.bin/eslint ."
20
+ "coverage": "vitest --coverage",
21
+ "test": "vitest",
22
+ "build": "tsup",
23
+ "linter": "eslint ."
10
24
  },
11
25
  "jest": {
12
- "setupTestFrameworkScriptFile": "jest-extended",
13
- "testURL": "http://localhost:8085/"
26
+ "setupFilesAfterEnv": [
27
+ "jest-extended"
28
+ ],
29
+ "testEnvironmentOptions": {
30
+ "url": "http://localhost:8085/"
31
+ }
14
32
  },
15
33
  "repository": {
16
34
  "type": "git",
@@ -22,28 +40,21 @@
22
40
  "url": "https://github.com/Autofleet/node-common/issues"
23
41
  },
24
42
  "homepage": "https://github.com/Autofleet/node-common",
25
- "dependencies": {
26
- "@google-cloud/logging-winston": "^4.1.1",
27
- "axios": "^0.18.0",
28
- "axios-retry": "^3.1.0",
29
- "dotenv": "^5.0.1",
30
- "event-pubsub": "^4.3.0",
31
- "express": "^4.16.2",
32
- "jest": "^22.4.4",
33
- "mock-socket": "^7.1.0",
34
- "node-cache": "^4.2.0",
35
- "node-resque": "^5.4.1",
36
- "portfinder": "^1.0.13",
37
- "qs": "^6.5.2",
38
- "timekeeper": "^2.1.2",
39
- "winston": "^3.0.0",
40
- "ws": "^5.2.1"
41
- },
42
43
  "devDependencies": {
43
- "eslint": "^4.19.1",
44
- "eslint-config-airbnb": "^16.1.0",
45
- "eslint-plugin-import": "^2.11.0",
46
- "jest-extended": "^0.7.1",
47
- "nock": "^10.0.2"
44
+ "@autofleet/logger": "^4.2.1",
45
+ "@types/express": "^4.17.21",
46
+ "@typescript-eslint/eslint-plugin": "^7.18.0",
47
+ "@typescript-eslint/parser": "^7.18.0",
48
+ "@vitest/coverage-v8": "^3.0.6",
49
+ "eslint": "^8.57.0",
50
+ "eslint-config-airbnb-base": "^15.0.0",
51
+ "eslint-plugin-import": "^2.29.1",
52
+ "tsup": "^8.3.6",
53
+ "typescript": "^5.7.3",
54
+ "vitest": "^3.0.6"
55
+ },
56
+ "peerDependencies": {
57
+ "@autofleet/logger": ">=4.2.1",
58
+ "express": "^4.21.2"
48
59
  }
49
60
  }
@@ -0,0 +1,26 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { consts } from '.';
3
+
4
+ describe('consts', () => {
5
+ it('should be an object', () => {
6
+ expect(typeof consts).toBe('object');
7
+ });
8
+
9
+ it('should have ok', () => {
10
+ expect('ok' in consts).toBeTruthy();
11
+ expect(typeof consts.ok).toBe('string');
12
+ expect(consts.ok).toBe('OK');
13
+ });
14
+
15
+ it('should have error', () => {
16
+ expect('error' in consts).toBeTruthy();
17
+ expect(typeof consts.error).toBe('string');
18
+ expect(consts.error).toBe('ERROR');
19
+ });
20
+
21
+ it('should have fail', () => {
22
+ expect('fail' in consts).toBeTruthy();
23
+ expect(typeof consts.fail).toBe('string');
24
+ expect(consts.fail).toBe('FAIL');
25
+ });
26
+ });
@@ -1,5 +1,5 @@
1
- module.exports = {
1
+ export const consts = Object.freeze({
2
2
  ok: 'OK',
3
3
  error: 'ERROR',
4
4
  fail: 'FAIL',
5
- };
5
+ });
@@ -0,0 +1,18 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import * as nodeCommon from '.';
3
+ import { consts } from './consts';
4
+ import { AfRouter } from './router';
5
+
6
+ describe('node-common', () => {
7
+ it('exports consts', () => {
8
+ expect(nodeCommon.consts).toBe(consts);
9
+ });
10
+
11
+ it('exports AfRouter', () => {
12
+ expect(nodeCommon.AfRouter).toBe(AfRouter);
13
+ });
14
+
15
+ it('exports AFRouter aliased as Router', () => {
16
+ expect(nodeCommon.Router).toBe(AfRouter);
17
+ });
18
+ });
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { AfRouter } from './router';
2
+
3
+ export { AfRouter } from './router';
4
+ export { consts } from './consts';
5
+
6
+ export const Router = AfRouter;
@@ -0,0 +1,149 @@
1
+ import Logger from '@autofleet/logger';
2
+ import type { Request, Response, NextFunction } from 'express';
3
+ import {
4
+ describe, expect, it, vi,
5
+ } from 'vitest';
6
+ import { AfRouter, METHODS } from '.';
7
+
8
+ const logger = Logger();
9
+ const noop = () => null;
10
+
11
+ describe('Router', () => {
12
+ it('is a function', () => {
13
+ expect(typeof AfRouter).toBe('function');
14
+ expect(AfRouter.length).toBe(1);
15
+ });
16
+
17
+ it('returns a Router instance', () => {
18
+ const router = AfRouter({ logger });
19
+ expect(typeof router).toBe('function');
20
+ expect(typeof router.use).toBe('function');
21
+ expect(typeof router.get).toBe('function');
22
+ expect(typeof router.post).toBe('function');
23
+ expect(typeof router.put).toBe('function');
24
+ expect(typeof router.delete).toBe('function');
25
+ expect(typeof router.patch).toBe('function');
26
+ expect(typeof router.options).toBe('function');
27
+ expect(typeof router.head).toBe('function');
28
+ });
29
+
30
+ describe.each(METHODS)('Route method %s', { timeout: 1000 }, (method) => {
31
+ it('handles sync routes', async () => {
32
+ const router = AfRouter({ logger });
33
+ router[method]('/test', (_req, res) => {
34
+ res.send('ok');
35
+ });
36
+ const { promise, resolve } = Promise.withResolvers<string>();
37
+ const req = { method, url: '/test', baseUrl: '' };
38
+ const res = { send: (data: string) => resolve(data) };
39
+ // @ts-expect-error handle is not typed for an unclear reason.
40
+ router.handle(req, res, noop);
41
+ expect(await promise).toBe('ok');
42
+ });
43
+
44
+ it('handles async routes', async () => {
45
+ const router = AfRouter({ logger });
46
+ router[method]('/test', async (req, res) => {
47
+ res.send('ok');
48
+ });
49
+ const { promise, resolve } = Promise.withResolvers<string>();
50
+ const req = { method, url: '/test', baseUrl: '' };
51
+ const res = { send: (data: string) => resolve(data) };
52
+ // @ts-expect-error handle is not typed for an unclear reason.
53
+ router.handle(req, res, noop);
54
+ expect(await promise).toBe('ok');
55
+ });
56
+
57
+ it('handles arrays of handlers', async () => {
58
+ const router = AfRouter({ logger });
59
+ router[method]('/test', [
60
+ (_req: Request, _res: Response, next: NextFunction) => next(),
61
+ (_req: Request, res: Response) => res.send('ok'),
62
+ ]);
63
+ const { promise, resolve } = Promise.withResolvers<string>();
64
+ const req = { method, url: '/test', baseUrl: '' };
65
+ const res = { send: (data: string) => resolve(data) };
66
+ // @ts-expect-error handle is not typed for an unclear reason.
67
+ router.handle(req, res, noop);
68
+ expect(await promise).toBe('ok');
69
+ });
70
+
71
+ it('handles errors', async () => {
72
+ const router = AfRouter({ logger });
73
+ const error = new Error('test error');
74
+ router[method]('/test', () => {
75
+ throw error;
76
+ });
77
+ const { promise, resolve } = Promise.withResolvers();
78
+ const next = vi.fn(resolve);
79
+ const req = { method, url: '/test', baseUrl: '' };
80
+ const res = {};
81
+ // @ts-expect-error handle is not typed for an unclear reason.
82
+ router.handle(req, res, next);
83
+ await promise;
84
+ expect(next).toHaveBeenCalledExactlyOnceWith(error);
85
+ });
86
+
87
+ it('handles rejections', async () => {
88
+ const router = AfRouter({ logger });
89
+ const error = new Error('test error');
90
+ router[method]('/test', () => Promise.reject(error));
91
+ const { promise, resolve } = Promise.withResolvers();
92
+ const next = vi.fn(resolve);
93
+ const req = { method, url: '/test', baseUrl: '' };
94
+ const res = {};
95
+ // @ts-expect-error handle is not typed for an unclear reason.
96
+ router.handle(req, res, next);
97
+ await promise;
98
+ // expect(await promise).toBe({ error: 'test error', status: "ERROR" });
99
+ expect(next).toHaveBeenCalledExactlyOnceWith(error);
100
+ });
101
+
102
+ it('forces errors with status code 400 if not 500', async () => {
103
+ const router = AfRouter({ logger });
104
+ const error = Object.assign(new Error('test error'), { statusCode: 418 });
105
+ router[method]('/test', () => Promise.reject(error));
106
+ const { promise, resolve } = Promise.withResolvers();
107
+ const status = vi.fn(() => ({ json: resolve }));
108
+ const req = { method, url: '/test', baseUrl: '' };
109
+ const res = { status };
110
+ // @ts-expect-error handle is not typed for an unclear reason.
111
+ router.handle(req, res, noop);
112
+ await promise;
113
+ expect(await promise).toEqual({ error: 'test error', status: 'ERROR' });
114
+ expect(status).toHaveBeenCalledExactlyOnceWith(400);
115
+ });
116
+
117
+ it('keeps original errors when status code is 500', async () => {
118
+ const router = AfRouter({ logger });
119
+ const error = Object.assign(new Error('test error'), { statusCode: 500 });
120
+ router[method]('/test', () => Promise.reject(error));
121
+ const { promise, resolve } = Promise.withResolvers();
122
+ const next = vi.fn(resolve);
123
+ const req = { method, url: '/test', baseUrl: '' };
124
+ const res = {};
125
+ // @ts-expect-error handle is not typed for an unclear reason.
126
+ router.handle(req, res, next);
127
+ await promise;
128
+ expect(next).toHaveBeenCalledExactlyOnceWith(error);
129
+ });
130
+
131
+ it('defaults to mergeParams true', async () => {
132
+ const router = AfRouter({ mergeParams: false, logger });
133
+ const childRouter = AfRouter({ logger });
134
+ router.use('/:parentParam', childRouter);
135
+ childRouter[method]<{ parentParam: string; param: string; }>('/:param', (req, res) => res.send({ param: req.params.param, parentParam: req.params.parentParam }));
136
+
137
+ const { promise, resolve } = Promise.withResolvers<string>();
138
+ const req = { method, url: '/foo/bar', baseUrl: '' };
139
+ const res = { send: (data: string) => resolve(data) };
140
+ // @ts-expect-error handle is not typed for an unclear reason.
141
+ router.handle(req, res, noop);
142
+ expect(await promise).toEqual({ parentParam: 'foo', param: 'bar' });
143
+ // @ts-expect-error mergeParams is a private field
144
+ expect(router.mergeParams).toBeFalsy();
145
+ // @ts-expect-error mergeParams is a private field
146
+ expect(childRouter.mergeParams).toBeTruthy();
147
+ });
148
+ });
149
+ });
@@ -0,0 +1,45 @@
1
+ import {
2
+ Router, type Request, type Response, type NextFunction, type Handler, type RouterOptions, type IRouterMatcher,
3
+ } from 'express';
4
+ import Logger, { type LoggerInstanceManager } from '@autofleet/logger';
5
+
6
+ export const METHODS = [
7
+ 'all',
8
+ 'get',
9
+ 'post',
10
+ 'put',
11
+ 'delete',
12
+ 'patch',
13
+ 'options',
14
+ 'head',
15
+ ] as const;
16
+
17
+ const AfEntryPoint = (logger: LoggerInstanceManager, func: (req: Request, res: Response, nextFn: NextFunction) => void | PromiseLike<void>): Handler => async (req, res, next) => {
18
+ try {
19
+ await func(req, res, next);
20
+ } catch (err) {
21
+ const e = err as Error & { statusCode?: number };
22
+ logger.error(e.message);
23
+ if (e.statusCode && e.statusCode < 500) {
24
+ res.status(400).json({ error: e.message, status: 'ERROR' });
25
+ return;
26
+ }
27
+ next(e);
28
+ }
29
+ };
30
+
31
+ /** @returns a monkey-patched express router that will handle async routes (and force a 400 status for codes <500) */
32
+ export const AfRouter = ({ logger = Logger(), ...options }: RouterOptions & { logger: LoggerInstanceManager }): Router => {
33
+ const myRouter = Router({ mergeParams: true, ...options });
34
+ METHODS.forEach((method) => {
35
+ const internalMethod = myRouter[method].bind(myRouter);
36
+ const newMethodHandler: IRouterMatcher<Router> = (...args: [...unknown[]]) => internalMethod(...args.map(argMapper) as [string]);
37
+ myRouter[method] = newMethodHandler;
38
+ });
39
+ return myRouter;
40
+
41
+ function argMapper(args: unknown): unknown {
42
+ if (Array.isArray(args)) return args.map(argMapper);
43
+ return typeof args === 'function' ? AfEntryPoint(logger, args as Handler) : args;
44
+ }
45
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "declaration": true,
6
+ "outDir": "./dist",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "lib": [
11
+ "ESNext"
12
+ ]
13
+ },
14
+ "include": ["src", "tsup.config.ts", "vitest.config.ts"],
15
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig((options) => ({
4
+ entry: ['src/index.ts'],
5
+ target: 'node18.0',
6
+ dts: true,
7
+ format: ['esm', 'cjs'],
8
+ clean: !options.watch,
9
+ minify: !options.watch,
10
+ removeNodeProtocol: false,
11
+ treeshake: true,
12
+ sourcemap: true,
13
+ }));
@@ -0,0 +1,14 @@
1
+ // eslint-disable-next-line import/no-unresolved
2
+ import { defineConfig } from 'vitest/config';
3
+
4
+ export default defineConfig({
5
+ test: {
6
+ coverage: {
7
+ provider: 'v8',
8
+ reporter: ['text'],
9
+ thresholds: {
10
+ lines: 80,
11
+ },
12
+ },
13
+ },
14
+ });
package/.jest.config.js DELETED
@@ -1,8 +0,0 @@
1
- module.exports = {
2
- testEnvironment: 'node',
3
- coverageThreshold: {
4
- global: {
5
- lines: 80,
6
- },
7
- },
8
- };
@@ -1,52 +0,0 @@
1
- const tk = require('timekeeper');
2
- const WebSocket = require('ws');
3
- const Logger = require('../logger');
4
-
5
- const logger = Logger();
6
-
7
- const createWs = (host, resolve) => {
8
- const ws = new WebSocket(`ws://${host}`);
9
-
10
- ws.on('open', () => {
11
- ws.send(JSON.stringify({ type: 'ping' }));
12
- if (resolve) {
13
- resolve(true);
14
- }
15
- });
16
-
17
- ws.on('error', (error) => {
18
- logger.error('ERROR', error);
19
- });
20
-
21
- ws.on('message', (data) => {
22
- const jsonMsg = JSON.parse(data);
23
-
24
- if (jsonMsg.type === 'freeze') {
25
- logger.info('I know you just sent me back to the future, but I\'m back.I\'m back from the future.', { goingBackTo: jsonMsg.time });
26
- tk.freeze(new Date(jsonMsg.time));
27
- } else if (jsonMsg.type === 'travel') {
28
- tk.travel(jsonMsg.time);
29
- }
30
- });
31
-
32
- ws.on('close', () => {
33
- setTimeout(() => {
34
- if (process.env.TIME_TRAVEL_MS_SERVICE_HOST === host) {
35
- createWs(host, null);
36
- }
37
- }, 500);
38
- });
39
- };
40
-
41
- module.exports = () => new Promise((resolve) => {
42
- const { TIME_TRAVEL_MS_SERVICE_HOST, SHOULD_TIME_TRAVEL } = process.env;
43
- if (TIME_TRAVEL_MS_SERVICE_HOST && SHOULD_TIME_TRAVEL) {
44
- logger.info('If my calculations are correct, when this baby hits 88 miles per hour... you\'re gonna see some serious shit.', {
45
- TIME_TRAVEL_MS_SERVICE_HOST,
46
- });
47
-
48
- createWs(TIME_TRAVEL_MS_SERVICE_HOST, resolve);
49
- } else {
50
- resolve(false);
51
- }
52
- });