@openapi-typescript-infra/service 2.0.1 → 2.1.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.
@@ -1,11 +1,15 @@
1
1
  {
2
+ "$schema": "tsschema://../src/config/schema#ConfigurationSchema",
2
3
  "logging": {
3
4
  "level": "debug"
4
5
  },
5
- "port": 0,
6
+ "server": {
7
+ "port": 0,
8
+ "internalPort": 0
9
+ },
6
10
  "connections": {
7
11
  "default": {
8
12
  "proxy": "http://localhost:9990"
9
13
  }
10
14
  }
11
- }
15
+ }
package/config/test.json CHANGED
@@ -1,5 +1,6 @@
1
1
  {
2
+ "$schema": "tsschema://../src/config/schema#ConfigurationSchema",
2
3
  "logging": {
3
4
  "level": "warn"
4
5
  }
5
- }
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openapi-typescript-infra/service",
3
- "version": "2.0.1",
3
+ "version": "2.1.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
  "main": "build/index.js",
6
6
  "scripts": {
@@ -67,7 +67,7 @@
67
67
  "@opentelemetry/exporter-trace-otlp-proto": "^0.41.2",
68
68
  "@opentelemetry/instrumentation": "^0.41.2",
69
69
  "@opentelemetry/instrumentation-aws-sdk": "^0.36.0",
70
- "@opentelemetry/instrumentation-dns": "^0.32.1",
70
+ "@opentelemetry/instrumentation-dns": "^0.32.2",
71
71
  "@opentelemetry/instrumentation-express": "^0.33.1",
72
72
  "@opentelemetry/instrumentation-generic-pool": "^0.32.2",
73
73
  "@opentelemetry/instrumentation-graphql": "^0.35.1",
@@ -83,7 +83,7 @@
83
83
  "dotenv": "^16.3.1",
84
84
  "eventsource": "^1.1.2",
85
85
  "express": "next",
86
- "express-openapi-validator": "^5.0.4",
86
+ "express-openapi-validator": "^5.0.6",
87
87
  "glob": "^8.1.0",
88
88
  "lodash": "^4.17.21",
89
89
  "minimist": "^1.2.8",
@@ -97,30 +97,33 @@
97
97
  "devDependencies": {
98
98
  "@commitlint/cli": "^17.7.1",
99
99
  "@commitlint/config-conventional": "^17.7.0",
100
- "@openapi-typescript-infra/coconfig": "^4.0.0",
100
+ "@openapi-typescript-infra/coconfig": "^4.1.0",
101
101
  "@semantic-release/changelog": "^6.0.3",
102
- "@semantic-release/commit-analyzer": "^10.0.1",
102
+ "@semantic-release/commit-analyzer": "^10.0.4",
103
103
  "@semantic-release/exec": "^6.0.3",
104
104
  "@semantic-release/git": "^10.0.1",
105
- "@semantic-release/release-notes-generator": "^11.0.4",
105
+ "@semantic-release/release-notes-generator": "^11.0.7",
106
106
  "@types/cookie-parser": "^1.4.3",
107
107
  "@types/eventsource": "1.1.11",
108
108
  "@types/express": "^4.17.17",
109
109
  "@types/glob": "^8.1.0",
110
110
  "@types/lodash": "^4.14.197",
111
111
  "@types/minimist": "^1.2.2",
112
- "@types/node": "^18.17.5",
112
+ "@types/node": "^18.17.12",
113
113
  "@types/supertest": "^2.0.12",
114
+ "@typescript-eslint/eslint-plugin": "^6.5.0",
115
+ "@typescript-eslint/parser": "^6.5.0",
114
116
  "coconfig": "^0.13.3",
115
- "eslint": "^8.47.0",
116
- "eslint-config-gasbuddy": "^7.2.0",
117
+ "eslint": "^8.48.0",
117
118
  "eslint-config-prettier": "^9.0.0",
119
+ "eslint-plugin-import": "^2.28.1",
118
120
  "pino-pretty": "^10.2.0",
119
121
  "pinst": "^3.0.0",
120
122
  "supertest": "^6.3.3",
121
123
  "ts-node": "^10.9.1",
122
- "typescript": "^5.1.6",
123
- "vitest": "^0.34.2"
124
+ "tsconfig-paths": "^4.2.0",
125
+ "typescript": "^5.2.2",
126
+ "vitest": "^0.34.3"
124
127
  },
125
128
  "packageManager": "yarn@3.2.3"
126
129
  }
package/src/bootstrap.ts CHANGED
@@ -79,6 +79,11 @@ export async function bootstrap<
79
79
  // eslint-disable-next-line import/no-extraneous-dependencies
80
80
  const { register } = await import('ts-node');
81
81
  register();
