@openapi-typescript-infra/service 1.2.1 → 2.0.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.
Files changed (42) hide show
  1. package/.eslintignore +1 -0
  2. package/.eslintrc.js +1 -1
  3. package/.prettierrc.js +1 -1
  4. package/.trunk/configs/.markdownlint.yaml +10 -0
  5. package/.trunk/configs/.yamllint.yaml +10 -0
  6. package/.trunk/trunk.yaml +35 -0
  7. package/CHANGELOG.md +12 -0
  8. package/README.md +10 -10
  9. package/build/express-app/app.js +4 -3
  10. package/build/express-app/app.js.map +1 -1
  11. package/build/express-app/modules.d.ts +2 -0
  12. package/build/express-app/modules.js +28 -0
  13. package/build/express-app/modules.js.map +1 -0
  14. package/build/express-app/route-loader.js +4 -12
  15. package/build/express-app/route-loader.js.map +1 -1
  16. package/build/openapi.d.ts +1 -1
  17. package/build/openapi.js +26 -6
  18. package/build/openapi.js.map +1 -1
  19. package/build/telemetry/index.js +3 -1
  20. package/build/telemetry/index.js.map +1 -1
  21. package/build/tsconfig.build.tsbuildinfo +1 -1
  22. package/package.json +51 -43
  23. package/src/express-app/app.ts +21 -20
  24. package/src/express-app/modules.ts +27 -0
  25. package/src/express-app/route-loader.ts +5 -16
  26. package/src/openapi.ts +43 -7
  27. package/src/telemetry/index.ts +3 -1
  28. package/tsconfig.build.json +2 -1
  29. package/tsconfig.json +2 -1
  30. package/vitest.config.ts +19 -0
  31. package/.husky/commit-msg +0 -4
  32. package/.husky/pre-commit +0 -6
  33. package/__tests__/config.test.ts +0 -32
  34. package/__tests__/fake-serv/api/fake-serv.yaml +0 -48
  35. package/__tests__/fake-serv/config/config.json +0 -16
  36. package/__tests__/fake-serv/src/handlers/hello.ts +0 -10
  37. package/__tests__/fake-serv/src/index.ts +0 -35
  38. package/__tests__/fake-serv/src/routes/error.ts +0 -13
  39. package/__tests__/fake-serv/src/routes/index.ts +0 -22
  40. package/__tests__/fake-serv/src/routes/other/world.ts +0 -7
  41. package/__tests__/fake-serv.test.ts +0 -74
  42. package/jest.config.js +0 -14
package/package.json CHANGED
@@ -1,18 +1,16 @@
1
1
  {
2
2
  "name": "@openapi-typescript-infra/service",
3
- "version": "1.2.1",
4
- "description": "An opinionated framework for building configuration driven services - web, api, or job. Uses OpenAPI, pino logging, express, confit, Typescript and Jest.",
3
+ "version": "2.0.0",
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": {
7
- "test": "jest",
7
+ "test": "vitest",
8
8
  "lint": "eslint .",
9
9
  "build": "tsc -p tsconfig.build.json && yarn dlx glob-chmod 755 build/bin/*",
10
10
  "watch": "tsc -p tsconfig.json -w --preserveWatchOutput",
11
11
  "clean": "npx rimraf ./build",
12
12
  "prepublishOnly": "yarn build",
13
- "prepack": "pinst --disable",
14
- "postpack": "pinst --enable",
15
- "_postinstall": "husky install && coconfig"
13
+ "_postinstall": "coconfig"
16
14
  },
