@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.
- package/.eslintignore +1 -0
- package/.eslintrc.js +1 -1
- package/.prettierrc.js +1 -1
- package/.trunk/configs/.markdownlint.yaml +10 -0
- package/.trunk/configs/.yamllint.yaml +10 -0
- package/.trunk/trunk.yaml +35 -0
- package/CHANGELOG.md +12 -0
- package/README.md +10 -10
- package/build/express-app/app.js +4 -3
- package/build/express-app/app.js.map +1 -1
- package/build/express-app/modules.d.ts +2 -0
- package/build/express-app/modules.js +28 -0
- package/build/express-app/modules.js.map +1 -0
- package/build/express-app/route-loader.js +4 -12
- package/build/express-app/route-loader.js.map +1 -1
- package/build/openapi.d.ts +1 -1
- package/build/openapi.js +26 -6
- package/build/openapi.js.map +1 -1
- package/build/telemetry/index.js +3 -1
- package/build/telemetry/index.js.map +1 -1
- package/build/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +51 -43
- package/src/express-app/app.ts +21 -20
- package/src/express-app/modules.ts +27 -0
- package/src/express-app/route-loader.ts +5 -16
- package/src/openapi.ts +43 -7
- package/src/telemetry/index.ts +3 -1
- package/tsconfig.build.json +2 -1
- package/tsconfig.json +2 -1
- package/vitest.config.ts +19 -0
- package/.husky/commit-msg +0 -4
- package/.husky/pre-commit +0 -6
- package/__tests__/config.test.ts +0 -32
- package/__tests__/fake-serv/api/fake-serv.yaml +0 -48
- package/__tests__/fake-serv/config/config.json +0 -16
- package/__tests__/fake-serv/src/handlers/hello.ts +0 -10
- package/__tests__/fake-serv/src/index.ts +0 -35
- package/__tests__/fake-serv/src/routes/error.ts +0 -13
- package/__tests__/fake-serv/src/routes/index.ts +0 -22
- package/__tests__/fake-serv/src/routes/other/world.ts +0 -7
- package/__tests__/fake-serv.test.ts +0 -74
- 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": "
|
|
4
|
-
"description": "An opinionated framework for building configuration driven services - web, api, or
|
|
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": "
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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.
|
|
59
|
-
"@opentelemetry/exporter-trace-otlp-proto": "^0.41.
|
|
60
|
-
"@opentelemetry/instrumentation": "^0.41.
|
|
61
|
-
"@opentelemetry/instrumentation-aws-sdk": "^0.
|
|
62
|
-
"@opentelemetry/instrumentation-dns": "^0.32.
|
|
63
|
-
"@opentelemetry/instrumentation-express": "^0.33.
|
|
64
|
-
"@opentelemetry/instrumentation-generic-pool": "^0.32.
|
|
65
|
-
"@opentelemetry/instrumentation-graphql": "^0.35.
|
|
66
|
-
"@opentelemetry/instrumentation-http": "^0.41.
|
|
67
|
-
"@opentelemetry/instrumentation-ioredis": "^0.35.
|
|
68
|
-
"@opentelemetry/instrumentation-net": "^0.32.
|
|
69
|
-
"@opentelemetry/instrumentation-pg": "^0.36.
|
|
70
|
-
"@opentelemetry/instrumentation-pino": "^0.34.
|
|
71
|
-
"@opentelemetry/sdk-metrics": "^1.15.
|
|
72
|
-
"@opentelemetry/sdk-node": "^0.41.
|
|
73
|
-
"@opentelemetry/semantic-conventions": "^1.15.
|
|
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.
|
|
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.
|
|
91
|
-
"@commitlint/config-conventional": "^17.
|
|
92
|
-
"@openapi-typescript-infra/coconfig": "^
|
|
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/
|
|
98
|
-
"@types/lodash": "^4.14.195",
|
|
110
|
+
"@types/lodash": "^4.14.197",
|
|
99
111
|
"@types/minimist": "^1.2.2",
|
|
100
|
-
"@types/node": "^18.
|
|
112
|
+
"@types/node": "^18.17.5",
|
|
101
113
|
"@types/supertest": "^2.0.12",
|
|
102
|
-
"coconfig": "^0.
|
|
103
|
-
"eslint": "^8.
|
|
114
|
+
"coconfig": "^0.13.3",
|
|
115
|
+
"eslint": "^8.47.0",
|
|
104
116
|
"eslint-config-gasbuddy": "^7.2.0",
|
|
105
|
-
"eslint-config-prettier": "^
|
|
106
|
-
"
|
|
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
|
}
|
package/src/express-app/app.ts
CHANGED
|
@@ -40,7 +40,9 @@ interface InternalMetricsInfo {
|
|
|
40
40
|
exporter?: PrometheusExporter;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
interface AppWithMetrics {
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
102
|
+
transport: {
|
|
103
|
+
destination,
|
|
104
|
+
target: 'pino-pretty',
|
|
105
|
+
options: {
|
|
106
|
+
colorize: true,
|
|
107
|
+
},
|
|
107
108
|
},
|
|
108
|
-
}
|
|
109
|
-
})
|
|
109
|
+
})
|
|
110
110
|
: pino(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
{
|
|
112
|
+
formatters: {
|
|
113
|
+
level(label) {
|
|
114
|
+
return { level: label };
|
|
115
|
+
},
|
|
115
116
|
},
|
|
116
117
|
},
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
37
|
-
resolver(
|
|
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
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
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
|
);
|
package/src/telemetry/index.ts
CHANGED
|
@@ -24,7 +24,9 @@ function getExporter() {
|
|
|
24
24
|
url: process.env.OTLP_EXPORTER || 'http://otlp-exporter:4318/v1/traces',
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
|
-
|
|
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<
|
package/tsconfig.build.json
CHANGED
package/tsconfig.json
CHANGED
package/vitest.config.ts
ADDED
|
@@ -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
package/.husky/pre-commit
DELETED
package/__tests__/config.test.ts
DELETED
|
@@ -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
|
-
}
|