@openapi-typescript-infra/service 1.2.1 → 1.2.2
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/build/telemetry/index.js +3 -1
- package/build/telemetry/index.js.map +1 -1
- package/build/tsconfig.build.tsbuildinfo +1 -1
- package/jest.config.js +1 -1
- package/package.json +19 -19
- package/src/telemetry/index.ts +3 -1
- package/tsconfig.build.json +2 -1
- package/tsconfig.json +2 -1
- 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
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Instead, edit the coconfig.js or coconfig.ts file in your project root.
|
|
4
4
|
*
|
|
5
5
|
* See https://github.com/gas-buddy/coconfig for more information.
|
|
6
|
-
* @version coconfig@0.
|
|
6
|
+
* @version coconfig@0.13.3
|
|
7
7
|
*/
|
|
8
8
|
const configModule = require('@openapi-typescript-infra/coconfig');
|
|
9
9
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openapi-typescript-infra/service",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "An opinionated framework for building configuration driven services - web, api, or job. Uses OpenAPI, pino logging, express, confit, Typescript and Jest.",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -55,22 +55,22 @@
|
|
|
55
55
|
"@godaddy/terminus": "^4.12.1",
|
|
56
56
|
"@opentelemetry/api": "^1.4.1",
|
|
57
57
|
"@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.
|
|
58
|
+
"@opentelemetry/exporter-prometheus": "^0.41.1",
|
|
59
|
+
"@opentelemetry/exporter-trace-otlp-proto": "^0.41.1",
|
|
60
|
+
"@opentelemetry/instrumentation": "^0.41.1",
|
|
61
61
|
"@opentelemetry/instrumentation-aws-sdk": "^0.35.0",
|
|
62
62
|
"@opentelemetry/instrumentation-dns": "^0.32.0",
|
|
63
63
|
"@opentelemetry/instrumentation-express": "^0.33.0",
|
|
64
64
|
"@opentelemetry/instrumentation-generic-pool": "^0.32.0",
|
|
65
65
|
"@opentelemetry/instrumentation-graphql": "^0.35.0",
|
|
66
|
-
"@opentelemetry/instrumentation-http": "^0.41.
|
|
66
|
+
"@opentelemetry/instrumentation-http": "^0.41.1",
|
|
67
67
|
"@opentelemetry/instrumentation-ioredis": "^0.35.0",
|
|
68
68
|
"@opentelemetry/instrumentation-net": "^0.32.0",
|
|
69
69
|
"@opentelemetry/instrumentation-pg": "^0.36.0",
|
|
70
70
|
"@opentelemetry/instrumentation-pino": "^0.34.0",
|
|
71
|
-
"@opentelemetry/sdk-metrics": "^1.15.
|
|
72
|
-
"@opentelemetry/sdk-node": "^0.41.
|
|
73
|
-
"@opentelemetry/semantic-conventions": "^1.15.
|
|
71
|
+
"@opentelemetry/sdk-metrics": "^1.15.1",
|
|
72
|
+
"@opentelemetry/sdk-node": "^0.41.1",
|
|
73
|
+
"@opentelemetry/semantic-conventions": "^1.15.1",
|
|
74
74
|
"cookie-parser": "^1.4.6",
|
|
75
75
|
"dotenv": "^16.3.1",
|
|
76
76
|
"eventsource": "^1.1.2",
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"glob": "^8.1.0",
|
|
80
80
|
"lodash": "^4.17.21",
|
|
81
81
|
"minimist": "^1.2.8",
|
|
82
|
-
"pino": "^8.14.
|
|
82
|
+
"pino": "^8.14.2",
|
|
83
83
|
"read-pkg-up": "^7.0.1",
|
|
84
84
|
"rest-api-support": "^1.16.3",
|
|
85
85
|
"shortstop-dns": "^1.1.0",
|
|
@@ -87,27 +87,27 @@
|
|
|
87
87
|
"shortstop-yaml": "^1.0.0"
|
|
88
88
|
},
|
|
89
89
|
"devDependencies": {
|
|
90
|
-
"@commitlint/cli": "^17.6.
|
|
91
|
-
"@commitlint/config-conventional": "^17.6.
|
|
92
|
-
"@openapi-typescript-infra/coconfig": "^3.
|
|
90
|
+
"@commitlint/cli": "^17.6.7",
|
|
91
|
+
"@commitlint/config-conventional": "^17.6.7",
|
|
92
|
+
"@openapi-typescript-infra/coconfig": "^3.3.6",
|
|
93
93
|
"@types/cookie-parser": "^1.4.3",
|
|
94
94
|
"@types/eventsource": "1.1.11",
|
|
95
95
|
"@types/express": "^4.17.17",
|
|
96
96
|
"@types/glob": "^8.1.0",
|
|
97
97
|
"@types/jest": "^29.5.3",
|
|
98
|
-
"@types/lodash": "^4.14.
|
|
98
|
+
"@types/lodash": "^4.14.196",
|
|
99
99
|
"@types/minimist": "^1.2.2",
|
|
100
|
-
"@types/node": "^18.
|
|
100
|
+
"@types/node": "^18.17.1",
|
|
101
101
|
"@types/supertest": "^2.0.12",
|
|
102
|
-
"coconfig": "^0.
|
|
103
|
-
"eslint": "^8.
|
|
102
|
+
"coconfig": "^0.13.3",
|
|
103
|
+
"eslint": "^8.46.0",
|
|
104
104
|
"eslint-config-gasbuddy": "^7.2.0",
|
|
105
|
-
"eslint-config-prettier": "^8.
|
|
105
|
+
"eslint-config-prettier": "^8.9.0",
|
|
106
106
|
"eslint-plugin-jest": "^27.2.3",
|
|
107
107
|
"husky": "^8.0.3",
|
|
108
|
-
"jest": "^29.6.
|
|
108
|
+
"jest": "^29.6.2",
|
|
109
109
|
"lint-staged": "^13.2.3",
|
|
110
|
-
"pino-pretty": "^10.0
|
|
110
|
+
"pino-pretty": "^10.2.0",
|
|
111
111
|
"pinst": "^3.0.0",
|
|
112
112
|
"supertest": "^6.3.3",
|
|
113
113
|
"ts-jest": "^29.1.1",
|
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/__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
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
|
|
3
|
-
import request from 'supertest';
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
listen, ServiceStartOptions, shutdownApp, startApp,
|
|
7
|
-
} from '../src/index';
|
|
8
|
-
|
|
9
|
-
import { FakeServLocals, service } from './fake-serv/src/index';
|
|
10
|
-
|
|
11
|
-
describe('fake-serv', () => {
|
|
12
|
-
test('basic service functionality', async () => {
|
|
13
|
-
const options: ServiceStartOptions<FakeServLocals> = {
|
|
14
|
-
service,
|
|
15
|
-
name: 'fake-serv',
|
|
16
|
-
rootDirectory: path.resolve(__dirname, './fake-serv'),
|
|
17
|
-
codepath: 'src',
|
|
18
|
-
};
|
|
19
|
-
const app = await startApp(options).catch((error) => {
|
|
20
|
-
console.error(error);
|
|
21
|
-
throw error;
|
|
22
|
-
});
|
|
23
|
-
expect(app).toBeTruthy();
|
|
24
|
-
|
|
25
|
-
let { body } = await request(app).get('/world').timeout(500).expect(200);
|
|
26
|
-
expect(body.hello).toEqual('world');
|
|
27
|
-
|
|
28
|
-
({ body } = await request(app).get('/other/world').timeout(500).expect(200));
|
|
29
|
-
expect(body.hello).toEqual('jupiter');
|
|
30
|
-
|
|
31
|
-
({ body } = await request(app).get('/hello').query({ greeting: 'Hello Pluto!', number: '6', break_things: true }).expect(200));
|
|
32
|
-
expect(body.greeting).toEqual('Hello Pluto!');
|
|
33
|
-
|
|
34
|
-
// Can't convert green to a number
|
|
35
|
-
await request(app).get('/hello').query({ greeting: 'Hello Pluto!', number: 'green' }).expect(400);
|
|
36
|
-
|
|
37
|
-
// Make sure body paramater conversion works
|
|
38
|
-
await request(app).post('/hello').send({ number: 'green' }).expect(400);
|
|
39
|
-
await request(app).post('/hello').send({ number: '6' }).expect(204);
|
|
40
|
-
await request(app).post('/hello').send({ number: 6 }).expect(204);
|
|
41
|
-
|
|
42
|
-
({ body } = await request(app).get('/error/sync').timeout(1000).expect(500));
|
|
43
|
-
expect(body.code).toEqual('SyncError');
|
|
44
|
-
|
|
45
|
-
({ body } = await request(app).get('/error/async').timeout(1000).expect(500));
|
|
46
|
-
expect(body.code).toEqual('AsyncError');
|
|
47
|
-
|
|
48
|
-
// Mocking
|
|
49
|
-
await request(app).post('/world').expect(500);
|
|
50
|
-
|
|
51
|
-
// Clean shutdown
|
|
52
|
-
await expect(shutdownApp(app)).resolves.toBeUndefined();
|
|
53
|
-
const secondApp = await startApp(options);
|
|
54
|
-
|
|
55
|
-
// Make sure we can listen
|
|
56
|
-
const server = await listen(secondApp);
|
|
57
|
-
|
|
58
|
-
// Call metrics
|
|
59
|
-
await request(secondApp).get('/world').expect(200);
|
|
60
|
-
await request(secondApp.locals.internalApp).get('/metrics')
|
|
61
|
-
.expect(200)
|
|
62
|
-
.expect((res) => expect(res.text).toMatch(/world_requests_total{method="get"} 1/));
|
|
63
|
-
|
|
64
|
-
await new Promise<void>((accept, reject) => {
|
|
65
|
-
server.close((e) => {
|
|
66
|
-
if (e) {
|
|
67
|
-
reject(e);
|
|
68
|
-
} else {
|
|
69
|
-
accept();
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
});
|