17
15
  "repository": {
18
16
  "type": "git",
@@ -27,9 +25,6 @@
27
25
  "engines": {
28
26
  "node": ">=18"
29
27
  },
30
- "lint-staged": {
31
- "*.{js,jsx,ts,tsx}": "yarn eslint --cache --fix"
32
- },
33
28
  "keywords": [
34
29
  "service",
35
30
  "openapi",
@@ -37,7 +32,7 @@
37
32
  "confit",
38
33
  "babel",
39
34
  "typescript",
40
- "jest"
35
+ "vitest"
41
36
  ],
42
37
  "author": "developers@pyralis.com>",
43
38
  "license": "MIT",
@@ -47,6 +42,19 @@
47
42
  "release": {
48
43
  "branches": [
49
44
  "main"
45
+ ],
46
+ "plugins": [
47
+ "@semantic-release/commit-analyzer",
48
+ "@semantic-release/release-notes-generator",
49
+ "@semantic-release/changelog",
50
+ [
51
+ "@semantic-release/exec",
52
+ {
53
+ "publishCmd": "yarn dlx pinst --disable"
54
+ }
55
+ ],
56
+ "@semantic-release/npm",
57
+ "@semantic-release/git"
50
58
  ]
51
59
  },
52
60
  "homepage": "https://github.com/openapi-typescript-infra/service#readme",
@@ -55,22 +63,22 @@
55
63
  "@godaddy/terminus": "^4.12.1",
56
64
  "@opentelemetry/api": "^1.4.1",
57
65
  "@opentelemetry/api-metrics": "^0.33.0",
58
- "@opentelemetry/exporter-prometheus": "^0.41.0",
59
- "@opentelemetry/exporter-trace-otlp-proto": "^0.41.0",
60
- "@opentelemetry/instrumentation": "^0.41.0",
61
- "@opentelemetry/instrumentation-aws-sdk": "^0.35.0",
62
- "@opentelemetry/instrumentation-dns": "^0.32.0",
63
- "@opentelemetry/instrumentation-express": "^0.33.0",
64
- "@opentelemetry/instrumentation-generic-pool": "^0.32.0",
65
- "@opentelemetry/instrumentation-graphql": "^0.35.0",
66
- "@opentelemetry/instrumentation-http": "^0.41.0",
67
- "@opentelemetry/instrumentation-ioredis": "^0.35.0",
68
- "@opentelemetry/instrumentation-net": "^0.32.0",
69
- "@opentelemetry/instrumentation-pg": "^0.36.0",
70
- "@opentelemetry/instrumentation-pino": "^0.34.0",
71
- "@opentelemetry/sdk-metrics": "^1.15.0",
72
- "@opentelemetry/sdk-node": "^0.41.0",
73
- "@opentelemetry/semantic-conventions": "^1.15.0",
66
+ "@opentelemetry/exporter-prometheus": "^0.41.2",
67
+ "@opentelemetry/exporter-trace-otlp-proto": "^0.41.2",
68
+ "@opentelemetry/instrumentation": "^0.41.2",
69
+ "@opentelemetry/instrumentation-aws-sdk": "^0.36.0",
70
+ "@opentelemetry/instrumentation-dns": "^0.32.1",
71
+ "@opentelemetry/instrumentation-express": "^0.33.1",
72
+ "@opentelemetry/instrumentation-generic-pool": "^0.32.2",
73
+ "@opentelemetry/instrumentation-graphql": "^0.35.1",
74
+ "@opentelemetry/instrumentation-http": "^0.41.2",
75
+ "@opentelemetry/instrumentation-ioredis": "^0.35.1",
76
+ "@opentelemetry/instrumentation-net": "^0.32.1",
77
+ "@opentelemetry/instrumentation-pg": "^0.36.1",
78
+ "@opentelemetry/instrumentation-pino": "^0.34.1",
79
+ "@opentelemetry/sdk-metrics": "^1.15.2",
80
+ "@opentelemetry/sdk-node": "^0.41.2",
81
+ "@opentelemetry/semantic-conventions": "^1.15.2",
74
82
  "cookie-parser": "^1.4.6",
75
83
  "dotenv": "^16.3.1",
76
84
  "eventsource": "^1.1.2",
@@ -79,7 +87,7 @@
79
87
  "glob": "^8.1.0",
80
88
  "lodash": "^4.17.21",
81
89
  "minimist": "^1.2.8",
82
- "pino": "^8.14.1",
90
+ "pino": "^8.15.0",
83
91
  "read-pkg-up": "^7.0.1",
84
92
  "rest-api-support": "^1.16.3",
85
93
  "shortstop-dns": "^1.1.0",
@@ -87,32 +95,32 @@
87
95
  "shortstop-yaml": "^1.0.0"
88
96
  },
