@openapi-typescript-infra/service 5.11.4 → 5.13.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/build/bootstrap.js.map +1 -1
- package/build/development/port-finder.js +1 -1
- package/build/development/port-finder.js.map +1 -1
- package/build/development/repl.js +12 -0
- package/build/development/repl.js.map +1 -1
- package/build/express-app/app.js +5 -2
- package/build/express-app/app.js.map +1 -1
- package/build/openapi.js +0 -1
- package/build/openapi.js.map +1 -1
- package/build/telemetry/hook-modules.js +1 -11
- package/build/telemetry/hook-modules.js.map +1 -1
- package/build/telemetry/index.js +1 -1
- package/build/telemetry/index.js.map +1 -1
- package/build/telemetry/instrumentations.js +1 -1
- package/build/telemetry/instrumentations.js.map +1 -1
- package/build/tsconfig.build.tsbuildinfo +1 -1
- package/build/types.d.ts +1 -0
- package/package.json +14 -13
- package/src/bootstrap.ts +6 -1
- package/src/development/port-finder.ts +1 -1
- package/src/development/repl.ts +16 -6
- package/src/express-app/app.ts +34 -21
- package/src/openapi.ts +10 -10
- package/src/telemetry/hook-modules.ts +1 -11
- package/src/telemetry/index.ts +1 -1
- package/src/telemetry/instrumentations.ts +4 -1
- package/src/types.ts +2 -0
package/build/types.d.ts
CHANGED
|
@@ -42,6 +42,7 @@ export interface Service<SLocals extends AnyServiceLocals = ServiceLocals<Config
|
|
|
42
42
|
configure?: (startOptions: ServiceStartOptions<SLocals, RLocals>, options: ServiceOptions) => ServiceOptions;
|
|
43
43
|
attach?: (app: ServiceExpress<SLocals>) => void | Promise<void>;
|
|
44
44
|
attachServer?: (app: ServiceExpress<SLocals>, server: Server) => void | Promise<void>;
|
|
45
|
+
onListening?: (app: ServiceExpress<SLocals>, port: number) => void | Promise<void>;
|
|
45
46
|
start(app: ServiceExpress<SLocals>): void | Promise<void>;
|
|
46
47
|
stop?: (app: ServiceExpress<SLocals>) => void | Promise<void>;
|
|
47
48
|
healthy?: (app: ServiceExpress<SLocals>) => boolean | Promise<boolean>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openapi-typescript-infra/service",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.13.0",
|
|
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
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"dependencies": {
|
|
71
71
|
"@godaddy/terminus": "^4.12.1",
|
|
72
72
|
"@opentelemetry/api": "^1.9.0",
|
|
73
|
-
"@opentelemetry/auto-instrumentations-node": "^0.60.
|
|
73
|
+
"@opentelemetry/auto-instrumentations-node": "^0.60.1",
|
|
74
74
|
"@opentelemetry/exporter-prometheus": "^0.202.0",
|
|
75
75
|
"@opentelemetry/instrumentation-dns": "^0.46.0",
|
|
76
76
|
"@opentelemetry/instrumentation-express": "^0.51.0",
|
|
@@ -78,10 +78,10 @@
|
|
|
78
78
|
"@opentelemetry/instrumentation-graphql": "^0.50.0",
|
|
79
79
|
"@opentelemetry/instrumentation-http": "^0.202.0",
|
|
80
80
|
"@opentelemetry/instrumentation-ioredis": "^0.50.0",
|
|
81
|
-
"@opentelemetry/instrumentation-net": "^0.46.
|
|
81
|
+
"@opentelemetry/instrumentation-net": "^0.46.1",
|
|
82
82
|
"@opentelemetry/instrumentation-pg": "^0.54.0",
|
|
83
83
|
"@opentelemetry/instrumentation-pino": "^0.49.0",
|
|
84
|
-
"@opentelemetry/instrumentation-undici": "^0.13.
|
|
84
|
+
"@opentelemetry/instrumentation-undici": "^0.13.1",
|
|
85
85
|
"@opentelemetry/resource-detector-container": "^0.7.2",
|
|
86
86
|
"@opentelemetry/resource-detector-gcp": "^0.36.0",
|
|
87
87
|
"@opentelemetry/sdk-node": "^0.202.0",
|
|
@@ -93,9 +93,9 @@
|
|
|
93
93
|
"cookie-parser": "^1.4.7",
|
|
94
94
|
"dotenv": "^16.5.0",
|
|
95
95
|
"express": "^5.1.0",
|
|
96
|
-
"express-openapi-validator": "^5.5.
|
|
97
|
-
"glob": "^11.0.
|
|
98
|
-
"import-in-the-middle": "^1.14.
|
|
96
|
+
"express-openapi-validator": "^5.5.7",
|
|
97
|
+
"glob": "^11.0.3",
|
|
98
|
+
"import-in-the-middle": "^1.14.2",
|
|
99
99
|
"minimist": "^1.2.8",
|
|
100
100
|
"moderndash": "^4.0.0",
|
|
101
101
|
"opentelemetry-resource-detector-sync-api": "^0.30.0",
|
|
@@ -111,10 +111,10 @@
|
|
|
111
111
|
"@semantic-release/exec": "^7.1.0",
|
|
112
112
|
"@semantic-release/github": "^11.0.3",
|
|
113
113
|
"@semantic-release/release-notes-generator": "^14.0.3",
|
|
114
|
-
"@types/cookie-parser": "^1.4.
|
|
115
|
-
"@types/express": "^5.0.
|
|
114
|
+
"@types/cookie-parser": "^1.4.9",
|
|
115
|
+
"@types/express": "^5.0.3",
|
|
116
116
|
"@types/minimist": "^1.2.5",
|
|
117
|
-
"@types/node": "^22.15.
|
|
117
|
+
"@types/node": "^22.15.31",
|
|
118
118
|
"@types/request-ip": "^0.0.41",
|
|
119
119
|
"@types/supertest": "^6.0.3",
|
|
120
120
|
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
@@ -122,15 +122,16 @@
|
|
|
122
122
|
"coconfig": "^1.6.2",
|
|
123
123
|
"eslint": "^8.57.1",
|
|
124
124
|
"eslint-config-prettier": "^9.1.0",
|
|
125
|
-
"eslint-import-resolver-typescript": "^4.4.
|
|
125
|
+
"eslint-import-resolver-typescript": "^4.4.3",
|
|
126
126
|
"eslint-plugin-import": "^2.31.0",
|
|
127
127
|
"pino-pretty": "^13.0.0",
|
|
128
128
|
"pinst": "^3.0.0",
|
|
129
|
+
"prettier": "^3.5.3",
|
|
129
130
|
"supertest": "^7.1.1",
|
|
130
131
|
"tsconfig-paths": "^4.2.0",
|
|
131
|
-
"tsx": "^4.
|
|
132
|
+
"tsx": "^4.20.3",
|
|
132
133
|
"typescript": "^5.8.3",
|
|
133
|
-
"vitest": "^3.2.
|
|
134
|
+
"vitest": "^3.2.3"
|
|
134
135
|
},
|
|
135
136
|
"resolutions": {
|
|
136
137
|
"qs": "^6.11.0"
|
package/src/bootstrap.ts
CHANGED
|
@@ -5,7 +5,12 @@ import { config } from 'dotenv';
|
|
|
5
5
|
import { readPackageUp } from 'read-package-up';
|
|
6
6
|
import type { NormalizedPackageJson } from 'read-package-up';
|
|
7
7
|
|
|
8
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
AnyServiceLocals,
|
|
10
|
+
RequestLocals,
|
|
11
|
+
ServiceLocals,
|
|
12
|
+
ServiceStartOptions,
|
|
13
|
+
} from './types.js';
|
|
9
14
|
import { isDev } from './env.js';
|
|
10
15
|
import { startWithTelemetry } from './telemetry/index.js';
|
|
11
16
|
import { ConfigurationSchema } from './config/schema.js';
|
|
@@ -64,5 +64,5 @@ async function getEphemeralPort(): Promise<number> {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
export async function getAvailablePort(basePort: number): Promise<number> {
|
|
67
|
-
return
|
|
67
|
+
return isTest() || process.env.TEST_RUNNER ? getEphemeralPort() : findPort(basePort);
|
|
68
68
|
}
|
package/src/development/repl.ts
CHANGED
|
@@ -19,11 +19,23 @@ export function serviceRepl<SLocals extends AnyServiceLocals = ServiceLocals<Con
|
|
|
19
19
|
codepath: string | undefined,
|
|
20
20
|
onExit: () => void,
|
|
21
21
|
) {
|
|
22
|
+
class FakeReq {
|
|
23
|
+
locals: { app: ServiceExpress<SLocals> } = { app };
|
|
24
|
+
headers: Record<string, string | string[] | undefined> = {};
|
|
25
|
+
query = new URLSearchParams();
|
|
26
|
+
body: unknown = {};
|
|
27
|
+
|
|
28
|
+
constructor(public path: string) {
|
|
29
|
+
this.locals.app = app;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
22
33
|
const rl = repl.start({
|
|
23
34
|
prompt: '> ',
|
|
24
35
|
});
|
|
25
36
|
Object.assign(rl.context, app.locals, {
|
|
26
37
|
app,
|
|
38
|
+
req: new FakeReq('/'),
|
|
27
39
|
dump(o: unknown) {
|
|
28
40
|
// eslint-disable-next-line no-console
|
|
29
41
|
console.log(JSON.stringify(o, null, '\t'));
|
|
@@ -47,11 +59,9 @@ export function serviceRepl<SLocals extends AnyServiceLocals = ServiceLocals<Con
|
|
|
47
59
|
rl.on('exit', onExit);
|
|
48
60
|
}
|
|
49
61
|
|
|
50
|
-
async function loadReplFunctions<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
rl: REPLServer,
|
|
54
|
-
) {
|
|
62
|
+
async function loadReplFunctions<
|
|
63
|
+
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
64
|
+
>(app: ServiceExpress<SLocals>, codepath: string | undefined, rl: REPLServer) {
|
|
55
65
|
if (!codepath) {
|
|
56
66
|
return;
|
|
57
67
|
}
|
|
@@ -104,7 +114,7 @@ type ReplAny = any;
|
|
|
104
114
|
*/
|
|
105
115
|
export function repl$<
|
|
106
116
|
S extends ServiceExpress<ReplAny>,
|
|
107
|
-
T extends (app: S, ...args: ReplAny[]) => ReplAny
|
|
117
|
+
T extends (app: S, ...args: ReplAny[]) => ReplAny,
|
|
108
118
|
>(fn: T, name?: string) {
|
|
109
119
|
const functionName = name || fn.name;
|
|
110
120
|
if (!functionName) {
|
package/src/express-app/app.ts
CHANGED
|
@@ -61,7 +61,7 @@ export async function startApp<
|
|
|
61
61
|
Object.assign(mergeObject, {
|
|
62
62
|
trace_id: ctx.traceId,
|
|
63
63
|
span_id: ctx.spanId,
|
|
64
|
-
trace_flags: ctx.traceFlags
|
|
64
|
+
trace_flags: ctx.traceFlags,
|
|
65
65
|
});
|
|
66
66
|
}
|
|
67
67
|
}
|
|
@@ -70,28 +70,28 @@ export async function startApp<
|
|
|
70
70
|
|
|
71
71
|
const logger = shouldPrettyPrint
|
|
72
72
|
? pino(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
{
|
|
74
|
+
transport: {
|
|
75
|
+
target: 'pino-pretty',
|
|
76
|
+
options: {
|
|
77
|
+
colorize: true,
|
|
78
|
+
},
|
|
78
79
|
},
|
|
80
|
+
mixin: poorMansOtlp,
|
|
79
81
|
},
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
destination,
|
|
83
|
-
)
|
|
82
|
+
destination,
|
|
83
|
+
)
|
|
84
84
|
: pino(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
{
|
|
86
|
+
formatters: {
|
|
87
|
+
level(label) {
|
|
88
|
+
return { level: label };
|
|
89
|
+
},
|
|
89
90
|
},
|
|
91
|
+
mixin: poorMansOtlp,
|
|
90
92
|
},
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
destination,
|
|
94
|
-
);
|
|
93
|
+
destination,
|
|
94
|
+
);
|
|
95
95
|
|
|
96
96
|
const serviceImpl = service();
|
|
97
97
|
assert(serviceImpl?.start, 'Service function did not return a conforming object');
|
|
@@ -192,7 +192,11 @@ export async function startApp<
|
|
|
192
192
|
);
|
|
193
193
|
}
|
|
194
194
|
if (routing?.bodyParsers?.form) {
|
|
195
|
-
app.use(
|
|
195
|
+
app.use(
|
|
196
|
+
express.urlencoded(
|
|
197
|
+
typeof routing.bodyParsers.form === 'object' ? routing.bodyParsers.form : {},
|
|
198
|
+
),
|
|
199
|
+
);
|
|
196
200
|
}
|
|
197
201
|
|
|
198
202
|
if (serviceImpl.authorize) {
|
|
@@ -259,7 +263,13 @@ export async function startApp<
|
|
|
259
263
|
);
|
|
260
264
|
}
|
|
261
265
|
if (routing?.openapi) {
|
|
262
|
-
const openApiMiddleware = await openApi(
|
|
266
|
+
const openApiMiddleware = await openApi(
|
|
267
|
+
app,
|
|
268
|
+
rootDirectory,
|
|
269
|
+
codepath,
|
|
270
|
+
codePattern,
|
|
271
|
+
options.openApiOptions,
|
|
272
|
+
);
|
|
263
273
|
app.use(openApiMiddleware);
|
|
264
274
|
}
|
|
265
275
|
|
|
@@ -352,7 +362,9 @@ export async function listen<SLocals extends AnyServiceLocals = ServiceLocals<Co
|
|
|
352
362
|
onShutdown() {
|
|
353
363
|
return Promise.resolve()
|
|
354
364
|
.then(() => service.stop?.(app))
|
|
355
|
-
.then(() => {
|
|
365
|
+
.then(() => {
|
|
366
|
+
logger.info('Service stop complete');
|
|
367
|
+
})
|
|
356
368
|
.then(shutdownHandler || (() => Promise.resolve()))
|
|
357
369
|
.then(() => logger.info('Graceful shutdown complete'))
|
|
358
370
|
.catch((error) => logger.error(error, 'Error terminating tracing'))
|
|
@@ -417,6 +429,7 @@ export async function listen<SLocals extends AnyServiceLocals = ServiceLocals<Co
|
|
|
417
429
|
});
|
|
418
430
|
|
|
419
431
|
await listenPromise;
|
|
432
|
+
await service.onListening?.(app, port);
|
|
420
433
|
return server;
|
|
421
434
|
}
|
|
422
435
|
|
package/src/openapi.ts
CHANGED
|
@@ -76,7 +76,7 @@ export async function openApi<
|
|
|
76
76
|
|
|
77
77
|
try {
|
|
78
78
|
app.locals.openApiSpecification = await new OpenAPIFramework({ apiDoc: apiSpec })
|
|
79
|
-
.initialize({ visitApi() {
|
|
79
|
+
.initialize({ visitApi() {} })
|
|
80
80
|
.then((docs) => docs.apiDoc)
|
|
81
81
|
.catch((error) => {
|
|
82
82
|
app.locals.logger.error(error, 'Failed to parse and load OpenAPI spec');
|
|
@@ -130,15 +130,15 @@ export async function openApi<
|
|
|
130
130
|
// by setting validateResponses to false in the config.
|
|
131
131
|
...(getNodeEnv() === 'test'
|
|
132
132
|
? {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
133
|
+
validateResponses: {
|
|
134
|
+
onError(error: Error, body: unknown, req: Request) {
|
|
135
|
+
console.log('Response body fails validation: ', error);
|
|
136
|
+
console.log('Emitted from:', req.originalUrl);
|
|
137
|
+
console.debug(body);
|
|
138
|
+
throw error;
|
|
139
|
+
},
|
|
139
140
|
},
|
|
140
|
-
}
|
|
141
|
-
}
|
|
141
|
+
}
|
|
142
142
|
: {}),
|
|
143
143
|
...(typeof routing.openapi === 'object' ? routing.openapi : {}),
|
|
144
144
|
...openApiOptions,
|
|
@@ -148,6 +148,6 @@ export async function openApi<
|
|
|
148
148
|
} finally {
|
|
149
149
|
if (_window) {
|
|
150
150
|
(global as { window: unknown }).window = _window;
|
|
151
|
-
}
|
|
151
|
+
}
|
|
152
152
|
}
|
|
153
153
|
}
|
|
@@ -3,16 +3,6 @@ import module from 'node:module';
|
|
|
3
3
|
module.register('import-in-the-middle/hook.mjs', import.meta.url, {
|
|
4
4
|
parentURL: import.meta.url,
|
|
5
5
|
data: {
|
|
6
|
-
include: [
|
|
7
|
-
'express',
|
|
8
|
-
'pino',
|
|
9
|
-
'http',
|
|
10
|
-
'dns',
|
|
11
|
-
'net',
|
|
12
|
-
'pg',
|
|
13
|
-
'ioredis',
|
|
14
|
-
'undici',
|
|
15
|
-
'generic-pool',
|
|
16
|
-
],
|
|
6
|
+
include: ['express', 'pino', 'http', 'dns', 'net', 'pg', 'ioredis', 'undici', 'generic-pool'],
|
|
17
7
|
},
|
|
18
8
|
});
|
package/src/telemetry/index.ts
CHANGED
|
@@ -133,7 +133,7 @@ export async function startWithTelemetry<
|
|
|
133
133
|
await startGlobalTelemetry(options.name);
|
|
134
134
|
|
|
135
135
|
// eslint-disable-next-line import/no-unresolved, @typescript-eslint/no-var-requires
|
|
136
|
-
const { startApp, listen } = await import('../express-app/app.js') as {
|
|
136
|
+
const { startApp, listen } = (await import('../express-app/app.js')) as {
|
|
137
137
|
startApp: StartAppFn<SLocals, RLocals>;
|
|
138
138
|
listen: ListenFn<SLocals>;
|
|
139
139
|
};
|
|
@@ -3,7 +3,10 @@ import { DnsInstrumentation } from '@opentelemetry/instrumentation-dns';
|
|
|
3
3
|
import { ExpressInstrumentation, SpanNameHook } from '@opentelemetry/instrumentation-express';
|
|
4
4
|
import { UndiciInstrumentation } from '@opentelemetry/instrumentation-undici';
|
|
5
5
|
import { GenericPoolInstrumentation } from '@opentelemetry/instrumentation-generic-pool';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
HttpInstrumentation,
|
|
8
|
+
IgnoreIncomingRequestFunction,
|
|
9
|
+
} from '@opentelemetry/instrumentation-http';
|
|
7
10
|
import { IORedisInstrumentation } from '@opentelemetry/instrumentation-ioredis';
|
|
8
11
|
import { NetInstrumentation } from '@opentelemetry/instrumentation-net';
|
|
9
12
|
import { GraphQLInstrumentation } from '@opentelemetry/instrumentation-graphql';
|
package/src/types.ts
CHANGED
|
@@ -78,6 +78,8 @@ export interface Service<
|
|
|
78
78
|
|
|
79
79
|
// Called after a server is created but before the server starts listening
|
|
80
80
|
attachServer?: (app: ServiceExpress<SLocals>, server: Server) => void | Promise<void>;
|
|
81
|
+
// Called after the server is listening
|
|
82
|
+
onListening?: (app: ServiceExpress<SLocals>, port: number) => void | Promise<void>;
|
|
81
83
|
|
|
82
84
|
start(app: ServiceExpress<SLocals>): void | Promise<void>;
|
|
83
85
|
|