@openapi-typescript-infra/service 3.0.1 → 3.0.3
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/CHANGELOG.md +15 -0
- package/build/bootstrap.d.ts +3 -2
- package/build/bootstrap.js.map +1 -1
- package/build/development/repl.d.ts +3 -2
- package/build/development/repl.js.map +1 -1
- package/build/error.d.ts +4 -3
- package/build/error.js.map +1 -1
- package/build/express-app/app.d.ts +7 -6
- package/build/express-app/app.js +5 -2
- package/build/express-app/app.js.map +1 -1
- package/build/express-app/internal-server.d.ts +3 -2
- package/build/express-app/internal-server.js.map +1 -1
- package/build/express-app/route-loader.d.ts +3 -2
- package/build/express-app/route-loader.js.map +1 -1
- package/build/express-app/types.d.ts +4 -3
- package/build/hook.d.ts +4 -3
- package/build/hook.js +1 -1
- package/build/hook.js.map +1 -1
- package/build/index.d.ts +0 -1
- package/build/index.js +0 -1
- package/build/index.js.map +1 -1
- package/build/openapi.d.ts +3 -2
- package/build/openapi.js.map +1 -1
- package/build/telemetry/index.d.ts +3 -2
- package/build/telemetry/index.js.map +1 -1
- package/build/telemetry/requestLogger.d.ts +4 -3
- package/build/telemetry/requestLogger.js.map +1 -1
- package/build/tsconfig.build.tsbuildinfo +1 -1
- package/build/types.d.ts +13 -11
- package/package.json +4 -5
- package/src/bootstrap.ts +3 -2
- package/src/development/repl.ts +6 -2
- package/src/error.ts +6 -3
- package/src/express-app/app.ts +21 -13
- package/src/express-app/internal-server.ts +6 -3
- package/src/express-app/route-loader.ts +5 -2
- package/src/express-app/types.ts +4 -3
- package/src/hook.ts +4 -3
- package/src/index.ts +0 -1
- package/src/openapi.ts +6 -3
- package/src/telemetry/index.ts +3 -1
- package/src/telemetry/requestLogger.ts +9 -12
- package/src/types.ts +32 -13
- package/build/service-calls/index.d.ts +0 -16
- package/build/service-calls/index.js +0 -85
- package/build/service-calls/index.js.map +0 -1
- package/src/service-calls/index.ts +0 -121
package/build/types.d.ts
CHANGED
|
@@ -10,9 +10,9 @@ import type { middleware } from 'express-openapi-validator';
|
|
|
10
10
|
import type { Meter } from '@opentelemetry/api';
|
|
11
11
|
import { Confit } from '@sesamecare-oss/confit';
|
|
12
12
|
import { ConfigurationSchema } from './config/schema';
|
|
13
|
-
export interface InternalLocals extends Record<string, unknown> {
|
|
13
|
+
export interface InternalLocals<SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>> extends Record<string, unknown> {
|
|
14
14
|
server?: Server;
|
|
15
|
-
mainApp: ServiceExpress
|
|
15
|
+
mainApp: ServiceExpress<SLocals>;
|
|
16
16
|
}
|
|
17
17
|
export type ServiceLogger = pino.BaseLogger & Pick<pino.Logger, 'isLevelEnabled'>;
|
|
18
18
|
export interface ServiceLocals<Config extends ConfigurationSchema = ConfigurationSchema> {
|
|
@@ -21,21 +21,21 @@ export interface ServiceLocals<Config extends ConfigurationSchema = Configuratio
|
|
|
21
21
|
logger: ServiceLogger;
|
|
22
22
|
config: Confit<Config>;
|
|
23
23
|
meter: Meter;
|
|
24
|
-
internalApp: Application<InternalLocals
|
|
24
|
+
internalApp: Application<InternalLocals<this>>;
|
|
25
25
|
}
|
|
26
26
|
export interface RequestLocals {
|
|
27
27
|
rawBody?: Buffer | true;
|
|
28
28
|
logger: ServiceLogger;
|
|
29
29
|
}
|
|
30
|
-
export type ServiceExpress<Locals extends
|
|
31
|
-
export type RequestWithApp<Locals extends
|
|
30
|
+
export type ServiceExpress<Locals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>> = Application<Locals>;
|
|
31
|
+
export type RequestWithApp<Locals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>> = Omit<Request, 'app'> & {
|
|
32
32
|
app: Application<Locals>;
|
|
33
33
|
};
|
|
34
34
|
export type ResponseFromApp<ResBody = unknown, RLocals extends RequestLocals = RequestLocals> = Response<ResBody, RLocals>;
|
|
35
35
|
/**
|
|
36
36
|
* This is the core type you need to implement to provide a service
|
|
37
37
|
*/
|
|
38
|
-
export interface Service<SLocals extends
|
|
38
|
+
export interface Service<SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>, RLocals extends RequestLocals = RequestLocals> {
|
|
39
39
|
name?: string;
|
|
40
40
|
configure?: (startOptions: ServiceStartOptions<SLocals, RLocals>, options: ServiceOptions) => ServiceOptions;
|
|
41
41
|
attach?: (app: ServiceExpress<SLocals>) => void | Promise<void>;
|
|
@@ -47,8 +47,8 @@ export interface Service<SLocals extends ServiceLocals = ServiceLocals, RLocals
|
|
|
47
47
|
getLogFields?(req: RequestWithApp<SLocals>, values: Record<string, string | string[] | number | undefined>): void;
|
|
48
48
|
attachRepl?(app: ServiceExpress<SLocals>, repl: REPLServer): void;
|
|
49
49
|
}
|
|
50
|
-
export type ServiceFactory<SLocals extends
|
|
51
|
-
export interface ServiceStartOptions<SLocals extends
|
|
50
|
+
export type ServiceFactory<SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>, RLocals extends RequestLocals = RequestLocals> = () => Service<SLocals, RLocals>;
|
|
51
|
+
export interface ServiceStartOptions<SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>, RLocals extends RequestLocals = RequestLocals> {
|
|
52
52
|
name: string;
|
|
53
53
|
rootDirectory: string;
|
|
54
54
|
codepath?: 'build' | 'src' | 'dist';
|
|
@@ -62,7 +62,7 @@ export interface ServiceOptions {
|
|
|
62
62
|
configurationDirectories: string[];
|
|
63
63
|
openApiOptions?: Parameters<typeof middleware>[0];
|
|
64
64
|
}
|
|
65
|
-
export interface ServiceLike<SLocals extends
|
|
65
|
+
export interface ServiceLike<SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>> {
|
|
66
66
|
locals: SLocals;
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
@@ -73,13 +73,13 @@ export interface ServiceLike<SLocals extends ServiceLocals = ServiceLocals> {
|
|
|
73
73
|
* like query strings or body or similar. Most often, you want the
|
|
74
74
|
* logger.
|
|
75
75
|
*/
|
|
76
|
-
export interface RequestLike<SLocals extends
|
|
76
|
+
export interface RequestLike<SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>, RLocals extends RequestLocals = RequestLocals> {
|
|
77
77
|
app: ServiceLike<SLocals>;
|
|
78
78
|
res: {
|
|
79
79
|
locals: RLocals;
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
|
-
export interface ServiceTypes<SLocals extends
|
|
82
|
+
export interface ServiceTypes<SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>, RLocals extends RequestLocals = RequestLocals, ResBody = unknown> {
|
|
83
83
|
App: ServiceExpress<SLocals>;
|
|
84
84
|
Handler: (req: RequestWithApp<SLocals>, res: ResponseFromApp<ResBody, RLocals>) => void | Promise<void>;
|
|
85
85
|
Request: RequestWithApp<SLocals>;
|
|
@@ -90,3 +90,5 @@ export interface ServiceTypes<SLocals extends ServiceLocals = ServiceLocals, RLo
|
|
|
90
90
|
ServiceFactory: ServiceFactory<SLocals, RLocals>;
|
|
91
91
|
ServiceLocals: SLocals;
|
|
92
92
|
}
|
|
93
|
+
export type UnwrapServiceConfig<SLocals extends ServiceLocals> = SLocals extends ServiceLocals<infer C> ? C : never;
|
|
94
|
+
export type AnyServiceLocals = ServiceLocals<any>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openapi-typescript-infra/service",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.3",
|
|
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": {
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"@opentelemetry/sdk-node": "^0.43.0",
|
|
81
81
|
"@opentelemetry/sdk-trace-base": "^1.17.1",
|
|
82
82
|
"@opentelemetry/semantic-conventions": "^1.17.1",
|
|
83
|
-
"@sesamecare-oss/confit": "^1.0
|
|
83
|
+
"@sesamecare-oss/confit": "^1.1.0",
|
|
84
84
|
"@sesamecare-oss/opentelemetry-node-metrics": "^1.0.1",
|
|
85
85
|
"cookie-parser": "^1.4.6",
|
|
86
86
|
"dotenv": "^16.3.1",
|
|
@@ -92,8 +92,7 @@
|
|
|
92
92
|
"minimist": "^1.2.8",
|
|
93
93
|
"opentelemetry-instrumentation-fetch-node": "^1.1.0",
|
|
94
94
|
"pino": "^8.16.0",
|
|
95
|
-
"read-pkg-up": "^7.0.1"
|
|
96
|
-
"rest-api-support": "^1.16.3"
|
|
95
|
+
"read-pkg-up": "^7.0.1"
|
|
97
96
|
},
|
|
98
97
|
"devDependencies": {
|
|
99
98
|
"@commitlint/cli": "^17.8.0",
|
|
@@ -104,7 +103,7 @@
|
|
|
104
103
|
"@semantic-release/exec": "^6.0.3",
|
|
105
104
|
"@semantic-release/git": "^10.0.1",
|
|
106
105
|
"@semantic-release/release-notes-generator": "^12.0.0",
|
|
107
|
-
"@types/cookie-parser": "^1.4.
|
|
106
|
+
"@types/cookie-parser": "^1.4.5",
|
|
108
107
|
"@types/eventsource": "1.1.12",
|
|
109
108
|
"@types/express": "^4.17.19",
|
|
110
109
|
"@types/glob": "^8.1.0",
|
package/src/bootstrap.ts
CHANGED
|
@@ -5,9 +5,10 @@ import dotenv from 'dotenv';
|
|
|
5
5
|
import readPackageUp from 'read-pkg-up';
|
|
6
6
|
import type { NormalizedPackageJson } from 'read-pkg-up';
|
|
7
7
|
|
|
8
|
-
import type { RequestLocals, ServiceLocals, ServiceStartOptions } from './types';
|
|
8
|
+
import type { AnyServiceLocals, RequestLocals, ServiceLocals, ServiceStartOptions } from './types';
|
|
9
9
|
import { isDev } from './env';
|
|
10
10
|
import { startWithTelemetry } from './telemetry/index';
|
|
11
|
+
import { ConfigurationSchema } from './config/schema';
|
|
11
12
|
|
|
12
13
|
interface BootstrapArguments {
|
|
13
14
|
// The name of the service, else discovered via read-pkg-up
|
|
@@ -68,7 +69,7 @@ function getBuildDir(main: string): 'build' | 'dist' {
|
|
|
68
69
|
// for jobs or other scripts that need service infra but are
|
|
69
70
|
// not simply the service
|
|
70
71
|
export async function bootstrap<
|
|
71
|
-
SLocals extends
|
|
72
|
+
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
72
73
|
RLocals extends RequestLocals = RequestLocals,
|
|
73
74
|
>(argv?: BootstrapArguments) {
|
|
74
75
|
const { main, rootDirectory, name } = await getServiceDetails(argv);
|
package/src/development/repl.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import repl from 'repl';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
|
|
4
|
-
import { ServiceExpress } from '../types';
|
|
4
|
+
import { AnyServiceLocals, ServiceExpress, ServiceLocals } from '../types';
|
|
5
|
+
import { ConfigurationSchema } from '../config/schema';
|
|
5
6
|
|
|
6
|
-
export function serviceRepl
|
|
7
|
+
export function serviceRepl<SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>>(
|
|
8
|
+
app: ServiceExpress<SLocals>,
|
|
9
|
+
onExit: () => void,
|
|
10
|
+
) {
|
|
7
11
|
const rl = repl.start({
|
|
8
12
|
prompt: '> ',
|
|
9
13
|
});
|
package/src/error.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ConfigurationSchema } from './config/schema';
|
|
2
|
+
import type { AnyServiceLocals, ServiceLike, ServiceLocals } from './types';
|
|
2
3
|
|
|
3
4
|
export interface ServiceErrorSpec {
|
|
4
5
|
status?: number;
|
|
@@ -16,7 +17,9 @@ export interface ServiceErrorSpec {
|
|
|
16
17
|
*
|
|
17
18
|
* You can also include a display_message which is intended to be viewed by the end user
|
|
18
19
|
*/
|
|
19
|
-
export class ServiceError
|
|
20
|
+
export class ServiceError<
|
|
21
|
+
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
22
|
+
> extends Error {
|
|
20
23
|
public status: number | undefined;
|
|
21
24
|
|
|
22
25
|
public code?: string;
|
|
@@ -32,7 +35,7 @@ export class ServiceError extends Error {
|
|
|
32
35
|
// take up the valuable mental space of an error log.
|
|
33
36
|
public expected_error?: boolean;
|
|
34
37
|
|
|
35
|
-
constructor(app: ServiceLike<
|
|
38
|
+
constructor(app: ServiceLike<SLocals>, message: string, spec?: ServiceErrorSpec) {
|
|
36
39
|
super(message);
|
|
37
40
|
this.domain = app.locals.name;
|
|
38
41
|
Object.assign(this, spec);
|
package/src/express-app/app.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { metrics } from '@opentelemetry/api';
|
|
|
10
10
|
import { setupNodeMetrics } from '@sesamecare-oss/opentelemetry-node-metrics';
|
|
11
11
|
import { createTerminus } from '@godaddy/terminus';
|
|
12
12
|
import type { RequestHandler, Response } from 'express';
|
|
13
|
+
import { Confit } from '@sesamecare-oss/confit';
|
|
13
14
|
|
|
14
15
|
import { loadConfiguration } from '../config/index';
|
|
15
16
|
import { openApi } from '../openapi';
|
|
@@ -19,6 +20,7 @@ import {
|
|
|
19
20
|
notFoundMiddleware,
|
|
20
21
|
} from '../telemetry/requestLogger';
|
|
21
22
|
import type {
|
|
23
|
+
AnyServiceLocals,
|
|
22
24
|
RequestLocals,
|
|
23
25
|
RequestWithApp,
|
|
24
26
|
ServiceExpress,
|
|
@@ -41,7 +43,7 @@ function isSyncLogging() {
|
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
export async function startApp<
|
|
44
|
-
SLocals extends
|
|
46
|
+
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
45
47
|
RLocals extends RequestLocals = RequestLocals,
|
|
46
48
|
>(startOptions: ServiceStartOptions<SLocals, RLocals>): Promise<ServiceExpress<SLocals>> {
|
|
47
49
|
const { service, rootDirectory, codepath = 'build', name } = startOptions;
|
|
@@ -86,12 +88,12 @@ export async function startApp<
|
|
|
86
88
|
sourceDirectory: path.join(rootDirectory, codepath),
|
|
87
89
|
});
|
|
88
90
|
|
|
89
|
-
const logging = config.get('logging')
|
|
91
|
+
const logging = config.get('logging');
|
|
90
92
|
logger.level = logging?.level || 'info';
|
|
91
93
|
|
|
92
94
|
// Concentrate the Typescript ugliness...
|
|
93
95
|
const app = express() as unknown as ServiceExpress<SLocals>;
|
|
94
|
-
const routing = config.get('routing')
|
|
96
|
+
const routing = config.get('routing');
|
|
95
97
|
|
|
96
98
|
app.disable('x-powered-by');
|
|
97
99
|
if (routing?.etag !== true) {
|
|
@@ -241,11 +243,13 @@ export async function startApp<
|
|
|
241
243
|
}
|
|
242
244
|
|
|
243
245
|
export type StartAppFn<
|
|
244
|
-
SLocals extends
|
|
246
|
+
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
245
247
|
RLocals extends RequestLocals = RequestLocals,
|
|
246
248
|
> = typeof startApp<SLocals, RLocals>;
|
|
247
249
|
|
|
248
|
-
export async function shutdownApp
|
|
250
|
+
export async function shutdownApp<
|
|
251
|
+
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
252
|
+
>(app: ServiceExpress<SLocals>) {
|
|
249
253
|
const { logger } = app.locals;
|
|
250
254
|
try {
|
|
251
255
|
await app.locals.service.stop?.(app);
|
|
@@ -256,10 +260,10 @@ export async function shutdownApp(app: ServiceExpress) {
|
|
|
256
260
|
(logger as pino.Logger).flush?.();
|
|
257
261
|
}
|
|
258
262
|
|
|
259
|
-
function httpServer<
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
) {
|
|
263
|
+
function httpServer<
|
|
264
|
+
Config extends ConfigurationSchema = ConfigurationSchema,
|
|
265
|
+
SLocals extends ServiceLocals<Config> = ServiceLocals<Config>,
|
|
266
|
+
>(app: ServiceExpress<SLocals>, config: ConfigurationSchema['server']) {
|
|
263
267
|
if (!config.certificate) {
|
|
264
268
|
return http.createServer(app);
|
|
265
269
|
}
|
|
@@ -280,11 +284,14 @@ function url(config: ConfigurationSchema['server'], port: number) {
|
|
|
280
284
|
return `http://${config.hostname}${port === 80 ? '' : `:${port}`}`;
|
|
281
285
|
}
|
|
282
286
|
|
|
283
|
-
export async function listen<SLocals extends
|
|
287
|
+
export async function listen<SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>>(
|
|
284
288
|
app: ServiceExpress<SLocals>,
|
|
285
289
|
shutdownHandler?: () => Promise<void>,
|
|
286
290
|
) {
|
|
287
|
-
|
|
291
|
+
// TODO I don't know why this is necessary, but TS can't quite figure this out
|
|
292
|
+
// otherwise.
|
|
293
|
+
const typedConfig = app.locals.config as unknown as Confit<ConfigurationSchema>;
|
|
294
|
+
const config = typedConfig.get('server') as Required<ConfigurationSchema['server']>;
|
|
288
295
|
const { port } = config;
|
|
289
296
|
|
|
290
297
|
const { service, logger } = app.locals;
|
|
@@ -341,7 +348,7 @@ export async function listen<SLocals extends ServiceLocals = ServiceLocals>(
|
|
|
341
348
|
const { locals } = app;
|
|
342
349
|
locals.logger.info({ url: url(config, port), service: locals.name }, 'express listening');
|
|
343
350
|
|
|
344
|
-
const serverConfig =
|
|
351
|
+
const serverConfig = typedConfig.get('server');
|
|
345
352
|
// Ok now start the internal port if we have one.
|
|
346
353
|
if (serverConfig?.internalPort || serverConfig?.internalPort === 0) {
|
|
347
354
|
startInternalApp(app, serverConfig.internalPort)
|
|
@@ -372,4 +379,5 @@ export async function listen<SLocals extends ServiceLocals = ServiceLocals>(
|
|
|
372
379
|
return server;
|
|
373
380
|
}
|
|
374
381
|
|
|
375
|
-
export type ListenFn<SLocals extends
|
|
382
|
+
export type ListenFn<SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>> =
|
|
383
|
+
typeof listen<SLocals>;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
2
|
import type { Application } from 'express-serve-static-core';
|
|
3
3
|
|
|
4
|
-
import { InternalLocals, ServiceExpress } from '../types';
|
|
4
|
+
import { AnyServiceLocals, InternalLocals, ServiceExpress, ServiceLocals } from '../types';
|
|
5
5
|
import { findPort } from '../development/port-finder';
|
|
6
|
+
import { ConfigurationSchema } from '../config/schema';
|
|
6
7
|
|
|
7
|
-
export async function startInternalApp
|
|
8
|
-
|
|
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>>;
|
|
9
12
|
app.locals.mainApp = mainApp;
|
|
10
13
|
|
|
11
14
|
const finalPort = port === 0 ? await findPort(3001) : port;
|
|
@@ -2,11 +2,14 @@ import path from 'path';
|
|
|
2
2
|
|
|
3
3
|
import express from 'express';
|
|
4
4
|
|
|
5
|
-
import type { ServiceExpress } from '../types';
|
|
5
|
+
import type { AnyServiceLocals, ServiceExpress, ServiceLocals } from '../types';
|
|
6
|
+
import { ConfigurationSchema } from '../config/schema';
|
|
6
7
|
|
|
7
8
|
import { getFilesInDir, loadModule } from './modules';
|
|
8
9
|
|
|
9
|
-
export async function loadRoutes
|
|
10
|
+
export async function loadRoutes<
|
|
11
|
+
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
12
|
+
>(app: ServiceExpress<SLocals>, routingDir: string, pattern: string) {
|
|
10
13
|
const files = await getFilesInDir(pattern, routingDir);
|
|
11
14
|
|
|
12
15
|
await Promise.all(
|
package/src/express-app/types.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { NextFunction, Response } from 'express';
|
|
2
2
|
|
|
3
|
-
import type { RequestLocals, RequestWithApp, ServiceLocals } from '../types';
|
|
3
|
+
import type { AnyServiceLocals, RequestLocals, RequestWithApp, ServiceLocals } from '../types';
|
|
4
|
+
import { ConfigurationSchema } from '../config/schema';
|
|
4
5
|
|
|
5
6
|
export type ServiceHandler<
|
|
6
|
-
SLocals extends
|
|
7
|
+
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
7
8
|
RLocals extends RequestLocals = RequestLocals,
|
|
8
9
|
ResBody = unknown,
|
|
9
10
|
RetType = unknown,
|
|
@@ -16,7 +17,7 @@ export type ServiceHandler<
|
|
|
16
17
|
// Make it easier to declare route files. This is not an exhaustive list
|
|
17
18
|
// of supported router methods, but it has the most common ones.
|
|
18
19
|
export interface ServiceRouter<
|
|
19
|
-
SLocals extends
|
|
20
|
+
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
20
21
|
RLocals extends RequestLocals = RequestLocals,
|
|
21
22
|
> {
|
|
22
23
|
all(path: string, ...handlers: ServiceHandler<SLocals, RLocals>[]): void;
|
package/src/hook.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ConfigurationSchema } from './config/schema';
|
|
2
|
+
import type { AnyServiceLocals, RequestLocals, Service, ServiceLocals } from './types';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Your service should call this function and then "inherit"
|
|
@@ -19,10 +20,10 @@ import type { RequestLocals, Service, ServiceLocals } from './types';
|
|
|
19
20
|
* }
|
|
20
21
|
* }
|
|
21
22
|
*
|
|
22
|
-
* @returns Service<SLocals, RLocals>
|
|
23
|
+
* @returns Service<Config, SLocals, RLocals>
|
|
23
24
|
*/
|
|
24
25
|
export function useService<
|
|
25
|
-
SLocals extends
|
|
26
|
+
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
26
27
|
RLocals extends RequestLocals = RequestLocals,
|
|
27
28
|
>(baseService?: Service<SLocals, RLocals>): Service<SLocals, RLocals> {
|
|
28
29
|
return {
|
package/src/index.ts
CHANGED
package/src/openapi.ts
CHANGED
|
@@ -4,8 +4,9 @@ import _ from 'lodash';
|
|
|
4
4
|
import * as OpenApiValidator from 'express-openapi-validator';
|
|
5
5
|
import type { Handler } from 'express';
|
|
6
6
|
|
|
7
|
-
import type { ServiceExpress } from './types';
|
|
7
|
+
import type { AnyServiceLocals, ServiceExpress, ServiceLocals } from './types';
|
|
8
8
|
import { getFilesInDir, loadModule } from './express-app/modules';
|
|
9
|
+
import { ConfigurationSchema } from './config/schema';
|
|
9
10
|
|
|
10
11
|
const notImplementedHandler: Handler = (req, res) => {
|
|
11
12
|
res.status(501).json({
|
|
@@ -21,8 +22,10 @@ function stripExtension(filename: string) {
|
|
|
21
22
|
return filename.slice(0, filename.lastIndexOf('.'));
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
export async function openApi
|
|
25
|
-
|
|
25
|
+
export async function openApi<
|
|
26
|
+
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
27
|
+
>(
|
|
28
|
+
app: ServiceExpress<SLocals>,
|
|
26
29
|
rootDirectory: string,
|
|
27
30
|
codepath: string,
|
|
28
31
|
pattern: string,
|
package/src/telemetry/index.ts
CHANGED
|
@@ -4,12 +4,14 @@ import * as opentelemetry from '@opentelemetry/sdk-node';
|
|
|
4
4
|
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';
|
|
5
5
|
|
|
6
6
|
import type {
|
|
7
|
+
AnyServiceLocals,
|
|
7
8
|
DelayLoadServiceStartOptions,
|
|
8
9
|
RequestLocals,
|
|
9
10
|
ServiceLocals,
|
|
10
11
|
ServiceStartOptions,
|
|
11
12
|
} from '../types';
|
|
12
13
|
import type { ListenFn, StartAppFn } from '../express-app/index';
|
|
14
|
+
import type { ConfigurationSchema } from '../config/schema';
|
|
13
15
|
|
|
14
16
|
import { getAutoInstrumentations, getResourceDetectors } from './instrumentations';
|
|
15
17
|
import { DummySpanExporter } from './DummyExporter';
|
|
@@ -70,7 +72,7 @@ export async function shutdownGlobalTelemetry() {
|
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
export async function startWithTelemetry<
|
|
73
|
-
SLocals extends
|
|
75
|
+
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
74
76
|
RLocals extends RequestLocals = RequestLocals,
|
|
75
77
|
>(options: DelayLoadServiceStartOptions) {
|
|
76
78
|
startGlobalTelemetry(options.name);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { RequestHandler, Request, Response, ErrorRequestHandler } from 'express';
|
|
2
2
|
|
|
3
3
|
import { ServiceError } from '../error';
|
|
4
|
-
import type { RequestWithApp, ServiceExpress, ServiceLocals } from '../types';
|
|
4
|
+
import type { AnyServiceLocals, RequestWithApp, ServiceExpress, ServiceLocals } from '../types';
|
|
5
5
|
import type { ServiceHandler } from '../express-app/types';
|
|
6
|
+
import { ConfigurationSchema } from '../config/schema';
|
|
6
7
|
|
|
7
8
|
const LOG_PREFS = Symbol('Logging information');
|
|
8
9
|
|
|
@@ -38,7 +39,7 @@ function getBasicInfo(req: Request) {
|
|
|
38
39
|
return preInfo;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
function finishLog<SLocals extends
|
|
42
|
+
function finishLog<SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>>(
|
|
42
43
|
app: ServiceExpress<SLocals>,
|
|
43
44
|
error: Error | undefined,
|
|
44
45
|
req: Request,
|
|
@@ -92,11 +93,9 @@ function finishLog<SLocals extends ServiceLocals = ServiceLocals>(
|
|
|
92
93
|
logger.info(endLog, 'req');
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
export function loggerMiddleware<
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
logResponses?: boolean,
|
|
99
|
-
): RequestHandler {
|
|
96
|
+
export function loggerMiddleware<
|
|
97
|
+
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
98
|
+
>(app: ServiceExpress<SLocals>, logRequests?: boolean, logResponses?: boolean): RequestHandler {
|
|
100
99
|
const { logger, service } = app.locals;
|
|
101
100
|
return function gblogger(req, res, next) {
|
|
102
101
|
const prefs: LogPrefs = {
|
|
@@ -142,11 +141,9 @@ export function loggerMiddleware<SLocals extends ServiceLocals = ServiceLocals>(
|
|
|
142
141
|
};
|
|
143
142
|
}
|
|
144
143
|
|
|
145
|
-
export function errorHandlerMiddleware<
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
returnError?: boolean,
|
|
149
|
-
) {
|
|
144
|
+
export function errorHandlerMiddleware<
|
|
145
|
+
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
146
|
+
>(app: ServiceExpress<SLocals>, unnest?: boolean, returnError?: boolean) {
|
|
150
147
|
const gbErrorHandler: ErrorRequestHandler = (error, req, res, next) => {
|
|
151
148
|
let loggable: Partial<ServiceError> = error;
|
|
152
149
|
const body = error.response?.body || error.body;
|
package/src/types.ts
CHANGED
|
@@ -10,9 +10,11 @@ import { Confit } from '@sesamecare-oss/confit';
|
|
|
10
10
|
|
|
11
11
|
import { ConfigurationSchema } from './config/schema';
|
|
12
12
|
|
|
13
|
-
export interface InternalLocals
|
|
13
|
+
export interface InternalLocals<
|
|
14
|
+
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
15
|
+
> extends Record<string, unknown> {
|
|
14
16
|
server?: Server;
|
|
15
|
-
mainApp: ServiceExpress
|
|
17
|
+
mainApp: ServiceExpress<SLocals>;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
export type ServiceLogger = pino.BaseLogger & Pick<pino.Logger, 'isLevelEnabled'>;
|
|
@@ -26,7 +28,7 @@ export interface ServiceLocals<Config extends ConfigurationSchema = Configuratio
|
|
|
26
28
|
logger: ServiceLogger;
|
|
27
29
|
config: Confit<Config>;
|
|
28
30
|
meter: Meter;
|
|
29
|
-
internalApp: Application<InternalLocals
|
|
31
|
+
internalApp: Application<InternalLocals<this>>;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
export interface RequestLocals {
|
|
@@ -36,10 +38,14 @@ export interface RequestLocals {
|
|
|
36
38
|
logger: ServiceLogger;
|
|
37
39
|
}
|
|
38
40
|
|
|
39
|
-
export type ServiceExpress<Locals extends
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
export type ServiceExpress<Locals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>> =
|
|
42
|
+
Application<Locals>;
|
|
43
|
+
|
|
44
|
+
export type RequestWithApp<Locals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>> =
|
|
45
|
+
Omit<Request, 'app'> & {
|
|
46
|
+
app: Application<Locals>;
|
|
47
|
+
};
|
|
48
|
+
|
|
43
49
|
export type ResponseFromApp<
|
|
44
50
|
ResBody = unknown,
|
|
45
51
|
RLocals extends RequestLocals = RequestLocals,
|
|
@@ -49,7 +55,7 @@ export type ResponseFromApp<
|
|
|
49
55
|
* This is the core type you need to implement to provide a service
|
|
50
56
|
*/
|
|
51
57
|
export interface Service<
|
|
52
|
-
SLocals extends
|
|
58
|
+
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
53
59
|
RLocals extends RequestLocals = RequestLocals,
|
|
54
60
|
> {
|
|
55
61
|
name?: string;
|
|
@@ -97,12 +103,12 @@ export interface Service<
|
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
export type ServiceFactory<
|
|
100
|
-
SLocals extends
|
|
106
|
+
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
101
107
|
RLocals extends RequestLocals = RequestLocals,
|
|
102
108
|
> = () => Service<SLocals, RLocals>;
|
|
103
109
|
|
|
104
110
|
export interface ServiceStartOptions<
|
|
105
|
-
SLocals extends
|
|
111
|
+
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
106
112
|
RLocals extends RequestLocals = RequestLocals,
|
|
107
113
|
> {
|
|
108
114
|
name: string;
|
|
@@ -134,7 +140,9 @@ export interface ServiceOptions {
|
|
|
134
140
|
openApiOptions?: Parameters<typeof middleware>[0];
|
|
135
141
|
}
|
|
136
142
|
|
|
137
|
-
export interface ServiceLike<
|
|
143
|
+
export interface ServiceLike<
|
|
144
|
+
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
145
|
+
> {
|
|
138
146
|
locals: SLocals;
|
|
139
147
|
}
|
|
140
148
|
|
|
@@ -147,7 +155,7 @@ export interface ServiceLike<SLocals extends ServiceLocals = ServiceLocals> {
|
|
|
147
155
|
* logger.
|
|
148
156
|
*/
|
|
149
157
|
export interface RequestLike<
|
|
150
|
-
SLocals extends
|
|
158
|
+
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
151
159
|
RLocals extends RequestLocals = RequestLocals,
|
|
152
160
|
> {
|
|
153
161
|
app: ServiceLike<SLocals>;
|
|
@@ -161,7 +169,7 @@ export interface RequestLike<
|
|
|
161
169
|
// Typically you should export an interface that extends this one
|
|
162
170
|
// and then access all your types through that.
|
|
163
171
|
export interface ServiceTypes<
|
|
164
|
-
SLocals extends
|
|
172
|
+
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
|
|
165
173
|
RLocals extends RequestLocals = RequestLocals,
|
|
166
174
|
ResBody = unknown,
|
|
167
175
|
> {
|
|
@@ -178,3 +186,14 @@ export interface ServiceTypes<
|
|
|
178
186
|
ServiceFactory: ServiceFactory<SLocals, RLocals>;
|
|
179
187
|
ServiceLocals: SLocals;
|
|
180
188
|
}
|
|
189
|
+
|
|
190
|
+
export type UnwrapServiceConfig<SLocals extends ServiceLocals> = SLocals extends ServiceLocals<
|
|
191
|
+
infer C
|
|
192
|
+
>
|
|
193
|
+
? C
|
|
194
|
+
: never;
|
|
195
|
+
|
|
196
|
+
// TODO this allows us to clean up the generics by having a loose parameter
|
|
197
|
+
// but using the UnwrapServiceConfig to get the specific type back
|
|
198
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
199
|
+
export type AnyServiceLocals = ServiceLocals<any>;
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { FetchConfig, RestApiResponse } from 'rest-api-support';
|
|
2
|
-
import { ServiceErrorSpec } from '../error';
|
|
3
|
-
import type { ServiceExpress, ServiceLike, ServiceLocals } from '../types';
|
|
4
|
-
/**
|
|
5
|
-
* Return a factory that will make instances of an OpenAPI/Swagger client for each request
|
|
6
|
-
*/
|
|
7
|
-
export declare function createServiceInterface<ServiceType>(service: ServiceExpress, name: string, Implementation: {
|
|
8
|
-
new (c: FetchConfig): ServiceType;
|
|
9
|
-
}): ServiceType;
|
|
10
|
-
interface SpecWithMessage extends ServiceErrorSpec {
|
|
11
|
-
message?: string;
|
|
12
|
-
}
|
|
13
|
-
export declare function throwOrGetResponse<SLocals extends ServiceLocals, AppType extends ServiceLike<SLocals>, ResType extends RestApiResponse<number, SuccessResponseType>, SuccessResponseType>(app: AppType, exec: () => Promise<ResType>, errorSpec?: SpecWithMessage): Promise<Extract<ResType, {
|
|
14
|
-
responseType: 'response';
|
|
15
|
-
}>>;
|
|
16
|
-
export {};
|