89
97
  "devDependencies": {
90
- "@commitlint/cli": "^17.6.6",
91
- "@commitlint/config-conventional": "^17.6.6",
92
- "@openapi-typescript-infra/coconfig": "^3.2.0",
98
+ "@commitlint/cli": "^17.7.1",
99
+ "@commitlint/config-conventional": "^17.7.0",
100
+ "@openapi-typescript-infra/coconfig": "^4.0.0",
101
+ "@semantic-release/changelog": "^6.0.3",
102
+ "@semantic-release/commit-analyzer": "^10.0.1",
103
+ "@semantic-release/exec": "^6.0.3",
104
+ "@semantic-release/git": "^10.0.1",
105
+ "@semantic-release/release-notes-generator": "^11.0.4",
93
106
  "@types/cookie-parser": "^1.4.3",
94
107
  "@types/eventsource": "1.1.11",
95
108
  "@types/express": "^4.17.17",
96
109
  "@types/glob": "^8.1.0",
97
- "@types/jest": "^29.5.3",
98
- "@types/lodash": "^4.14.195",
110
+ "@types/lodash": "^4.14.197",
99
111
  "@types/minimist": "^1.2.2",
100
- "@types/node": "^18.16.19",
112
+ "@types/node": "^18.17.5",
101
113
  "@types/supertest": "^2.0.12",
102
- "coconfig": "^0.12.2",
103
- "eslint": "^8.45.0",
114
+ "coconfig": "^0.13.3",
115
+ "eslint": "^8.47.0",
104
116
  "eslint-config-gasbuddy": "^7.2.0",
105
- "eslint-config-prettier": "^8.8.0",
106
- "eslint-plugin-jest": "^27.2.3",
107
- "husky": "^8.0.3",
108
- "jest": "^29.6.1",
109
- "lint-staged": "^13.2.3",
110
- "pino-pretty": "^10.0.1",
117
+ "eslint-config-prettier": "^9.0.0",
118
+ "pino-pretty": "^10.2.0",
111
119
  "pinst": "^3.0.0",
112
120
  "supertest": "^6.3.3",
113
- "ts-jest": "^29.1.1",
114
121
  "ts-node": "^10.9.1",
115
- "typescript": "^5.1.6"
122
+ "typescript": "^5.1.6",
123
+ "vitest": "^0.34.2"
116
124
  },
117
125
  "packageManager": "yarn@3.2.3"
118
126
  }
@@ -40,7 +40,9 @@ interface InternalMetricsInfo {
40
40
  exporter?: PrometheusExporter;
41
41
  }
42
42
 
43
- interface AppWithMetrics { [METRICS_KEY]?: InternalMetricsInfo; }
43
+ interface AppWithMetrics {
44
+ [METRICS_KEY]?: InternalMetricsInfo;
45
+ }
44
46
 
45
47
  async function enableMetrics<SLocals extends ServiceLocals = ServiceLocals>(
46
48
  app: ServiceExpress<SLocals>,
@@ -89,9 +91,7 @@ export async function startApp<
89
91
  SLocals extends ServiceLocals = ServiceLocals,
90
92
  RLocals extends RequestLocals = RequestLocals,
91
93
  >(startOptions: ServiceStartOptions<SLocals, RLocals>): Promise<ServiceExpress<SLocals>> {
92
- const {
93
- service, rootDirectory, codepath = 'build', name,
94
- } = startOptions;
94
+ const { service, rootDirectory, codepath = 'build', name } = startOptions;
95
95
  const shouldPrettyPrint = isDev() && !process.env.NO_PRETTY_LOGS;
96
96
  const destination = pino.destination({
97
97
  dest: process.env.LOG_TO_FILE || process.stdout.fd,
@@ -99,24 +99,24 @@ export async function startApp<
99
99
  });
100
100
  const logger = shouldPrettyPrint
101
101
  ? pino({
102
- transport: {
103
- destination,
104
- target: 'pino-pretty',
105
- options: {
106
- colorize: true,
102
+ transport: {
103
+ destination,
104
+ target: 'pino-pretty',
105
+ options: {
106
+ colorize: true,
107
+ },
107
108
  },
108
- },
109
- })
109
+ })
110
110
  : pino(
111
- {
112
- formatters: {
113
- level(label) {
114
- return { level: label };
111
+ {
112
+ formatters: {
113
+ level(label) {
114
+ return { level: label };
115
+ },
115
116
  },
116
117
  },
117
- },
118
- destination,
119
- );
118
+ destination,
119
+ );
120
120
 
121
121
  const serviceImpl = service();
122
122
  assert(serviceImpl?.start, 'Service function did not return a conforming object');
@@ -260,15 +260,16 @@ export async function startApp<
260
260
  });
