@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.
Files changed (47) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/build/bootstrap.d.ts +3 -2
  3. package/build/bootstrap.js.map +1 -1
  4. package/build/development/repl.d.ts +3 -2
  5. package/build/development/repl.js.map +1 -1
  6. package/build/error.d.ts +4 -3
  7. package/build/error.js.map +1 -1
  8. package/build/express-app/app.d.ts +7 -6
  9. package/build/express-app/app.js +5 -2
  10. package/build/express-app/app.js.map +1 -1
  11. package/build/express-app/internal-server.d.ts +3 -2
  12. package/build/express-app/internal-server.js.map +1 -1
  13. package/build/express-app/route-loader.d.ts +3 -2
  14. package/build/express-app/route-loader.js.map +1 -1
  15. package/build/express-app/types.d.ts +4 -3
  16. package/build/hook.d.ts +4 -3
  17. package/build/hook.js +1 -1
  18. package/build/hook.js.map +1 -1
  19. package/build/index.d.ts +0 -1
  20. package/build/index.js +0 -1
  21. package/build/index.js.map +1 -1
  22. package/build/openapi.d.ts +3 -2
  23. package/build/openapi.js.map +1 -1
  24. package/build/telemetry/index.d.ts +3 -2
  25. package/build/telemetry/index.js.map +1 -1
  26. package/build/telemetry/requestLogger.d.ts +4 -3
  27. package/build/telemetry/requestLogger.js.map +1 -1
  28. package/build/tsconfig.build.tsbuildinfo +1 -1
  29. package/build/types.d.ts +13 -11
  30. package/package.json +4 -5
  31. package/src/bootstrap.ts +3 -2
  32. package/src/development/repl.ts +6 -2
  33. package/src/error.ts +6 -3
  34. package/src/express-app/app.ts +21 -13
  35. package/src/express-app/internal-server.ts +6 -3
  36. package/src/express-app/route-loader.ts +5 -2
  37. package/src/express-app/types.ts +4 -3
  38. package/src/hook.ts +4 -3
  39. package/src/index.ts +0 -1
  40. package/src/openapi.ts +6 -3
  41. package/src/telemetry/index.ts +3 -1
  42. package/src/telemetry/requestLogger.ts +9 -12
  43. package/src/types.ts +32 -13
  44. package/build/service-calls/index.d.ts +0 -16
  45. package/build/service-calls/index.js +0 -85
  46. package/build/service-calls/index.js.map +0 -1
  47. 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 ServiceLocals = ServiceLocals> = Application<Locals>;
31
- export type RequestWithApp<Locals extends ServiceLocals = ServiceLocals> = Omit<Request, 'app'> & {
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 ServiceLocals = ServiceLocals, RLocals extends RequestLocals = RequestLocals> {
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 ServiceLocals = ServiceLocals, RLocals extends RequestLocals = RequestLocals> = () => Service<SLocals, RLocals>;
51
- export interface ServiceStartOptions<SLocals extends ServiceLocals = ServiceLocals, RLocals extends RequestLocals = RequestLocals> {
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 ServiceLocals = ServiceLocals> {
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 ServiceLocals = ServiceLocals, RLocals extends RequestLocals = RequestLocals> {
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 ServiceLocals = ServiceLocals, RLocals extends RequestLocals = RequestLocals, ResBody = unknown> {
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.1",
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.6",
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.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 ServiceLocals = ServiceLocals,
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);
@@ -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(app: ServiceExpress, onExit: () => void) {
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 type { ServiceLike, ServiceLocals } from './types';
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 extends Error {
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<ServiceLocals>, message: string, spec?: ServiceErrorSpec) {
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);
@@ -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 ServiceLocals = ServiceLocals,
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') as ConfigurationSchema['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') as ConfigurationSchema['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 ServiceLocals = ServiceLocals,
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(app: ServiceExpress) {
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<SLocals extends ServiceLocals = ServiceLocals>(
260
- app: ServiceExpress<SLocals>,
261
- config: ConfigurationSchema['server'],
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 ServiceLocals = ServiceLocals>(
287
+ export async function listen<SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>>(
284
288
  app: ServiceExpress<SLocals>,
285
289
  shutdownHandler?: () => Promise<void>,
286
290
  ) {
287
- const config = app.locals.config.get('server') as Required<ConfigurationSchema['server']>;
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 = locals.config.get('server') as ConfigurationSchema['server'];
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 ServiceLocals = ServiceLocals> = typeof listen<SLocals>;
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(mainApp: ServiceExpress, port: number) {
8
- const app = express() as unknown as Application<InternalLocals>;
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(app: ServiceExpress, routingDir: string, pattern: string) {
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(
@@ -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 ServiceLocals = ServiceLocals,
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 ServiceLocals = ServiceLocals,
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 type { RequestLocals, Service, ServiceLocals } from './types';
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 ServiceLocals = ServiceLocals,
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
@@ -1,5 +1,4 @@
1
1
  export * from './telemetry';
2
- export * from './service-calls';
3
2
  export * from './express-app';
4
3
  export * from './types';
5
4
  export * from './env';
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
- app: ServiceExpress,
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,
@@ -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 ServiceLocals = ServiceLocals,
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 ServiceLocals = ServiceLocals>(
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<SLocals extends ServiceLocals = ServiceLocals>(
96
- app: ServiceExpress<SLocals>,
97
- logRequests?: boolean,
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<SLocals extends ServiceLocals = ServiceLocals>(
146
- app: ServiceExpress<SLocals>,
147
- unnest?: boolean,
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 extends Record<string, unknown> {
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 ServiceLocals = ServiceLocals> = Application<Locals>;
40
- export type RequestWithApp<Locals extends ServiceLocals = ServiceLocals> = Omit<Request, 'app'> & {
41
- app: Application<Locals>;
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 ServiceLocals = ServiceLocals,
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 ServiceLocals = ServiceLocals,
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 ServiceLocals = ServiceLocals,
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<SLocals extends ServiceLocals = ServiceLocals> {
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 ServiceLocals = ServiceLocals,
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 ServiceLocals = ServiceLocals,
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 {};