@openapi-typescript-infra/service 5.13.0 → 5.14.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/types.d.ts CHANGED
@@ -5,6 +5,7 @@ import type { Request, Response } from 'express';
5
5
  import type { Application } from 'express-serve-static-core';
6
6
  import type { middleware } from 'express-openapi-validator';
7
7
  import type { Meter } from '@opentelemetry/api';
8
+ import type { NodeSDKConfiguration } from '@opentelemetry/sdk-node';
8
9
  import { ShortstopHandler } from '@sesamecare-oss/confit';
9
10
  import { ConfigurationSchema } from './config/schema.js';
10
11
  export interface InternalLocals<SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>> extends Record<string, unknown> {
@@ -42,7 +43,10 @@ export interface Service<SLocals extends AnyServiceLocals = ServiceLocals<Config
42
43
  configure?: (startOptions: ServiceStartOptions<SLocals, RLocals>, options: ServiceOptions) => ServiceOptions;
43
44
  attach?: (app: ServiceExpress<SLocals>) => void | Promise<void>;
44
45
  attachServer?: (app: ServiceExpress<SLocals>, server: Server) => void | Promise<void>;
45
- onListening?: (app: ServiceExpress<SLocals>, port: number) => void | Promise<void>;
46
+ onListening?: (app: ServiceExpress<SLocals>, info: {
47
+ port: number;
48
+ protocol: 'http' | 'https';
49
+ }) => void | Promise<void>;
46
50
  start(app: ServiceExpress<SLocals>): void | Promise<void>;
47
51
  stop?: (app: ServiceExpress<SLocals>) => void | Promise<void>;
48
52
  healthy?: (app: ServiceExpress<SLocals>) => boolean | Promise<boolean>;
@@ -62,6 +66,7 @@ export interface ServiceStartOptions<SLocals extends AnyServiceLocals = ServiceL
62
66
  }
63
67
  export interface DelayLoadServiceStartOptions extends Omit<ServiceStartOptions, 'service'> {
64
68
  service: string;
69
+ customizer?: ((options: Partial<NodeSDKConfiguration>) => Partial<NodeSDKConfiguration>) | undefined;
65
70
  }
66
71
  export interface ServiceOptions {
67
72
  codepath?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openapi-typescript-infra/service",
3
- "version": "5.13.0",
3
+ "version": "5.14.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
  ".": {
package/src/bootstrap.ts CHANGED
@@ -4,6 +4,7 @@ import assert from 'node:assert';
4
4
  import { config } from 'dotenv';
5
5
  import { readPackageUp } from 'read-package-up';
6
6
  import type { NormalizedPackageJson } from 'read-package-up';
7
+ import { NodeSDKConfiguration } from '@opentelemetry/sdk-node';
7
8
 
8
9
  import type {
9
10
  AnyServiceLocals,
@@ -64,6 +65,7 @@ async function getServiceDetails(argv: BootstrapArguments = {}) {
64
65
  rootDirectory: path.dirname(pkg.path),
65
66
  name: parts[parts.length - 1],
66
67
  version: pkg.packageJson.version,
68
+ customizer: (pkg.packageJson.config?.telemetry as { customizer?: string })?.customizer,
67
69
  };
68
70
  }
69
71
 
@@ -81,7 +83,7 @@ export async function bootstrap<
81
83
  SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
82
84
  RLocals extends RequestLocals = RequestLocals,
83
85
  >(argv?: BootstrapArguments) {
84
- const { main, rootDirectory, name, version } = await getServiceDetails(argv);
86
+ const { main, rootDirectory, name, version, customizer } = await getServiceDetails(argv);
85
87
 
86
88
  let entrypoint: string;
87
89
  let codepath: 'build' | 'dist' | 'src' = 'build';
@@ -104,12 +106,23 @@ export async function bootstrap<
104
106
 
105
107
  const absoluteEntrypoint = path.resolve(rootDirectory, entrypoint);
106
108
  if (argv?.telemetry) {
109
+ let otelCustomizer:
110
+ | ((options: Partial<NodeSDKConfiguration>) => Partial<NodeSDKConfiguration>)
111
+ | undefined = undefined;
112
+ if (customizer) {
113
+ // Customize OTEL with a dynamic import based on the codePath (so put it in src, generally)
114
+ otelCustomizer = (await import(`$(codePath}/${customizer}`)).NodeSDKConfiguration;
115
+ if (typeof otelCustomizer === 'object') {
116
+ otelCustomizer = (v) => ({ ...v, ...(otelCustomizer as Partial<NodeSDKConfiguration>) });
117
+ }
118
+ }
107
119
  return startWithTelemetry<SLocals, RLocals>({
108
120
  name,
109
121
  rootDirectory,
110
122
  service: absoluteEntrypoint,
111
123
  codepath,
112
124
  version,
125
+ customizer: otelCustomizer,
113
126
  });
114
127
  }
115
128
 
@@ -429,7 +429,7 @@ export async function listen<SLocals extends AnyServiceLocals = ServiceLocals<Co
429
429
  });
430
430
 
431
431
  await listenPromise;
432
- await service.onListening?.(app, port);
432
+ await service.onListening?.(app, { port, protocol: config.certificate ? 'https' : 'http' });
433
433
  return server;
434
434
  }
