@openapi-typescript-infra/service 6.10.1 → 6.10.2
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/package.json +8 -2
- package/.github/workflows/codeql-analysis.yml +0 -77
- package/.github/workflows/nodejs.yml +0 -62
- package/.trunk/configs/.markdownlint.yaml +0 -10
- package/.trunk/configs/.yamllint.yaml +0 -10
- package/.trunk/trunk.yaml +0 -35
- package/.yarn/patches/confit-npm-3.0.0-eade8c7ce1.patch +0 -52
- package/.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs +0 -541
- package/.yarn/releases/yarn-3.2.3.cjs +0 -783
- package/.yarnrc.yml +0 -7
- package/CHANGELOG.md +0 -525
- package/SECURITY.md +0 -12
- package/__tests__/config.test.ts +0 -53
- package/__tests__/fake-serv/api/fake-serv.yaml +0 -48
- package/__tests__/fake-serv/config/config.json +0 -13
- package/__tests__/fake-serv/src/handlers/hello.ts +0 -17
- package/__tests__/fake-serv/src/index.ts +0 -36
- package/__tests__/fake-serv/src/routes/error.ts +0 -16
- package/__tests__/fake-serv/src/routes/index.ts +0 -19
- package/__tests__/fake-serv/src/routes/other/world.ts +0 -7
- package/__tests__/fake-serv.test.ts +0 -119
- package/__tests__/vitest.test-setup.ts +0 -15
- package/src/bin/start-service.ts +0 -32
- package/src/bootstrap.ts +0 -160
- package/src/config/index.ts +0 -124
- package/src/config/schema.ts +0 -70
- package/src/config/shortstops.ts +0 -155
- package/src/config/validation.ts +0 -23
- package/src/development/port-finder.ts +0 -67
- package/src/development/repl.ts +0 -131
- package/src/env.ts +0 -29
- package/src/error.ts +0 -47
- package/src/express-app/app.ts +0 -438
- package/src/express-app/index.ts +0 -3
- package/src/express-app/internal-server.ts +0 -43
- package/src/express-app/modules.ts +0 -10
- package/src/express-app/route-loader.ts +0 -40
- package/src/express-app/types.ts +0 -32
- package/src/hook.ts +0 -36
- package/src/index.ts +0 -9
- package/src/openapi.ts +0 -184
- package/src/telemetry/DummyExporter.ts +0 -17
- package/src/telemetry/hook-modules.ts +0 -8
- package/src/telemetry/index.ts +0 -168
- package/src/telemetry/instrumentations.ts +0 -103
- package/src/telemetry/requestLogger.ts +0 -267
- package/src/tsx.d.ts +0 -1
- package/src/types.ts +0 -223
package/src/express-app/app.ts
DELETED
|
@@ -1,438 +0,0 @@
|
|
|
1
|
-
import assert from 'assert';
|
|
2
|
-
import http from 'http';
|
|
3
|
-
import https from 'https';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
|
|
6
|
-
import { destination, type Logger, pino } from 'pino';
|
|
7
|
-
import cookieParser from 'cookie-parser';
|
|
8
|
-
import { context, metrics, trace } from '@opentelemetry/api';
|
|
9
|
-
import { setupNodeMetrics } from '@sesamecare-oss/opentelemetry-node-metrics';
|
|
10
|
-
import { createTerminus } from '@godaddy/terminus';
|
|
11
|
-
import type { RequestHandler, Response } from 'express';
|
|
12
|
-
|
|
13
|
-
import { loadConfiguration } from '../config/index.js';
|
|
14
|
-
import { openApi } from '../openapi.js';
|
|
15
|
-
import {
|
|
16
|
-
errorHandlerMiddleware,
|
|
17
|
-
loggerMiddleware,
|
|
18
|
-
notFoundMiddleware,
|
|
19
|
-
} from '../telemetry/requestLogger.js';
|
|
20
|
-
import type {
|
|
21
|
-
AnyServiceLocals,
|
|
22
|
-
RequestLocals,
|
|
23
|
-
RequestWithApp,
|
|
24
|
-
ServiceExpress,
|
|
25
|
-
ServiceLocals,
|
|
26
|
-
ServiceOptions,
|
|
27
|
-
ServiceStartOptions,
|
|
28
|
-
} from '../types.js';
|
|
29
|
-
import type { ConfigurationSchema } from '../config/schema.js';
|
|
30
|
-
import { shortstops } from '../config/shortstops.js';
|
|
31
|
-
import { getNodeEnv, isDev } from '../env.js';
|
|
32
|
-
import { getGlobalPrometheusExporter } from '../telemetry/index.js';
|
|
33
|
-
|
|
34
|
-
import { loadRoutes } from './route-loader.js';
|
|
35
|
-
import { startInternalApp } from './internal-server.js';
|
|
36
|
-
|
|
37
|
-
function isSyncLogging() {
|
|
38
|
-
if (process.env.LOG_SYNC) {
|
|
39
|
-
return true;
|
|
40
|
-
}
|
|
41
|
-
return isDev() || getNodeEnv() === 'test';
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export async function startApp<
|
|
45
|
-
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
46
|
-
RLocals extends RequestLocals = RequestLocals,
|
|
47
|
-
>(startOptions: ServiceStartOptions<SLocals, RLocals>): Promise<ServiceExpress<SLocals>> {
|
|
48
|
-
const { service, rootDirectory, codepath = 'build', name, version } = startOptions;
|
|
49
|
-
const shouldPrettyPrint = isDev() && !process.env.NO_PRETTY_LOGS;
|
|
50
|
-
const pinoDestination = destination({
|
|
51
|
-
sync: isSyncLogging(),
|
|
52
|
-
dest: process.env.LOG_TO_FILE || process.stdout.fd,
|
|
53
|
-
minLength: process.env.LOG_BUFFER ? Number(process.env.LOG_BUFFER) : undefined,
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
function poorMansOtlp(mergeObject: object) {
|
|
57
|
-
if (!('trace_id' in mergeObject)) {
|
|
58
|
-
const activeSpan = trace.getSpan(context.active());
|
|
59
|
-
if (activeSpan) {
|
|
60
|
-
const ctx = activeSpan.spanContext();
|
|
61
|
-
Object.assign(mergeObject, {
|
|
62
|
-
trace_id: ctx.traceId,
|
|
63
|
-
span_id: ctx.spanId,
|
|
64
|
-
trace_flags: ctx.traceFlags,
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return mergeObject;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const logger = shouldPrettyPrint
|
|
72
|
-
? pino(
|
|
73
|
-
{
|
|
74
|
-
transport: {
|
|
75
|
-
target: 'pino-pretty',
|
|
76
|
-
options: {
|
|
77
|
-
colorize: true,
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
mixin: poorMansOtlp,
|
|
81
|
-
},
|
|
82
|
-
pinoDestination,
|
|
83
|
-
)
|
|
84
|
-
: pino(
|
|
85
|
-
{
|
|
86
|
-
formatters: {
|
|
87
|
-
level(label) {
|
|
88
|
-
return { level: label };
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
|
-
mixin: poorMansOtlp,
|
|
92
|
-
},
|
|
93
|
-
pinoDestination,
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
const serviceImpl = service();
|
|
97
|
-
assert(serviceImpl?.start, 'Service function did not return a conforming object');
|
|
98
|
-
|
|
99
|
-
const sourceDirectory = path.join(rootDirectory, codepath);
|
|
100
|
-
const codeExtension = codepath === 'src' ? '.ts' : '.js';
|
|
101
|
-
|
|
102
|
-
const baseOptions: ServiceOptions = {
|
|
103
|
-
configurationDirectories: [path.resolve(rootDirectory, './config')],
|
|
104
|
-
shortstopHandlers: shortstops({ name }, sourceDirectory),
|
|
105
|
-
codepath,
|
|
106
|
-
codeExtension,
|
|
107
|
-
};
|
|
108
|
-
const options = serviceImpl.configure?.(startOptions, baseOptions) || baseOptions;
|
|
109
|
-
|
|
110
|
-
const config = await loadConfiguration({
|
|
111
|
-
configurationDirectories: options.configurationDirectories,
|
|
112
|
-
shortstopHandlers: options.shortstopHandlers,
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
const logging = config.logging;
|
|
116
|
-
logger.level = logging?.level || 'info';
|
|
117
|
-
|
|
118
|
-
// Concentrate the Typescript ugliness...
|
|
119
|
-
const { default: express } = await import('express');
|
|
120
|
-
const app = express() as unknown as ServiceExpress<SLocals>;
|
|
121
|
-
const routing = config.routing;
|
|
122
|
-
|
|
123
|
-
app.disable('x-powered-by');
|
|
124
|
-
if (routing?.etag !== true) {
|
|
125
|
-
app.disable('etag');
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
Object.assign(app.locals, startOptions.locals, {
|
|
129
|
-
service: serviceImpl,
|
|
130
|
-
logger,
|
|
131
|
-
config,
|
|
132
|
-
name,
|
|
133
|
-
version,
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
if (serviceImpl.attach) {
|
|
137
|
-
await serviceImpl.attach(app);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
app.locals.meter = metrics.getMeterProvider().getMeter(name);
|
|
141
|
-
setupNodeMetrics(app.locals.meter, {});
|
|
142
|
-
|
|
143
|
-
if (config.trustProxy === true) {
|
|
144
|
-
app.enable('trust proxy');
|
|
145
|
-
} else if (config.trustProxy) {
|
|
146
|
-
app.set('trust proxy', config.trustProxy);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const histogram = app.locals.meter.createHistogram('http_request_duration_seconds', {
|
|
150
|
-
description: 'Duration of HTTP requests in seconds',
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
app.use(loggerMiddleware(app, histogram, logging));
|
|
154
|
-
|
|
155
|
-
// Allow the service to add locals, etc. We put this before the body parsers
|
|
156
|
-
// so that the req can decide whether to save the raw request body or not.
|
|
157
|
-
const attachServiceLocals: RequestHandler = (req, res, next) => {
|
|
158
|
-
res.locals.logger = logger;
|
|
159
|
-
try {
|
|
160
|
-
const result = serviceImpl.onRequest?.(
|
|
161
|
-
req as RequestWithApp<SLocals>,
|
|
162
|
-
res as Response<unknown, RLocals>,
|
|
163
|
-
);
|
|
164
|
-
if (result !== undefined && result !== null && typeof result.then === 'function') {
|
|
165
|
-
void result.catch(next).then(next);
|
|
166
|
-
} else {
|
|
167
|
-
next();
|
|
168
|
-
}
|
|
169
|
-
} catch (error) {
|
|
170
|
-
next(error);
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
app.use(attachServiceLocals);
|
|
174
|
-
|
|
175
|
-
if (routing?.cookieParser) {
|
|
176
|
-
app.use(cookieParser());
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (routing?.bodyParsers?.json) {
|
|
180
|
-
const jsonArgs = typeof routing.bodyParsers.json === 'object' ? routing.bodyParsers.json : {};
|
|
181
|
-
app.use(
|
|
182
|
-
express.json({
|
|
183
|
-
verify(req, res, buf) {
|
|
184
|
-
const locals = (res as Response).locals as RequestLocals;
|
|
185
|
-
if (locals?.rawBody === true) {
|
|
186
|
-
locals.rawBody = buf;
|
|
187
|
-
}
|
|
188
|
-
},
|
|
189
|
-
...jsonArgs,
|
|
190
|
-
}),
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
if (routing?.bodyParsers?.form) {
|
|
194
|
-
app.use(
|
|
195
|
-
express.urlencoded(
|
|
196
|
-
typeof routing.bodyParsers.form === 'object' ? routing.bodyParsers.form : {},
|
|
197
|
-
),
|
|
198
|
-
);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (serviceImpl.authorize) {
|
|
202
|
-
const authorize: RequestHandler = (req, res, next) => {
|
|
203
|
-
let maybePromise: Promise<boolean> | boolean | undefined;
|
|
204
|
-
try {
|
|
205
|
-
maybePromise = serviceImpl.authorize?.(
|
|
206
|
-
req as RequestWithApp<SLocals>,
|
|
207
|
-
res as Response<unknown, RLocals>,
|
|
208
|
-
);
|
|
209
|
-
} catch (error) {
|
|
210
|
-
next(error);
|
|
211
|
-
}
|
|
212
|
-
if (maybePromise && typeof maybePromise !== 'boolean') {
|
|
213
|
-
maybePromise
|
|
214
|
-
.then((val) => {
|
|
215
|
-
if (val === false) {
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
next();
|
|
219
|
-
})
|
|
220
|
-
.catch(next);
|
|
221
|
-
} else if (maybePromise !== false) {
|
|
222
|
-
next();
|
|
223
|
-
}
|
|
224
|
-
};
|
|
225
|
-
app.use(authorize);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (routing?.static?.enabled) {
|
|
229
|
-
const localdir = path.resolve(rootDirectory, routing?.static?.path || 'public');
|
|
230
|
-
if (routing.static.mountPath) {
|
|
231
|
-
app.use(routing.static.mountPath, express.static(localdir));
|
|
232
|
-
} else {
|
|
233
|
-
app.use(express.static(localdir));
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (routing?.freezeQuery) {
|
|
238
|
-
app.use(function freezeQuery(req, res, next) {
|
|
239
|
-
// Express 5 re-parses the query string every time. This causes problems with
|
|
240
|
-
// various libraries, namely the express OpenAPI parser. So we "freeze it" in place
|
|
241
|
-
// here, which runs right before the routing validation logic does. Note that this
|
|
242
|
-
// means the app middleware will see the unfrozen one, which is intentional. If the
|
|
243
|
-
// app wants to modify or freeze the query itself, this shouldn't get in the way.
|
|
244
|
-
const { query } = req;
|
|
245
|
-
if (query) {
|
|
246
|
-
Object.defineProperty(req, 'query', {
|
|
247
|
-
configurable: true,
|
|
248
|
-
enumerable: true,
|
|
249
|
-
value: query,
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
next();
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const codePattern = codepath === 'src' ? '**/*.ts' : '**/*.js';
|
|
257
|
-
if (routing?.routes) {
|
|
258
|
-
await loadRoutes(
|
|
259
|
-
app,
|
|
260
|
-
path.resolve(rootDirectory, codepath, config.routing?.routes || 'routes'),
|
|
261
|
-
codePattern,
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
if (routing?.openapi) {
|
|
265
|
-
const openApiMiddleware = await openApi(
|
|
266
|
-
app,
|
|
267
|
-
rootDirectory,
|
|
268
|
-
codepath,
|
|
269
|
-
codePattern,
|
|
270
|
-
options.openApiOptions,
|
|
271
|
-
);
|
|
272
|
-
app.use(openApiMiddleware);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Putting this here allows more flexible middleware insertion
|
|
276
|
-
await serviceImpl.start(app);
|
|
277
|
-
|
|
278
|
-
const { notFound, errors } = routing?.finalHandlers || {};
|
|
279
|
-
if (notFound) {
|
|
280
|
-
app.use(notFoundMiddleware());
|
|
281
|
-
}
|
|
282
|
-
if (errors?.enabled) {
|
|
283
|
-
app.use(errorHandlerMiddleware(app, histogram, errors?.unnest, errors?.render));
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
return app;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
export type StartAppFn<
|
|
290
|
-
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
291
|
-
RLocals extends RequestLocals = RequestLocals,
|
|
292
|
-
> = typeof startApp<SLocals, RLocals>;
|
|
293
|
-
|
|
294
|
-
export async function shutdownApp<
|
|
295
|
-
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
296
|
-
>(app: ServiceExpress<SLocals>) {
|
|
297
|
-
const { logger } = app.locals;
|
|
298
|
-
try {
|
|
299
|
-
await app.locals.service.stop?.(app);
|
|
300
|
-
logger.info('App shutdown complete');
|
|
301
|
-
} catch (error) {
|
|
302
|
-
logger.warn(error, 'Shutdown failed');
|
|
303
|
-
}
|
|
304
|
-
(logger as Logger).flush?.();
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
function httpServer<
|
|
308
|
-
Config extends ConfigurationSchema = ConfigurationSchema,
|
|
309
|
-
SLocals extends ServiceLocals<Config> = ServiceLocals<Config>,
|
|
310
|
-
>(app: ServiceExpress<SLocals>, config: ConfigurationSchema['server']) {
|
|
311
|
-
if (!config.certificate) {
|
|
312
|
-
return http.createServer(app);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
return https.createServer(
|
|
316
|
-
{
|
|
317
|
-
key: config.key ? Buffer.from(config.key as string) : undefined,
|
|
318
|
-
cert: config.certificate ? Buffer.from(config.certificate as string) : undefined,
|
|
319
|
-
},
|
|
320
|
-
app,
|
|
321
|
-
);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
function url(config: ConfigurationSchema['server'], port?: number) {
|
|
325
|
-
if (config.certificate) {
|
|
326
|
-
return `https://${config.hostname}${port === 443 ? '' : `:${port}`}`;
|
|
327
|
-
}
|
|
328
|
-
return `http://${config.hostname}${port === 80 ? '' : `:${port}`}`;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
export async function listen<SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>>(
|
|
332
|
-
app: ServiceExpress<SLocals>,
|
|
333
|
-
shutdownHandler?: () => Promise<void>,
|
|
334
|
-
) {
|
|
335
|
-
const config: ConfigurationSchema['server'] = app.locals.config.server || {};
|
|
336
|
-
const { port } = config;
|
|
337
|
-
|
|
338
|
-
const { service, logger } = app.locals;
|
|
339
|
-
const server = httpServer(app, config);
|
|
340
|
-
await app.locals.service.attachServer?.(app, server);
|
|
341
|
-
|
|
342
|
-
let shutdownInProgress = false;
|
|
343
|
-
createTerminus(server, {
|
|
344
|
-
timeout: 15000,
|
|
345
|
-
useExit0: true,
|
|
346
|
-
// https://github.com/godaddy/terminus#how-to-set-terminus-up-with-kubernetes
|
|
347
|
-
beforeShutdown() {
|
|
348
|
-
if (shutdownInProgress) {
|
|
349
|
-
return Promise.resolve();
|
|
350
|
-
}
|
|
351
|
-
shutdownInProgress = true;
|
|
352
|
-
if (app.locals.internalApp) {
|
|
353
|
-
app.locals.internalApp.locals.server?.close();
|
|
354
|
-
}
|
|
355
|
-
logger.info('Graceful shutdown beginning');
|
|
356
|
-
return new Promise((accept) => {
|
|
357
|
-
// Per docs https://www.npmjs.com/package/@godaddy/terminus in Kubernetes, wait for readiness threshold
|
|
358
|
-
setTimeout(accept, 10000);
|
|
359
|
-
});
|
|
360
|
-
},
|
|
361
|
-
onShutdown() {
|
|
362
|
-
return Promise.resolve()
|
|
363
|
-
.then(() => service.stop?.(app))
|
|
364
|
-
.then(() => {
|
|
365
|
-
logger.info('Service stop complete');
|
|
366
|
-
})
|
|
367
|
-
.then(shutdownHandler || (() => Promise.resolve()))
|
|
368
|
-
.then(() => logger.info('Graceful shutdown complete'))
|
|
369
|
-
.catch((error) => logger.error(error, 'Error terminating tracing'))
|
|
370
|
-
.then(() => (logger as Logger).flush?.());
|
|
371
|
-
},
|
|
372
|
-
logger: (msg, e) => {
|
|
373
|
-
logger.error(e, msg);
|
|
374
|
-
},
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
server.on('close', () => {
|
|
378
|
-
if (!shutdownInProgress) {
|
|
379
|
-
shutdownInProgress = true;
|
|
380
|
-
app.locals.logger.info('Shutdown requested');
|
|
381
|
-
if (app.locals.internalApp) {
|
|
382
|
-
if (app.locals.internalApp.locals.server?.listening) {
|
|
383
|
-
app.locals.internalApp.locals.server?.close();
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
void shutdownApp(app);
|
|
387
|
-
}
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
server.on('error', (error) => {
|
|
391
|
-
logger.error(error, 'Main service listener error');
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
// TODO handle rejection/error?
|
|
395
|
-
const listenPromise = new Promise<void>((accept) => {
|
|
396
|
-
server.listen(port, () => {
|
|
397
|
-
const { locals } = app;
|
|
398
|
-
locals.logger.info({ url: url(config, port), service: locals.name }, 'express listening');
|
|
399
|
-
|
|
400
|
-
const serverConfig = app.locals.config.server;
|
|
401
|
-
// Ok now start the internal port if we have one.
|
|
402
|
-
if (serverConfig?.internalPort || serverConfig?.internalPort === 0) {
|
|
403
|
-
startInternalApp(app, serverConfig.internalPort as number)
|
|
404
|
-
.then((internalApp) => {
|
|
405
|
-
locals.internalApp = internalApp;
|
|
406
|
-
const prometheusExporter = getGlobalPrometheusExporter();
|
|
407
|
-
if (prometheusExporter) {
|
|
408
|
-
locals.internalApp.get(
|
|
409
|
-
'/metrics',
|
|
410
|
-
prometheusExporter.getMetricsRequestHandler.bind(prometheusExporter),
|
|
411
|
-
);
|
|
412
|
-
locals.logger.info('Metrics exporter started');
|
|
413
|
-
} else {
|
|
414
|
-
locals.logger.info('No metrics will be exported');
|
|
415
|
-
}
|
|
416
|
-
if (app.locals.openApiSpecification) {
|
|
417
|
-
locals.internalApp.get('/api-docs', (req, res) => {
|
|
418
|
-
res.json(app.locals.openApiSpecification);
|
|
419
|
-
});
|
|
420
|
-
}
|
|
421
|
-
accept();
|
|
422
|
-
})
|
|
423
|
-
.catch((error) => {
|
|
424
|
-
locals.logger.warn(error, 'Failed to start internal metadata app');
|
|
425
|
-
});
|
|
426
|
-
} else {
|
|
427
|
-
accept();
|
|
428
|
-
}
|
|
429
|
-
});
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
await listenPromise;
|
|
433
|
-
await service.onListening?.(app, { port, protocol: config.certificate ? 'https' : 'http' });
|
|
434
|
-
return server;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
export type ListenFn<SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>> =
|
|
438
|
-
typeof listen<SLocals>;
|
package/src/express-app/index.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import express from 'express';
|
|
2
|
-
import type { Application } from 'express-serve-static-core';
|
|
3
|
-
|
|
4
|
-
import type { AnyServiceLocals, InternalLocals, ServiceExpress, ServiceLocals } from '../types.js';
|
|
5
|
-
import { getAvailablePort } from '../development/port-finder.js';
|
|
6
|
-
import type { ConfigurationSchema } from '../config/schema.js';
|
|
7
|
-
|
|
8
|
-
export async function startInternalApp<
|
|
9
|
-
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
10
|
-
>(mainApp: ServiceExpress<SLocals>, port: number) {
|
|
11
|
-
const app = express() as unknown as Application<InternalLocals<SLocals>>;
|
|
12
|
-
app.locals.mainApp = mainApp;
|
|
13
|
-
|
|
14
|
-
const finalPort = port === 0 ? await getAvailablePort(3001) : port;
|
|
15
|
-
|
|
16
|
-
app.get('/health', async (req, res) => {
|
|
17
|
-
if (mainApp.locals.service?.healthy) {
|
|
18
|
-
try {
|
|
19
|
-
const ok = await mainApp.locals.service.healthy(mainApp);
|
|
20
|
-
res.sendStatus(ok ? 200 : 500);
|
|
21
|
-
} catch (error) {
|
|
22
|
-
mainApp.locals.logger.error(error, 'Health check failed');
|
|
23
|
-
}
|
|
24
|
-
} else {
|
|
25
|
-
res.sendStatus(200);
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const listenPromise = new Promise<void>((accept) => {
|
|
30
|
-
app.locals.server = app.listen(finalPort, () => {
|
|
31
|
-
accept();
|
|
32
|
-
});
|
|
33
|
-
app.locals.server.on('error', (error) => {
|
|
34
|
-
mainApp.locals.logger.error(error, 'Internal app server error');
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
await listenPromise;
|
|
39
|
-
|
|
40
|
-
mainApp.locals.logger.info({ port: finalPort }, 'Internal metadata server started');
|
|
41
|
-
|
|
42
|
-
return app;
|
|
43
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { glob } from 'glob';
|
|
2
|
-
|
|
3
|
-
export async function getFilesInDir(pattern: string, dir: string) {
|
|
4
|
-
const files = await glob(pattern, {
|
|
5
|
-
nodir: true,
|
|
6
|
-
cwd: dir,
|
|
7
|
-
ignore: ['**/*.spec.@(js|ts)', '**/*.test.@(js|ts)', '**/*.fixtures.@(js|ts)'],
|
|
8
|
-
});
|
|
9
|
-
return files;
|
|
10
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
|
|
3
|
-
import { Router } from 'express';
|
|
4
|
-
|
|
5
|
-
import type { AnyServiceLocals, ServiceExpress, ServiceLocals } from '../types.js';
|
|
6
|
-
import type { ConfigurationSchema } from '../config/schema.js';
|
|
7
|
-
|
|
8
|
-
import { getFilesInDir } from './modules.js';
|
|
9
|
-
|
|
10
|
-
export async function loadRoutes<
|
|
11
|
-
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
12
|
-
>(app: ServiceExpress<SLocals>, routingDir: string, pattern: string) {
|
|
13
|
-
const files = await getFilesInDir(pattern, routingDir);
|
|
14
|
-
|
|
15
|
-
await Promise.all(
|
|
16
|
-
files.map(async (filename) => {
|
|
17
|
-
const routeBase = path.dirname(filename);
|
|
18
|
-
const modulePath = path.resolve(routingDir, filename);
|
|
19
|
-
const module = await import(modulePath);
|
|
20
|
-
const mounter = module.default || module.route;
|
|
21
|
-
if (typeof mounter === 'function') {
|
|
22
|
-
const childRouter = Router();
|
|
23
|
-
mounter(childRouter, app);
|
|
24
|
-
const pathParts = [''];
|
|
25
|
-
if (routeBase !== '.') {
|
|
26
|
-
pathParts.push(routeBase);
|
|
27
|
-
}
|
|
28
|
-
const fn = path.parse(path.basename(filename)).name;
|
|
29
|
-
if (fn.toLowerCase() !== 'index') {
|
|
30
|
-
pathParts.push(fn);
|
|
31
|
-
}
|
|
32
|
-
const finalPath = pathParts.join('/') || '/';
|
|
33
|
-
app.locals.logger.debug({ path: finalPath, source: filename }, 'Registering routes');
|
|
34
|
-
app.use(finalPath, childRouter);
|
|
35
|
-
} else {
|
|
36
|
-
app.locals.logger.warn({ filename }, 'Route file had no default export');
|
|
37
|
-
}
|
|
38
|
-
}),
|
|
39
|
-
);
|
|
40
|
-
}
|
package/src/express-app/types.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import type { NextFunction, Response } from 'express';
|
|
2
|
-
|
|
3
|
-
import type { AnyServiceLocals, RequestLocals, RequestWithApp, ServiceLocals } from '../types.js';
|
|
4
|
-
import type { ConfigurationSchema } from '../config/schema.js';
|
|
5
|
-
|
|
6
|
-
export type ServiceHandler<
|
|
7
|
-
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
8
|
-
RLocals extends RequestLocals = RequestLocals,
|
|
9
|
-
ResBody = unknown,
|
|
10
|
-
RetType = unknown,
|
|
11
|
-
> = (
|
|
12
|
-
req: RequestWithApp<SLocals>,
|
|
13
|
-
res: Response<ResBody, RLocals>,
|
|
14
|
-
next: NextFunction,
|
|
15
|
-
) => RetType | Promise<RetType>;
|
|
16
|
-
|
|
17
|
-
// Make it easier to declare route files. This is not an exhaustive list
|
|
18
|
-
// of supported router methods, but it has the most common ones.
|
|
19
|
-
export interface ServiceRouter<
|
|
20
|
-
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
21
|
-
RLocals extends RequestLocals = RequestLocals,
|
|
22
|
-
> {
|
|
23
|
-
all(path: string | RegExp, ...handlers: ServiceHandler<SLocals, RLocals>[]): void;
|
|
24
|
-
get(path: string | RegExp, ...handlers: ServiceHandler<SLocals, RLocals>[]): void;
|
|
25
|
-
post(path: string | RegExp, ...handlers: ServiceHandler<SLocals, RLocals>[]): void;
|
|
26
|
-
put(path: string | RegExp, ...handlers: ServiceHandler<SLocals, RLocals>[]): void;
|
|
27
|
-
delete(path: string | RegExp, ...handlers: ServiceHandler<SLocals, RLocals>[]): void;
|
|
28
|
-
patch(path: string | RegExp, ...handlers: ServiceHandler<SLocals, RLocals>[]): void;
|
|
29
|
-
options(path: string | RegExp, ...handlers: ServiceHandler<SLocals, RLocals>[]): void;
|
|
30
|
-
head(path: string | RegExp, ...handlers: ServiceHandler<SLocals, RLocals>[]): void;
|
|
31
|
-
use(...handlers: ServiceHandler<SLocals, RLocals>[]): void;
|
|
32
|
-
}
|
package/src/hook.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { ConfigurationSchema } from './config/schema.js';
|
|
2
|
-
import type { AnyServiceLocals, RequestLocals, Service, ServiceLocals } from './types.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Your service should call this function and then "inherit"
|
|
6
|
-
* the behavior in a functional way. So,
|
|
7
|
-
*
|
|
8
|
-
* const myServiceFn = () => {
|
|
9
|
-
* const baseService = useService<YourService>();
|
|
10
|
-
* return {
|
|
11
|
-
* ...baseService,
|
|
12
|
-
* async start(app) {
|
|
13
|
-
* await baseService.start(app);
|
|
14
|
-
* // your start stuff goes here
|
|
15
|
-
* },
|
|
16
|
-
* async onRequest(req, res) {
|
|
17
|
-
* // This might throw (auth for example), so don't catch it
|
|
18
|
-
* await baseService?.onRequest(req, res);
|
|
19
|
-
* },
|
|
20
|
-
* }
|
|
21
|
-
* }
|
|
22
|
-
*
|
|
23
|
-
* @returns Service<Config, SLocals, RLocals>
|
|
24
|
-
*/
|
|
25
|
-
export function useService<
|
|
26
|
-
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
27
|
-
RLocals extends RequestLocals = RequestLocals,
|
|
28
|
-
>(baseService?: Service<SLocals, RLocals>): Service<SLocals, RLocals> {
|
|
29
|
-
return {
|
|
30
|
-
async start(app) {
|
|
31
|
-
await baseService?.start(app);
|
|
32
|
-
// Do nothing. This hook exists mainly to reduce change required
|
|
33
|
-
// to adopt your specific companies base service.
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export * from './telemetry/index.js';
|
|
2
|
-
export * from './express-app/index.js';
|
|
3
|
-
export * from './types.js';
|
|
4
|
-
export * from './env.js';
|
|
5
|
-
export * from './config/index.js';
|
|
6
|
-
export * from './error.js';
|
|
7
|
-
export * from './bootstrap.js';
|
|
8
|
-
export * from './hook.js';
|
|
9
|
-
export { repl$ } from './development/repl.js';
|