261
261
  }
262
262
 
263
+ const codePattern = codepath === 'src' ? '**/*.ts' : '**/*.js';
263
264
  if (routing?.routes) {
264
265
  await loadRoutes(
265
266
  app,
266
267
  path.resolve(rootDirectory, codepath, config.get<string>('routing:routes') || 'routes'),
267
- codepath === 'src' ? '**/*.ts' : '**/*.js',
268
+ codePattern,
268
269
  );
269
270
  }
270
271
  if (routing?.openapi) {
271
- app.use(openApi(app, rootDirectory, codepath, options.openApiOptions));
272
+ app.use(await openApi(app, rootDirectory, codepath, codePattern, options.openApiOptions));
272
273
  }
273
274
 
274
275
  // Putting this here allows more flexible middleware insertion
@@ -0,0 +1,27 @@
1
+ import { glob } from 'glob';
2
+
3
+ export async function loadModule(path: string): Promise<Record<string, unknown>> {
4
+ try {
5
+ return require(path);
6
+ } catch (error) {
7
+ if ((error as Error).message.includes('Cannot use import statement outside a module')) {
8
+ return import(path);
9
+ }
10
+ throw error;
11
+ }
12
+ }
13
+
14
+ export async function getFilesInDir(pattern: string, dir: string) {
15
+ const files: string[] = await new Promise((accept, reject) => {
16
+ glob(
17
+ pattern,
18
+ {
19
+ nodir: true,
20
+ strict: true,
21
+ cwd: dir,
22
+ },
23
+ (error, matches) => (error ? reject(error) : accept(matches)),
24
+ );
25
+ });
26
+ return files;
27
+ }
@@ -1,30 +1,19 @@
1
1
  import path from 'path';
2
2
 
3
- import { glob } from 'glob';
4
3
  import express from 'express';
5
4
 
6
5
  import type { ServiceExpress } from '../types';
7
6
 