82
+ try {
83
+ require('tsconfig-paths/register');
84
+ } catch (error) {
85
+ // No action needed
86
+ }
82
87
  if (main) {
83
88
  entrypoint = main.replace(/^(\.?\/?)(build|dist)\//, '$1src/').replace(/\.js$/, '.ts');
84
89
  } else {
@@ -19,7 +19,7 @@ export interface ConfigurationSchema extends Record<string, unknown> {
19
19
  logHttpRequests?: boolean;
20
20
  logRequestBody?: boolean;
21
21
  logResponseBody?: boolean;
22
- },
22
+ };
23
23
  routing?: {
24
24
  openapi?: boolean;
25
25
  // Relative to the *root directory* of the app
@@ -34,14 +34,14 @@ export interface ConfigurationSchema extends Record<string, unknown> {
34
34
  bodyParsers?: {
35
35
  json?: boolean;
36
36
  form?: boolean;
37
- },
37
+ };
38
38
  // Set static.enabled to true to enable static assets to be served
39
39
  static?: ConfigurationItemEnabled & {
40
40
  // The path relative to the root directory of the app
41
41
  path?: string;
42
42
  // The path on which to mount the static assets (defaults to /)
43
43
  mountPath?: string;
44
- },
44
+ };
45
45
  finalHandlers: {
46
46
  // Whether to create and return errors for unhandled routes
47
47
  notFound?: boolean;
@@ -55,12 +55,18 @@ export interface ConfigurationSchema extends Record<string, unknown> {
55
55
  // information.
56
56
  unnest: boolean;
57
57
  };
58
- },
59
- },
60
- server?: {
61
- internalPort?: number,
62
- port?: number,
63
- metrics: ConfigurationItemEnabled,
64
- },
58
+ };
59
+ };
60
+ server: {
61
+ internalPort?: number;
62
+ port?: number;
63
+ metrics: ConfigurationItemEnabled;
64
+ // To enable HTTPS on the main service, set the key and cert to the
65
+ // actual key material (not the path). Use shortstop file: handler.
66
+ // Note that generally it's better to offload tls termination,
67
+ // but this is useful for dev.
68
+ key?: string;
69
+ certificate?: string;
70
+ };
65
71
  connections: Record<string, ServiceConfiguration>;
66
72
  }
@@ -1,5 +1,6 @@
1
1
  import assert from 'assert';
2
2
  import http from 'http';
3
+ import https from 'https';
3
4
  import path from 'path';
4
5
 
5
6
  import express from 'express';
@@ -303,18 +304,32 @@ export async function shutdownApp(app: ServiceExpress) {
303
304
  (logger as pino.Logger).flush?.();
304
305
  }
305
306
 
307
+ function tlsServer<SLocals extends ServiceLocals = ServiceLocals>(
308
+ app: ServiceExpress<SLocals>,
309
+ config: ConfigurationSchema['server'],
310
+ ) {
311
+ return https.createServer(
312
+ {
313
+ key: config.key,
314
+ cert: config.certificate,
315
+ },
316
+ app,
317
+ );
318
+ }
319
+
306
320
  export async function listen<SLocals extends ServiceLocals = ServiceLocals>(
307
321
  app: ServiceExpress<SLocals>,
308
322
  shutdownHandler?: () => Promise<void>,
309
323
  ) {
310
- let port = app.locals.config.get('server:port');
324
+ const config = app.locals.config.get('server') as Required<ConfigurationSchema['server']>;
325
+ let { port } = config;
311
326
 
312
327
  if (port === 0) {
313
- port = await findPort(8001);
328
+ port = (await findPort(8001)) as number;
314
329
  }
315
330
 
316
331
  const { service, logger } = app.locals;
317
- const server = http.createServer(app);
332
+ const server = config.certificate ? tlsServer(app, config) : http.createServer(app);
318
333
  let shutdownInProgress = false;
319
334
  createTerminus(server, {
320
335
  timeout: 15000,
@@ -358,6 +373,10 @@ export async function listen<SLocals extends ServiceLocals = ServiceLocals>(
358
373
  }
359
374
  });
360
375
 
376
+ server.on('error', (error) => {
377
+ logger.error(error, 'Main service listener error');
378
+ });
379
+
361
380
  const metricInfo = (app.locals as AppWithMetrics)[METRICS_KEY] as InternalMetricsInfo;
362
381
  delete (app.locals as AppWithMetrics)[METRICS_KEY];
363
382
 
@@ -2,11 +2,14 @@ import express from 'express';
2
2
  import type { Application } from 'express-serve-static-core';
3
3
 
4
4
  import { InternalLocals, ServiceExpress } from '../types';
5
+ import { findPort } from '../development/port-finder';
5
6
 
6
7
  export async function startInternalApp(mainApp: ServiceExpress, port: number) {
7
8
  const app = express() as unknown as Application<InternalLocals>;
8
9
  app.locals.mainApp = mainApp;
9
10
 
11
+ const finalPort = port === 0 ? await findPort(3001) : port;
12
+
10
13
  app.get('/health', async (req, res) => {
11
14
  if (mainApp.locals.service?.healthy) {
12
15
  try {
@@ -21,9 +24,12 @@ export async function startInternalApp(mainApp: ServiceExpress, port: number) {
21
24
  });
22
25
 
23
26
  const listenPromise = new Promise<void>((accept) => {
24
- app.locals.server = app.listen(port, () => {
27
+ app.locals.server = app.listen(finalPort, () => {
25
28
  accept();
26
29
  });
30
+ app.locals.server.on('error', (error) => {
31
+ mainApp.locals.logger.error(error, 'Internal app server error');
32
+ });
27
33
  });
28
34
 
29
35
  await listenPromise;
package/tsconfig.json CHANGED
@@ -20,7 +20,7 @@
20
20
  "ES2022",
21
21
  "DOM"
22
22
  ],
23
- "module": "CommonJS",
23
+ "module": "NodeNext",
24
24
  "moduleResolution": "NodeNext",
25
25
  "target": "ES2022",
26
26
  "declaration": true,