@openapi-typescript-infra/service 4.19.1 → 4.20.1
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/build/config/index.js +1 -25
- package/build/config/index.js.map +1 -1
- package/build/development/port-finder.d.ts +1 -1
- package/build/development/port-finder.js +27 -2
- package/build/development/port-finder.js.map +1 -1
- package/build/express-app/internal-server.js +1 -1
- package/build/express-app/internal-server.js.map +1 -1
- package/build/telemetry/requestLogger.js +15 -6
- package/build/telemetry/requestLogger.js.map +1 -1
- package/build/tsconfig.build.tsbuildinfo +1 -1
- package/config/test.json +2 -1
- package/package.json +2 -2
- package/src/config/index.ts +2 -27
- package/src/development/port-finder.ts +29 -1
- package/src/express-app/internal-server.ts +2 -2
- package/src/telemetry/requestLogger.ts +15 -6
package/config/test.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openapi-typescript-infra/service",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.20.1",
|
|
4
4
|
"description": "An opinionated framework for building configuration driven services - web, api, or ob. Uses OpenAPI, pino logging, express, confit, Typescript and vitest.",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"glob": "^8.1.0",
|
|
86
86
|
"lodash": "^4.17.21",
|
|
87
87
|
"minimist": "^1.2.8",
|
|
88
|
-
"pino": "^
|
|
88
|
+
"pino": "^9.0.0",
|
|
89
89
|
"read-pkg-up": "^7.0.1",
|
|
90
90
|
"request-ip": "^3.3.0"
|
|
91
91
|
},
|
package/src/config/index.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
-
import net from 'net';
|
|
3
2
|
import path from 'path';
|
|
4
3
|
|
|
5
4
|
import {
|
|
@@ -10,8 +9,7 @@ import {
|
|
|
10
9
|
confit,
|
|
11
10
|
} from '@sesamecare-oss/confit';
|
|
12
11
|
|
|
13
|
-
import {
|
|
14
|
-
import { isTest } from '../env';
|
|
12
|
+
import { getAvailablePort } from '../development/port-finder';
|
|
15
13
|
|
|
16
14
|
import type { ConfigurationSchema } from './schema';
|
|
17
15
|
|
|
@@ -57,28 +55,6 @@ async function addDefaultConfiguration<Config extends ConfigurationSchema = Conf
|
|
|
57
55
|
}
|
|
58
56
|
}
|
|
59
57
|
|
|
60
|
-
async function getEphemeralPort(): Promise<number> {
|
|
61
|
-
return new Promise((resolve, reject) => {
|
|
62
|
-
const server = net.createServer();
|
|
63
|
-
|
|
64
|
-
server.listen(0, () => {
|
|
65
|
-
const address = server.address();
|
|
66
|
-
if (typeof address === 'string' || !address) {
|
|
67
|
-
reject(new Error('Invalid address'));
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
const port = address.port; // Retrieve the ephemeral port
|
|
71
|
-
server.close((err) => {
|
|
72
|
-
if (err) {
|
|
73
|
-
reject(err);
|
|
74
|
-
} else {
|
|
75
|
-
resolve(port);
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
|
|
82
58
|
export interface ServiceConfigurationSpec {
|
|
83
59
|
// The LAST configuration is the most "specific" - if a configuration value
|
|
84
60
|
// exists in all directories, the last one wins
|
|
@@ -117,8 +93,7 @@ export async function loadConfiguration<Config extends ConfigurationSchema>({
|
|
|
117
93
|
// configured to auto-select
|
|
118
94
|
const serverConfig = loaded.get().server;
|
|
119
95
|
if (serverConfig.port === 0) {
|
|
120
|
-
const
|
|
121
|
-
const port = await portPromise;
|
|
96
|
+
const port = await getAvailablePort(8001);
|
|
122
97
|
const store = loaded.get();
|
|
123
98
|
store.server = store.server || {};
|
|
124
99
|
store.server.port = port;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import net from 'net';
|
|
2
2
|
|
|
3
|
+
import { isTest } from '../env';
|
|
4
|
+
|
|
3
5
|
// Inspired by https://github.com/kessler/find-port/blob/master/lib/findPort.js
|
|
4
6
|
async function isAvailable(port: number) {
|
|
5
7
|
return new Promise((accept, reject) => {
|
|
@@ -29,7 +31,7 @@ async function isAvailable(port: number) {
|
|
|
29
31
|
});
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
async function findPort(start: number) {
|
|
33
35
|
for (let p = start; p < start + 1000; p += 1) {
|
|
34
36
|
// eslint-disable-next-line no-await-in-loop
|
|
35
37
|
if (await isAvailable(p)) {
|
|
@@ -38,3 +40,29 @@ export async function findPort(start: number) {
|
|
|
38
40
|
}
|
|
39
41
|
return 0;
|
|
40
42
|
}
|
|
43
|
+
|
|
44
|
+
async function getEphemeralPort(): Promise<number> {
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
const server = net.createServer();
|
|
47
|
+
|
|
48
|
+
server.listen(0, () => {
|
|
49
|
+
const address = server.address();
|
|
50
|
+
if (typeof address === 'string' || !address) {
|
|
51
|
+
reject(new Error('Invalid address'));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const port = address.port; // Retrieve the ephemeral port
|
|
55
|
+
server.close((err) => {
|
|
56
|
+
if (err) {
|
|
57
|
+
reject(err);
|
|
58
|
+
} else {
|
|
59
|
+
resolve(port);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function getAvailablePort(basePort: number): Promise<number> {
|
|
67
|
+
return (isTest() || process.env.TEST_RUNNER) ? getEphemeralPort() : findPort(basePort);
|
|
68
|
+
}
|
|
@@ -2,7 +2,7 @@ import express from 'express';
|
|
|
2
2
|
import type { Application } from 'express-serve-static-core';
|
|
3
3
|
|
|
4
4
|
import { AnyServiceLocals, InternalLocals, ServiceExpress, ServiceLocals } from '../types';
|
|
5
|
-
import {
|
|
5
|
+
import { getAvailablePort } from '../development/port-finder';
|
|
6
6
|
import { ConfigurationSchema } from '../config/schema';
|
|
7
7
|
|
|
8
8
|
export async function startInternalApp<
|
|
@@ -11,7 +11,7 @@ export async function startInternalApp<
|
|
|
11
11
|
const app = express() as unknown as Application<InternalLocals<SLocals>>;
|
|
12
12
|
app.locals.mainApp = mainApp;
|
|
13
13
|
|
|
14
|
-
const finalPort = port === 0 ? await
|
|
14
|
+
const finalPort = port === 0 ? await getAvailablePort(3001) : port;
|
|
15
15
|
|
|
16
16
|
app.get('/health', async (req, res) => {
|
|
17
17
|
if (mainApp.locals.service?.healthy) {
|
|
@@ -29,6 +29,7 @@ interface WithIdentifiedSession {
|
|
|
29
29
|
|
|
30
30
|
interface ErrorWithStatus extends Error {
|
|
31
31
|
status?: number;
|
|
32
|
+
expected_error?: boolean;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
function getBasicInfo(req: Request): [string, Record<string, string | number>] {
|
|
@@ -84,11 +85,15 @@ function finishLog<SLocals extends AnyServiceLocals = ServiceLocals<Configuratio
|
|
|
84
85
|
endLog.u = res.locals.user.id;
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
let unexpectedError = false;
|
|
87
89
|
if (error) {
|
|
88
90
|
endLog.e = error.message;
|
|
89
91
|
if (!(error instanceof ServiceError) || error.log_stack) {
|
|
90
92
|
endLog.st = error.stack;
|
|
91
93
|
}
|
|
94
|
+
if (!(error as ErrorWithStatus).expected_error) {
|
|
95
|
+
unexpectedError = true;
|
|
96
|
+
}
|
|
92
97
|
}
|
|
93
98
|
|
|
94
99
|
if (prefs.logRequests) {
|
|
@@ -110,7 +115,11 @@ function finishLog<SLocals extends AnyServiceLocals = ServiceLocals<Configuratio
|
|
|
110
115
|
}
|
|
111
116
|
|
|
112
117
|
const msg = service.getLogFields?.(req as RequestWithApp<SLocals>, endLog) || url;
|
|
113
|
-
|
|
118
|
+
if (unexpectedError) {
|
|
119
|
+
logger.error(endLog, msg);
|
|
120
|
+
} else {
|
|
121
|
+
logger.info(endLog, msg);
|
|
122
|
+
}
|
|
114
123
|
}
|
|
115
124
|
|
|
116
125
|
export function loggerMiddleware<
|
|
@@ -122,7 +131,7 @@ export function loggerMiddleware<
|
|
|
122
131
|
): RequestHandler {
|
|
123
132
|
const nonProd = getNodeEnv() !== 'production';
|
|
124
133
|
const { logger, service } = app.locals;
|
|
125
|
-
return function
|
|
134
|
+
return function serviceLogger(req, res, next) {
|
|
126
135
|
const logResponse =
|
|
127
136
|
config?.logResponseBody || (nonProd && req.headers['x-log']?.includes('res'));
|
|
128
137
|
const logRequest = config?.logRequestBody || (nonProd && req.headers['x-log']?.includes('req'));
|
|
@@ -176,7 +185,7 @@ export function loggerMiddleware<
|
|
|
176
185
|
export function errorHandlerMiddleware<
|
|
177
186
|
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
178
187
|
>(app: ServiceExpress<SLocals>, histogram: Histogram, unnest?: boolean, returnError?: boolean) {
|
|
179
|
-
const
|
|
188
|
+
const svcErrorHandler: ErrorRequestHandler = (error, req, res, next) => {
|
|
180
189
|
let loggable: Partial<ServiceError> = error;
|
|
181
190
|
const body = error.response?.body || error.body;
|
|
182
191
|
if (unnest && body?.domain && body?.code && body?.message) {
|
|
@@ -204,11 +213,11 @@ export function errorHandlerMiddleware<
|
|
|
204
213
|
next(error);
|
|
205
214
|
}
|
|
206
215
|
};
|
|
207
|
-
return
|
|
216
|
+
return svcErrorHandler;
|
|
208
217
|
}
|
|
209
218
|
|
|
210
219
|
export function notFoundMiddleware() {
|
|
211
|
-
const
|
|
220
|
+
const serviceNotFoundHandler: ServiceHandler = (req, res, next) => {
|
|
212
221
|
const error = new ServiceError(req.app, `Cannot ${req.method} ${req.path}`, {
|
|
213
222
|
status: 404,
|
|
214
223
|
code: 'NotFound',
|
|
@@ -216,5 +225,5 @@ export function notFoundMiddleware() {
|
|
|
216
225
|
});
|
|
217
226
|
next(error);
|
|
218
227
|
};
|
|
219
|
-
return
|
|
228
|
+
return serviceNotFoundHandler as RequestHandler;
|
|
220
229
|
}
|