435
435
 
@@ -72,7 +72,14 @@ let telemetrySdk: opentelemetry.NodeSDK | undefined;
72
72
  * In addition, since we have to load it right away before configuration
73
73
  * is available, we can't use configuration to decide anything.
74
74
  */
75
- export async function startGlobalTelemetry(serviceName: string) {
75
+ export async function startGlobalTelemetry(
76
+ serviceName: string,
77
+ customizer?:
78
+ | ((
79
+ options: Partial<opentelemetry.NodeSDKConfiguration>,
80
+ ) => Partial<opentelemetry.NodeSDKConfiguration>)
81
+ | undefined,
82
+ ) {
76
83
  if (!prometheusExporter) {
77
84
  const { metrics, logs, NodeSDK } = opentelemetry;
78
85
 
@@ -90,7 +97,7 @@ export async function startGlobalTelemetry(serviceName: string) {
90
97
  prometheusExporter = new PrometheusExporter({ preventServerStart: true });
91
98
  const instrumentations = getAutoInstrumentations();
92
99
  const logExporter = getLogExporter();
93
- telemetrySdk = new NodeSDK({
100
+ const options: Partial<opentelemetry.NodeSDKConfiguration> = {
94
101
  serviceName,
95
102
  autoDetectResources: false,
96
103
  resource,
@@ -111,7 +118,8 @@ export async function startGlobalTelemetry(serviceName: string) {
111
118
  },
112
119
  },
113
120
  ],
114
- });
121
+ };
122
+ telemetrySdk = new NodeSDK(customizer ? customizer(options) : options);
115
123
  telemetrySdk.start();
116
124
  }
117
125
  }
@@ -130,7 +138,7 @@ export async function startWithTelemetry<
130
138
  SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
131
139
  RLocals extends RequestLocals = RequestLocals,
132
140
  >(options: DelayLoadServiceStartOptions) {
133
- await startGlobalTelemetry(options.name);
141
+ await startGlobalTelemetry(options.name, options.customizer);
134
142
 
135
143
  // eslint-disable-next-line import/no-unresolved, @typescript-eslint/no-var-requires
136
144
  const { startApp, listen } = (await import('../express-app/app.js')) as {
package/src/types.ts CHANGED
@@ -6,6 +6,7 @@ import type { Request, Response } from 'express';
6
6
  import type { Application } from 'express-serve-static-core';
7
7
  import type { middleware } from 'express-openapi-validator';
8
8
  import type { Meter } from '@opentelemetry/api';
9
+ import type { NodeSDKConfiguration } from '@opentelemetry/sdk-node';
9
10
  import { ShortstopHandler } from '@sesamecare-oss/confit';
10
11
 
11
12
  import { ConfigurationSchema } from './config/schema.js';
@@ -79,7 +80,10 @@ export interface Service<
79
80
  // Called after a server is created but before the server starts listening
80
81
  attachServer?: (app: ServiceExpress<SLocals>, server: Server) => void | Promise<void>;
81
82
  // Called after the server is listening
82
- onListening?: (app: ServiceExpress<SLocals>, port: number) => void | Promise<void>;
83
+ onListening?: (
84
+ app: ServiceExpress<SLocals>,
85
+ info: { port: number; protocol: 'http' | 'https' },
86
+ ) => void | Promise<void>;
83
87
 
84
88
  start(app: ServiceExpress<SLocals>): void | Promise<void>;
85
89
 
@@ -141,6 +145,9 @@ export interface ServiceStartOptions<
141
145
 
142
146
  export interface DelayLoadServiceStartOptions extends Omit<ServiceStartOptions, 'service'> {
143
147
  service: string;
148
+ customizer?:
149
+ | ((options: Partial<NodeSDKConfiguration>) => Partial<NodeSDKConfiguration>)
150
+ | undefined;
144
151
  }
145
152
 
146
153
  // Handled by service.configure