7
+ import { getFilesInDir, loadModule } from './modules';
8
+
8
9
  export async function loadRoutes(app: ServiceExpress, routingDir: string, pattern: string) {
9
- const files: string[] = await new Promise((accept, reject) => {
10
- glob(
11
- pattern,
12
- {
13
- nodir: true,
14
- strict: true,
15
- cwd: routingDir,
16
- },
17
- (error, matches) => (error ? reject(error) : accept(matches)),
18
- );
19
- });
10
+ const files = await getFilesInDir(pattern, routingDir);
20
11
 
21
12
  await Promise.all(
22
13
  files.map(async (filename) => {
23
14
  const routeBase = path.dirname(filename);
24
15
  const modulePath = path.resolve(routingDir, filename);
25
- // Need to use require for loading .ts files
26
- // eslint-disable-next-line import/no-dynamic-require, global-require, @typescript-eslint/no-var-requires
27
- const module = require(modulePath);
16
+ const module = await loadModule(modulePath);
28
17
  const mounter = module.default || module.route;
29
18
  if (typeof mounter === 'function') {
30
19
  const childRouter = express.Router();
@@ -38,7 +27,7 @@ export async function loadRoutes(app: ServiceExpress, routingDir: string, patter
38
27
  pathParts.push(fn);
39
28
  }
40
29
  const finalPath = pathParts.join('/') || '/';
41
- app.locals.logger.debug({ path: finalPath, source: filename }, 'Registering route');
30
+ app.locals.logger.debug({ path: finalPath, source: filename }, 'Registering routes');
42
31
  app.use(finalPath, childRouter);
43
32
  } else {
44
33
  app.locals.logger.warn({ filename }, 'Route file had no default export');
package/src/openapi.ts CHANGED
@@ -5,6 +5,7 @@ import * as OpenApiValidator from 'express-openapi-validator';
5
5
  import type { Handler } from 'express';
6
6
 
7
7
  import type { ServiceExpress } from './types';
8
+ import { getFilesInDir, loadModule } from './express-app/modules';
8
9
 
9
10
  const notImplementedHandler: Handler = (req, res) => {
10
11
  res.status(501).json({
@@ -16,15 +17,46 @@ const notImplementedHandler: Handler = (req, res) => {
16
17
 
17
18
  type OAPIOpts = Parameters<typeof OpenApiValidator.middleware>[0];
18
19
 
19
- export function openApi(
20
+ export async function openApi(
20
21
  app: ServiceExpress,
21
22
  rootDirectory: string,
22
23
  codepath: string,
24
+ pattern: string,
23
25
  openApiOptions?: Partial<OAPIOpts>,
24
26
  ) {
25
27
  const apiSpec = path.resolve(rootDirectory, `./api/${app.locals.name}.yaml`);
26
28
  app.locals.logger.debug({ apiSpec, codepath }, 'Serving OpenAPI');
27
29
 
30
+ const basePath = path.resolve(rootDirectory, `${codepath}/handlers`);
31
+ // Because of the weirdness of ESM/CJS interop, and the synchronous nature of
32
+ // the OpenAPI resolver, we need to preload all the modules we might need
33
+ const moduleFiles = await getFilesInDir(
34
+ pattern,
35
+ path.resolve(rootDirectory, `${codepath}/handlers`),
36
+ );
37
+ const preloadedModules = await Promise.all(
38
+ moduleFiles.map((file) => {
39
+ const fullPath = path.join(basePath, file);
40
+ return loadModule(fullPath).catch((error) => {
41
+ app.locals.logger.warn(
42
+ { file: fullPath, message: error.message },
43
+ 'Could not load potential API handler',
44
+ );
45
+ return undefined;
46
+ });
47
+ }),
48
+ );
49
+ const modulesByPath = moduleFiles.reduce(
50
+ (acc, file, index) => {
51
+ const m = preloadedModules[index];
52
+ if (m) {
53
+ acc[`/${path.basename(file, path.extname(file))}`] = m;
54
+ }
55
+ return acc;
56
+ },
57
+ {} as Record<string, Record<string, unknown>>,
58
+ );
59
+
28
60
  const defaultOptions: OAPIOpts = {
29
61
  apiSpec,
30
62
  ignoreUndocumented: true,
@@ -33,16 +65,20 @@ export function openApi(
33
65
  coerceTypes: 'array',
34
66
  },
35
67
  operationHandlers: {
36
- basePath: path.resolve(rootDirectory, `${codepath}/handlers`),
37
- resolver(basePath: string, route: Parameters<typeof OpenApiValidator.resolvers.defaultResolver>[1]) {
68
+ basePath,
69
+ resolver(
70
+ basePath: string,
71
+ route: Parameters<typeof OpenApiValidator.resolvers.defaultResolver>[1],
72
+ ) {
38
73
  const pathKey = route.openApiRoute.substring(route.basePath.length);
39
74
  const modulePath = path.join(basePath, pathKey);
40
75
 
41
76
  try {
42
- // eslint-disable-next-line import/no-dynamic-require, global-require, @typescript-eslint/no-var-requires
43
- const module = require(modulePath);
44
- const method = Object.keys(module).find((m) => m.toUpperCase() === route.method);
45
- if (!method) {
77
+ const module = modulesByPath[pathKey];
78
+ const method = module
79
+ ? Object.keys(module).find((m) => m.toUpperCase() === route.method)
80
+ : undefined;
81
+ if (!module || !method) {
46
82
  throw new Error(
47
83
  `Could not find a [${route.method}] function in ${modulePath} when trying to route [${route.method} ${route.expressRoute}].`,
48
84
  );
@@ -24,7 +24,9 @@ function getExporter() {
24
24
  url: process.env.OTLP_EXPORTER || 'http://otlp-exporter:4318/v1/traces',
25
25
  });
26
26
  }
27
- return new opentelemetry.tracing.ConsoleSpanExporter();
27
+ if (process.env.ENABLE_CONSOLE_OLTP_EXPORTER) {
28
+ return new opentelemetry.tracing.ConsoleSpanExporter();
29
+ }
28
30
  }
29
31
 
30
32
  export async function startWithTelemetry<
@@ -5,6 +5,7 @@
5
5
  "src/**/*.test.ts",
6
6
  "__tests__/**/*.ts",
7
7
  "__mocks__/**/*.ts",
8
- "coconfig.ts"
8
+ "coconfig.ts",
9
+ "vitest.config.ts"
9
10
  ]
10
11
  }
package/tsconfig.json CHANGED
@@ -4,7 +4,8 @@
4
4
  "src/**/*",
5
5
  "__tests__/**/*",
6
6
  "__mocks__/**/*",
7
- "coconfig.ts"
7
+ "coconfig.ts",
8
+ "vitest.config.ts"
8
9
  ],
9
10
  "exclude": [
10
11
  "node_modules",
@@ -0,0 +1,19 @@
1
+ /**
2
+ * This file is generated by coconfig. Do not edit it directly.
3
+ * Instead, edit the coconfig.js or coconfig.ts file in your project root.
4
+ *
5
+ * See https://github.com/gas-buddy/coconfig for more information.
6
+ * @version coconfig@0.13.3
7
+ */
8
+ import cjs from '@openapi-typescript-infra/coconfig';
9
+ import * as esmToCjs from '@openapi-typescript-infra/coconfig';
10
+
11
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
+ const configModule: any = cjs || esmToCjs;
13
+
14
+ const configItem = configModule.default || configModule.config || configModule;
15
+ const { configuration } = configItem && configItem['vitest.config.ts'];
16
+ const resolved = typeof configuration === 'function' ? configuration() : configuration;
17
+
18
+ // eslint-disable-next-line import/no-default-export
19
+ export default resolved;
package/.husky/commit-msg DELETED
@@ -1,4 +0,0 @@
1
- #!/usr/bin/env sh
2
- . "$(dirname -- "$0")/_/husky.sh"
3
-
4
- npx --no -- commitlint --edit ${1}
package/.husky/pre-commit DELETED
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env sh
2
- . "$(dirname -- "$0")/_/husky.sh"
3
-
4
- yarn lint-staged
5
- yarn build
6
- yarn test
@@ -1,32 +0,0 @@
1
- import path from 'path';
2
-
3
- import { insertConfigurationBefore, loadConfiguration } from '../src/config';
4
-
5
- describe('configuration loader', () => {
6
- test('overrides and shortstops', async () => {
7
- const rootDirectory = path.resolve(__dirname, './fake-serv');
8
- const config = await loadConfiguration({
9
- name: 'fake-serv',
10
- rootDirectory,
11
- configurationDirectories: [path.resolve(rootDirectory, './config')],
12
- });
13
- expect(config.get('logging:level')).toEqual('debug');
14
- expect(config.get('google')).toBeTruthy();
15
- expect(config.get('google')).not.toEqual('google.com');
16
-
17
- expect(config.get('envswitchoff')).toBeFalsy();
18
- expect(config.get('envswitchon')).toBeTruthy();
19
-
20
- expect(config.get('servicetype')).toBeTruthy();
21
- expect(config.get('oservicetype')).toBeTruthy();
22
- expect(config.get('notservicetype')).toBeFalsy();
23
-
24
- const orig = ['a', 'b', 'd'];
25
- const withC = insertConfigurationBefore(orig, 'c', 'd');
26
- expect(withC).toEqual(['a', 'b', 'c', 'd']);
27
- const withE = insertConfigurationBefore(orig, 'e', 'q');
28
- expect(withE).toEqual(['a', 'b', 'd', 'e', 'q']);
29
- expect(insertConfigurationBefore(undefined, 'a', 'b')).toEqual(['a', 'b']);
30
- expect(insertConfigurationBefore([], 'a', 'b')).toEqual(['a', 'b']);
31
- });
32
- });
@@ -1,48 +0,0 @@
1
- openapi: 3.0.0
2
- info:
3
- title: Fake Service
4
- version: 1.0.0
5
- paths:
6
- /hello:
7
- get:
8
- description: Get a greeting
9
- parameters:
10
- - name: greeting
11
- in: query
12
- required: false
13
- schema:
14
- type: string
15
- - name: number
16
- in: query
17
- required: false
18
- schema:
19
- type: number
20
- - name: break_things
21
- in: query
22
- required: false
23
- schema:
24
- type: boolean
25
- responses:
26
- 200:
27
- description: We responded
28
- content:
29
- application/json:
30
- schema:
31
- type: object
32
- properties:
33
- greeting:
34
- type: string
35
- post:
36
- description: Test body conversion
37
- requestBody:
38
- content:
39
- application/json:
40
- schema:
41
- type: object
42
- properties:
43
- number:
44
- type: number
45
- required: true
46
- responses:
47
- 204:
48
- description: I heard you
@@ -1,16 +0,0 @@
1
- {
2
- "logging": {
3
- "level": "debug"
4
- },
5
- "google": "dns:google.com",
6
- "envswitchoff": "env_switch:ENV_VAR_DOESNT_EXIST",
7
- "envswitchon": "env_switch:!ENV_VAR_DOESNT_EXIST",
8
- "servicetype": "servicetype:serv",
9
- "oservicetype": "servicetype:internal",
10
- "notservicetype": "servicetype:api",
11
- "server": {
12
- "metrics": {
13
- "enabled": true
14
- }
15
- }
16
- }
@@ -1,10 +0,0 @@
1
- import { ServiceHandler } from '../../../../src/index';
2
- import { FakeServLocals } from '../index';
3
-
4
- export const get: ServiceHandler<FakeServLocals> = async (req, res) => {
5
- res.json({ greeting: req.query.greeting || 'Hello World' });
6
- };
7
-
8
- export const post: ServiceHandler<FakeServLocals> = async (req, res) => {
9
- res.sendStatus(204);
10
- };
@@ -1,35 +0,0 @@
1
- import { RestApiErrorResponse, RestApiSuccessResponse } from 'rest-api-support';
2
-
3
- import type { Service, ServiceLocals } from '../../../src/types';
4
- import { useService } from '../../../src';
5
-
6
- export interface FakeServLocals extends ServiceLocals {
7
- services: {
8
- fakeServ: {
9
- get_something(): RestApiSuccessResponse<{ things: string[] }> | RestApiErrorResponse;
10
- }
11
- }
12
- }
13
-
14
- export function service(): Service<FakeServLocals> {
15
- const base = useService<FakeServLocals>();
16
- return {
17
- ...base,
18
- async start(app) {
19
- await base.start(app);
20
- app.locals.services.fakeServ = {
21
- get_something() { throw new Error('Should not be called.'); },
22
- };
23
- },
24
- async onRequest(req, res) {
25
- await base.onRequest?.(req, res);
26
- res.locals.rawBody = true;
27
- },
28
- async healthy(app) {
29
- await base.healthy?.(app);
30
- return new Promise((accept) => {
31
- setTimeout(accept, 1000);
32
- });
33
- },
34
- };
35
- }
@@ -1,13 +0,0 @@
1
- import { ServiceRouter } from '../../../../src/index';
2
- import { ServiceError } from '../../../../src/error';
3
-
4
- export function route(router: ServiceRouter) {
5
- router.get('/sync', (req) => {
6
- throw new ServiceError(req.app, 'Synchronous error', { code: 'SyncError' });
7
- });
8
-
9
- router.get('/async', async (req) => {
10
- await new Promise((accept) => { setTimeout(accept, 100); })
11
- .then(() => { throw new ServiceError(req.app, 'Async error', { code: 'AsyncError' }); });
12
- });
13
- }
@@ -1,22 +0,0 @@
1
- import type { ServiceExpress, ServiceRouter } from '../../../../src';
2
- import { FakeServLocals } from '../index';
3
-
4
- export function route(
5
- router: ServiceRouter<FakeServLocals>,
6
- app: ServiceExpress<FakeServLocals>,
7
- ) {
8
- const worldRequests = app.locals.meter.createCounter('world_requests', {
9
- description: 'Metrics about requests to world',
10
- });
11
-
12
- router.get('/world', (req, res) => {
13
- worldRequests.add(1, { method: 'get' });
14
- res.json({ hello: 'world' });
15
- });
16
-
17
- router.post('/world', async (req, res) => {
18
- await app.locals.services.fakeServ.get_something();
19
- worldRequests.add(1);
20
- res.sendStatus(204);
21
- });
22
- }
@@ -1,7 +0,0 @@
1
- import type { Router } from 'express';
2
-
3
- export function route(router: Router) {
4
- router.get('/', (req, res) => {
5
- res.json({ hello: 'jupiter' });
6
- });
7
- }