@unito/integration-sdk 0.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.
- package/.eslintrc.cjs +27 -0
- package/.nvmrc +1 -0
- package/.prettierignore +2 -0
- package/.prettierrc +7 -0
- package/LICENSE +3 -0
- package/README.md +16 -0
- package/dist/src/api/index.d.ts +2 -0
- package/dist/src/api/index.d.ts.map +1 -0
- package/dist/src/api/index.js +2 -0
- package/dist/src/api/index.js.map +1 -0
- package/dist/src/app/errors/HTTPError.d.ts +5 -0
- package/dist/src/app/errors/HTTPError.d.ts.map +1 -0
- package/dist/src/app/errors/HTTPError.js +8 -0
- package/dist/src/app/errors/HTTPError.js.map +1 -0
- package/dist/src/app/errors/HTTPNotFoundError.d.ts +5 -0
- package/dist/src/app/errors/HTTPNotFoundError.d.ts.map +1 -0
- package/dist/src/app/errors/HTTPNotFoundError.js +7 -0
- package/dist/src/app/errors/HTTPNotFoundError.js.map +1 -0
- package/dist/src/app/errors/HTTPUnprocessableEntityError.d.ts +5 -0
- package/dist/src/app/errors/HTTPUnprocessableEntityError.d.ts.map +1 -0
- package/dist/src/app/errors/HTTPUnprocessableEntityError.js +7 -0
- package/dist/src/app/errors/HTTPUnprocessableEntityError.js.map +1 -0
- package/dist/src/app/errors/index.d.ts +4 -0
- package/dist/src/app/errors/index.d.ts.map +1 -0
- package/dist/src/app/errors/index.js +4 -0
- package/dist/src/app/errors/index.js.map +1 -0
- package/dist/src/app/index.d.ts +6 -0
- package/dist/src/app/index.d.ts.map +1 -0
- package/dist/src/app/index.js +80 -0
- package/dist/src/app/index.js.map +1 -0
- package/dist/src/app/integration.d.ts +5 -0
- package/dist/src/app/integration.d.ts.map +1 -0
- package/dist/src/app/integration.js +86 -0
- package/dist/src/app/integration.js.map +1 -0
- package/dist/src/app/itemNode.d.ts +8 -0
- package/dist/src/app/itemNode.d.ts.map +1 -0
- package/dist/src/app/itemNode.js +13 -0
- package/dist/src/app/itemNode.js.map +1 -0
- package/dist/src/app/middlewares/withCorrelationId.d.ts +11 -0
- package/dist/src/app/middlewares/withCorrelationId.d.ts.map +1 -0
- package/dist/src/app/middlewares/withCorrelationId.js +8 -0
- package/dist/src/app/middlewares/withCorrelationId.js.map +1 -0
- package/dist/src/app/middlewares/withLogger.d.ts +12 -0
- package/dist/src/app/middlewares/withLogger.d.ts.map +1 -0
- package/dist/src/app/middlewares/withLogger.js +18 -0
- package/dist/src/app/middlewares/withLogger.js.map +1 -0
- package/dist/src/errors.d.ts +15 -0
- package/dist/src/errors.js +39 -0
- package/dist/src/handler.d.ts +67 -0
- package/dist/src/handler.js +224 -0
- package/dist/src/httpErrors.d.ts +22 -0
- package/dist/src/httpErrors.js +37 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +6 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/integration.d.ts +9 -0
- package/dist/src/integration.js +108 -0
- package/dist/src/middlewares/correlationId.d.ts +10 -0
- package/dist/src/middlewares/correlationId.js +6 -0
- package/dist/src/middlewares/credentials.d.ts +11 -0
- package/dist/src/middlewares/credentials.js +17 -0
- package/dist/src/middlewares/errors.d.ts +3 -0
- package/dist/src/middlewares/errors.js +22 -0
- package/dist/src/middlewares/filters.d.ts +16 -0
- package/dist/src/middlewares/filters.js +25 -0
- package/dist/src/middlewares/finish.d.ts +11 -0
- package/dist/src/middlewares/finish.js +10 -0
- package/dist/src/middlewares/logger.d.ts +12 -0
- package/dist/src/middlewares/logger.js +18 -0
- package/dist/src/middlewares/notFound.d.ts +3 -0
- package/dist/src/middlewares/notFound.js +8 -0
- package/dist/src/middlewares/selects.d.ts +10 -0
- package/dist/src/middlewares/selects.js +11 -0
- package/dist/src/resources/cache.d.ts +4 -0
- package/dist/src/resources/cache.d.ts.map +1 -0
- package/dist/src/resources/cache.js +25 -0
- package/dist/src/resources/cache.js.map +1 -0
- package/dist/src/resources/context.d.ts +78 -0
- package/dist/src/resources/context.js +2 -0
- package/dist/src/resources/logger.d.ts +14 -0
- package/dist/src/resources/logger.d.ts.map +1 -0
- package/dist/src/resources/logger.js +46 -0
- package/dist/src/resources/logger.js.map +1 -0
- package/dist/src/resources/provider.d.ts +36 -0
- package/dist/src/resources/provider.js +76 -0
- package/dist/test/errors.test.d.ts +1 -0
- package/dist/test/errors.test.js +16 -0
- package/dist/test/handler.test.d.ts +1 -0
- package/dist/test/handler.test.js +146 -0
- package/dist/test/middlewares/correlationId.test.d.ts +1 -0
- package/dist/test/middlewares/correlationId.test.js +19 -0
- package/dist/test/middlewares/credentials.test.d.ts +1 -0
- package/dist/test/middlewares/credentials.test.js +37 -0
- package/dist/test/middlewares/errors.test.d.ts +1 -0
- package/dist/test/middlewares/errors.test.js +64 -0
- package/dist/test/middlewares/filters.test.d.ts +1 -0
- package/dist/test/middlewares/filters.test.js +26 -0
- package/dist/test/middlewares/finish.test.d.ts +1 -0
- package/dist/test/middlewares/finish.test.js +68 -0
- package/dist/test/middlewares/logger.test.d.ts +1 -0
- package/dist/test/middlewares/logger.test.js +41 -0
- package/dist/test/middlewares/notFound.test.d.ts +1 -0
- package/dist/test/middlewares/notFound.test.js +27 -0
- package/dist/test/middlewares/selects.test.d.ts +1 -0
- package/dist/test/middlewares/selects.test.js +21 -0
- package/dist/test/resources/cache.test.d.ts +1 -0
- package/dist/test/resources/cache.test.js +25 -0
- package/dist/test/resources/logger.test.d.ts +1 -0
- package/dist/test/resources/logger.test.js +67 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +59 -0
- package/src/errors.ts +34 -0
- package/src/handler.ts +404 -0
- package/src/httpErrors.ts +44 -0
- package/src/index.ts +10 -0
- package/src/integration.ts +129 -0
- package/src/middlewares/correlationId.ts +19 -0
- package/src/middlewares/credentials.ts +35 -0
- package/src/middlewares/errors.ts +30 -0
- package/src/middlewares/filters.ts +51 -0
- package/src/middlewares/finish.ts +24 -0
- package/src/middlewares/logger.ts +36 -0
- package/src/middlewares/notFound.ts +13 -0
- package/src/middlewares/selects.ts +31 -0
- package/src/resources/cache.ts +34 -0
- package/src/resources/context.ts +113 -0
- package/src/resources/logger.ts +57 -0
- package/src/resources/provider.ts +120 -0
- package/test/errors.test.ts +17 -0
- package/test/handler.test.ts +178 -0
- package/test/middlewares/correlationId.test.ts +26 -0
- package/test/middlewares/credentials.test.ts +52 -0
- package/test/middlewares/errors.test.ts +78 -0
- package/test/middlewares/filters.test.ts +39 -0
- package/test/middlewares/finish.test.ts +81 -0
- package/test/middlewares/logger.test.ts +57 -0
- package/test/middlewares/notFound.test.ts +32 -0
- package/test/middlewares/selects.test.ts +34 -0
- package/test/resources/cache.test.ts +31 -0
- package/test/resources/logger.test.ts +80 -0
- package/tsconfig.json +29 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { InvalidHandler } from './errors.js';
|
|
3
|
+
import { UnauthorizedError, BadRequestError } from './httpErrors.js';
|
|
4
|
+
function assertValidPath(path) {
|
|
5
|
+
if (!path.startsWith('/')) {
|
|
6
|
+
throw new InvalidHandler(`The provided path '${path}' is invalid. All paths must start with a '/'.`);
|
|
7
|
+
}
|
|
8
|
+
if (path.length > 1 && path.endsWith('/')) {
|
|
9
|
+
throw new InvalidHandler(`The provided path '${path}' is invalid. Paths must not end with a '/'.`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function parsePath(path) {
|
|
13
|
+
const pathParts = path.split('/');
|
|
14
|
+
const lastPart = pathParts.at(-1);
|
|
15
|
+
if (pathParts.length > 1 && lastPart && lastPart.startsWith(':')) {
|
|
16
|
+
pathParts.pop();
|
|
17
|
+
}
|
|
18
|
+
return { pathWithIdentifier: path, path: pathParts.join('/') };
|
|
19
|
+
}
|
|
20
|
+
function assertCreateItemRequestPayload(body) {
|
|
21
|
+
if (typeof body !== 'object' || body === null) {
|
|
22
|
+
throw new BadRequestError('Invalid CreateItemRequestPayload');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function assertUpdateItemRequestPayload(body) {
|
|
26
|
+
if (typeof body !== 'object' || body === null) {
|
|
27
|
+
throw new BadRequestError('Invalid UpdateItemRequestPayload');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function assertWebhookParseRequestPayload(body) {
|
|
31
|
+
if (typeof body !== 'object' || body === null) {
|
|
32
|
+
throw new BadRequestError('Invalid WebhookParseRequestPayload');
|
|
33
|
+
}
|
|
34
|
+
if (!('payload' in body) || body.payload !== 'string') {
|
|
35
|
+
throw new BadRequestError("Missing required 'payload' property in WebhookParseRequestPayload");
|
|
36
|
+
}
|
|
37
|
+
if (!('url' in body) || body.url !== 'string') {
|
|
38
|
+
throw new BadRequestError("Missing required 'url' property in WebhookParseRequestPayload");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function assertWebhookSubscriptionRequestPayload(body) {
|
|
42
|
+
if (typeof body !== 'object' || body === null) {
|
|
43
|
+
throw new BadRequestError('Invalid WebhookSubscriptionRequestPayload');
|
|
44
|
+
}
|
|
45
|
+
if (!('itemPath' in body) || body.itemPath !== 'string') {
|
|
46
|
+
throw new BadRequestError("Missing required 'itemPath' property in WebhookSubscriptionRequestPayload");
|
|
47
|
+
}
|
|
48
|
+
if (!('targetUrl' in body) || body.targetUrl !== 'string') {
|
|
49
|
+
throw new BadRequestError("Missing required 'targetUrl' property in WebhookSubscriptionRequestPayload");
|
|
50
|
+
}
|
|
51
|
+
if (!('action' in body) || body.action !== 'string') {
|
|
52
|
+
throw new BadRequestError("Missing required 'action' property in WebhookSubscriptionRequestPayload");
|
|
53
|
+
}
|
|
54
|
+
if (!['start', 'stop'].includes(body.action)) {
|
|
55
|
+
throw new BadRequestError("Invalid value for 'action' property in WebhookSubscriptionRequestPayload");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export class Handler {
|
|
59
|
+
path;
|
|
60
|
+
pathWithIdentifier;
|
|
61
|
+
handlers;
|
|
62
|
+
constructor(inputPath, handlers) {
|
|
63
|
+
// Build paths.
|
|
64
|
+
assertValidPath(inputPath);
|
|
65
|
+
const { pathWithIdentifier, path } = parsePath(inputPath);
|
|
66
|
+
this.pathWithIdentifier = pathWithIdentifier;
|
|
67
|
+
this.path = path;
|
|
68
|
+
this.handlers = handlers;
|
|
69
|
+
}
|
|
70
|
+
generate() {
|
|
71
|
+
const router = Router({ caseSensitive: true });
|
|
72
|
+
console.debug(`\x1b[33mMounting handler at path ${this.pathWithIdentifier}`);
|
|
73
|
+
if (this.handlers.getCollection) {
|
|
74
|
+
const handler = this.handlers.getCollection;
|
|
75
|
+
console.debug(` Enabling GET ${this.path}`);
|
|
76
|
+
router.get(this.path, async (req, res) => {
|
|
77
|
+
if (!res.locals.credentials) {
|
|
78
|
+
throw new UnauthorizedError();
|
|
79
|
+
}
|
|
80
|
+
const collection = await handler({
|
|
81
|
+
credentials: res.locals.credentials,
|
|
82
|
+
selects: res.locals.selects,
|
|
83
|
+
filters: res.locals.filters,
|
|
84
|
+
logger: res.locals.logger,
|
|
85
|
+
params: req.params,
|
|
86
|
+
query: req.query,
|
|
87
|
+
});
|
|
88
|
+
res.status(200).send(collection);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
if (this.handlers.createItem) {
|
|
92
|
+
const handler = this.handlers.createItem;
|
|
93
|
+
console.debug(` Enabling POST ${this.path}`);
|
|
94
|
+
router.post(this.path, async (req, res) => {
|
|
95
|
+
if (!res.locals.credentials) {
|
|
96
|
+
throw new UnauthorizedError();
|
|
97
|
+
}
|
|
98
|
+
assertCreateItemRequestPayload(req.body);
|
|
99
|
+
const createItemSummary = await handler({
|
|
100
|
+
credentials: res.locals.credentials,
|
|
101
|
+
body: req.body,
|
|
102
|
+
logger: res.locals.logger,
|
|
103
|
+
params: req.params,
|
|
104
|
+
query: req.query,
|
|
105
|
+
});
|
|
106
|
+
res.status(201).send(createItemSummary);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
if (this.handlers.getItem) {
|
|
110
|
+
const handler = this.handlers.getItem;
|
|
111
|
+
console.debug(` Enabling GET ${this.pathWithIdentifier}`);
|
|
112
|
+
router.get(this.pathWithIdentifier, async (req, res) => {
|
|
113
|
+
if (!res.locals.credentials) {
|
|
114
|
+
throw new UnauthorizedError();
|
|
115
|
+
}
|
|
116
|
+
const item = await handler({
|
|
117
|
+
credentials: res.locals.credentials,
|
|
118
|
+
logger: res.locals.logger,
|
|
119
|
+
params: req.params,
|
|
120
|
+
query: req.query,
|
|
121
|
+
});
|
|
122
|
+
res.status(200).send(item);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (this.handlers.updateItem) {
|
|
126
|
+
const handler = this.handlers.updateItem;
|
|
127
|
+
console.debug(` Enabling PATCH ${this.pathWithIdentifier}`);
|
|
128
|
+
router.patch(this.pathWithIdentifier, async (req, res) => {
|
|
129
|
+
if (!res.locals.credentials) {
|
|
130
|
+
throw new UnauthorizedError();
|
|
131
|
+
}
|
|
132
|
+
assertUpdateItemRequestPayload(req.body);
|
|
133
|
+
const item = await handler({
|
|
134
|
+
credentials: res.locals.credentials,
|
|
135
|
+
body: req.body,
|
|
136
|
+
logger: res.locals.logger,
|
|
137
|
+
params: req.params,
|
|
138
|
+
query: req.query,
|
|
139
|
+
});
|
|
140
|
+
res.status(200).send(item);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if (this.handlers.deleteItem) {
|
|
144
|
+
const handler = this.handlers.deleteItem;
|
|
145
|
+
console.debug(` Enabling DELETE ${this.pathWithIdentifier}`);
|
|
146
|
+
router.delete(this.pathWithIdentifier, async (req, res) => {
|
|
147
|
+
if (!res.locals.credentials) {
|
|
148
|
+
throw new UnauthorizedError();
|
|
149
|
+
}
|
|
150
|
+
await handler({
|
|
151
|
+
credentials: res.locals.credentials,
|
|
152
|
+
logger: res.locals.logger,
|
|
153
|
+
params: req.params,
|
|
154
|
+
query: req.query,
|
|
155
|
+
});
|
|
156
|
+
res.status(204).send(null);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
if (this.handlers.getCredentialAccount) {
|
|
160
|
+
const handler = this.handlers.getCredentialAccount;
|
|
161
|
+
console.debug(` Enabling GET ${this.pathWithIdentifier}`);
|
|
162
|
+
router.get(this.pathWithIdentifier, async (req, res) => {
|
|
163
|
+
if (!res.locals.credentials) {
|
|
164
|
+
throw new UnauthorizedError();
|
|
165
|
+
}
|
|
166
|
+
const credentialAccount = await handler({
|
|
167
|
+
credentials: res.locals.credentials,
|
|
168
|
+
logger: res.locals.logger,
|
|
169
|
+
params: req.params,
|
|
170
|
+
query: req.query,
|
|
171
|
+
});
|
|
172
|
+
res.status(200).send(credentialAccount);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
if (this.handlers.acknowledgeWebhooks) {
|
|
176
|
+
const handler = this.handlers.acknowledgeWebhooks;
|
|
177
|
+
console.debug(` Enabling POST ${this.pathWithIdentifier}`);
|
|
178
|
+
router.post(this.pathWithIdentifier, async (req, res) => {
|
|
179
|
+
assertWebhookParseRequestPayload(req.body);
|
|
180
|
+
const response = await handler({
|
|
181
|
+
logger: res.locals.logger,
|
|
182
|
+
params: req.params,
|
|
183
|
+
query: req.query,
|
|
184
|
+
body: req.body,
|
|
185
|
+
});
|
|
186
|
+
res.status(200).send(response);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
if (this.handlers.parseWebhooks) {
|
|
190
|
+
const handler = this.handlers.parseWebhooks;
|
|
191
|
+
console.debug(` Enabling POST ${this.pathWithIdentifier}`);
|
|
192
|
+
router.post(this.pathWithIdentifier, async (req, res) => {
|
|
193
|
+
assertWebhookParseRequestPayload(req.body);
|
|
194
|
+
const response = await handler({
|
|
195
|
+
logger: res.locals.logger,
|
|
196
|
+
params: req.params,
|
|
197
|
+
query: req.query,
|
|
198
|
+
body: req.body,
|
|
199
|
+
});
|
|
200
|
+
res.status(200).send(response);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
if (this.handlers.updateWebhookSubscriptions) {
|
|
204
|
+
const handler = this.handlers.updateWebhookSubscriptions;
|
|
205
|
+
console.debug(` Enabling PUT ${this.pathWithIdentifier}`);
|
|
206
|
+
router.put(this.pathWithIdentifier, async (req, res) => {
|
|
207
|
+
if (!res.locals.credentials) {
|
|
208
|
+
throw new UnauthorizedError();
|
|
209
|
+
}
|
|
210
|
+
assertWebhookSubscriptionRequestPayload(req.body);
|
|
211
|
+
const response = await handler({
|
|
212
|
+
credentials: res.locals.credentials,
|
|
213
|
+
body: req.body,
|
|
214
|
+
logger: res.locals.logger,
|
|
215
|
+
params: req.params,
|
|
216
|
+
query: req.query,
|
|
217
|
+
});
|
|
218
|
+
res.status(204).send(response);
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
console.debug(`\x1b[0m`);
|
|
222
|
+
return router;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare class HttpError extends Error {
|
|
2
|
+
readonly status: number;
|
|
3
|
+
constructor(message: string, status: number);
|
|
4
|
+
}
|
|
5
|
+
export declare class BadRequestError extends HttpError {
|
|
6
|
+
constructor(message?: string);
|
|
7
|
+
}
|
|
8
|
+
export declare class UnauthorizedError extends HttpError {
|
|
9
|
+
constructor(message?: string);
|
|
10
|
+
}
|
|
11
|
+
export declare class NotFoundError extends HttpError {
|
|
12
|
+
constructor(message?: string);
|
|
13
|
+
}
|
|
14
|
+
export declare class TimeoutError extends HttpError {
|
|
15
|
+
constructor(message?: string);
|
|
16
|
+
}
|
|
17
|
+
export declare class UnprocessableEntityError extends HttpError {
|
|
18
|
+
constructor(message?: string);
|
|
19
|
+
}
|
|
20
|
+
export declare class RateLimitExceededError extends HttpError {
|
|
21
|
+
constructor(message?: string);
|
|
22
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export class HttpError extends Error {
|
|
2
|
+
status;
|
|
3
|
+
constructor(message, status) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.status = status;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export class BadRequestError extends HttpError {
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(message || 'Bad request', 400);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export class UnauthorizedError extends HttpError {
|
|
14
|
+
constructor(message) {
|
|
15
|
+
super(message || 'Unauthorized', 401);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export class NotFoundError extends HttpError {
|
|
19
|
+
constructor(message) {
|
|
20
|
+
super(message || 'Not found', 404);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export class TimeoutError extends HttpError {
|
|
24
|
+
constructor(message) {
|
|
25
|
+
super(message || 'Not found', 408);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export class UnprocessableEntityError extends HttpError {
|
|
29
|
+
constructor(message) {
|
|
30
|
+
super(message || 'Unprocessable Entity', 422);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export class RateLimitExceededError extends HttpError {
|
|
34
|
+
constructor(message) {
|
|
35
|
+
super(message || 'Unprocessable Entity', 429);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export * as Api from '@unito/integration-api';
|
|
2
|
+
export { default as Integration } from './integration.js';
|
|
3
|
+
export * from './handler.js';
|
|
4
|
+
export { Provider, type Response as ProviderResponse, type RequestOptions as ProviderRequestOptions, } from './resources/provider.js';
|
|
5
|
+
export * as HttpErrors from './httpErrors.js';
|
|
6
|
+
export * from './resources/context.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export * as Api from '@unito/integration-api';
|
|
2
|
+
export { default as Integration } from './integration.js';
|
|
3
|
+
export * from './handler.js';
|
|
4
|
+
export { Provider, } from './resources/provider.js';
|
|
5
|
+
export * as HttpErrors from './httpErrors.js';
|
|
6
|
+
export * from './resources/context.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { InvalidHandler } from './errors.js';
|
|
3
|
+
import correlationIdMiddleware from './middlewares/correlationId.js';
|
|
4
|
+
import loggerMiddleware from './middlewares/logger.js';
|
|
5
|
+
import credentialsMiddleware from './middlewares/credentials.js';
|
|
6
|
+
import selectsMiddleware from './middlewares/selects.js';
|
|
7
|
+
import errorsMiddleware from './middlewares/errors.js';
|
|
8
|
+
import finishMiddleware from './middlewares/finish.js';
|
|
9
|
+
import notFoundMiddleware from './middlewares/notFound.js';
|
|
10
|
+
import { initializeCache } from './resources/cache.js';
|
|
11
|
+
import { Handler } from './handler.js';
|
|
12
|
+
function printErrorMessage(message) {
|
|
13
|
+
console.error();
|
|
14
|
+
console.error(`\x1b[31m Oups! Something went wrong! \x1b[0m`);
|
|
15
|
+
console.error(message);
|
|
16
|
+
}
|
|
17
|
+
export default class Integration {
|
|
18
|
+
handlers;
|
|
19
|
+
instance = undefined;
|
|
20
|
+
cache = undefined;
|
|
21
|
+
constructor() {
|
|
22
|
+
this.handlers = [];
|
|
23
|
+
}
|
|
24
|
+
addHandler(path, handlers) {
|
|
25
|
+
if (this.instance) {
|
|
26
|
+
printErrorMessage(`
|
|
27
|
+
It seems like you're trying to add a handler after the server has already started. This is probably
|
|
28
|
+
a mistake as calling the start() function essentially starts the server and ignore any further change.
|
|
29
|
+
To fix this error, move all your addHandler() calls before the start() function.
|
|
30
|
+
`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
this.handlers.push(new Handler(path, handlers));
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (error instanceof InvalidHandler) {
|
|
38
|
+
printErrorMessage(`
|
|
39
|
+
It seems like you're trying to add an invalid handler. The exact error message is:
|
|
40
|
+
|
|
41
|
+
> ${error.message}
|
|
42
|
+
|
|
43
|
+
You must address this issue before trying again.
|
|
44
|
+
`);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
printErrorMessage(`
|
|
48
|
+
An unexpected error happened as we were trying to add your handler. The exact error message is;
|
|
49
|
+
|
|
50
|
+
> ${error.message}
|
|
51
|
+
`);
|
|
52
|
+
}
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
start() {
|
|
57
|
+
// Initialize the cache.
|
|
58
|
+
this.cache = initializeCache();
|
|
59
|
+
// Express Server initialization
|
|
60
|
+
const app = express();
|
|
61
|
+
// Parse query strings with https://github.com/ljharb/qs.
|
|
62
|
+
app.set('query parser', 'extended');
|
|
63
|
+
app.use(express.json());
|
|
64
|
+
// Must be one of the first handlers (to catch all the errors).
|
|
65
|
+
app.use(finishMiddleware);
|
|
66
|
+
app.use(correlationIdMiddleware);
|
|
67
|
+
app.use(loggerMiddleware);
|
|
68
|
+
app.use(credentialsMiddleware);
|
|
69
|
+
app.use(selectsMiddleware);
|
|
70
|
+
// Load handlers as needed.
|
|
71
|
+
if (this.handlers.length) {
|
|
72
|
+
for (const handler of this.handlers) {
|
|
73
|
+
app.use(handler.generate());
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
printErrorMessage(`
|
|
78
|
+
It seems like you're trying to start the server without any handler. This is probably a mistake as the
|
|
79
|
+
server wouldn't expose any route. To fix this error, add at least one handler before calling the start()
|
|
80
|
+
function.
|
|
81
|
+
`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
// Must be the (last - 1) handler.
|
|
85
|
+
app.use(errorsMiddleware);
|
|
86
|
+
// Must be the last handler.
|
|
87
|
+
app.use(notFoundMiddleware);
|
|
88
|
+
this.instance = app.listen(process.env.PORT || 9200, () => console.info(`Server started on port ${process.env.PORT || 9200}.`));
|
|
89
|
+
// Trap exit signals.
|
|
90
|
+
['SIGTERM', 'SIGINT', 'SIGUSR2'].forEach(signalType => {
|
|
91
|
+
process.once(signalType, async () => {
|
|
92
|
+
console.info(`Received termination signal ${signalType}. Exiting.`);
|
|
93
|
+
try {
|
|
94
|
+
if (this.instance) {
|
|
95
|
+
this.instance.close();
|
|
96
|
+
}
|
|
97
|
+
if (this.cache) {
|
|
98
|
+
await this.cache.quit();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (e) {
|
|
102
|
+
console.error('Failed to gracefully exit', e);
|
|
103
|
+
}
|
|
104
|
+
process.exit();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
declare global {
|
|
3
|
+
namespace Express {
|
|
4
|
+
interface Locals {
|
|
5
|
+
correlationId: string;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
declare const middleware: (req: Request, res: Response, next: NextFunction) => void;
|
|
10
|
+
export default middleware;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
declare global {
|
|
3
|
+
namespace Express {
|
|
4
|
+
interface Locals {
|
|
5
|
+
credentials: Credentials;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export type Credentials = Record<string, unknown>;
|
|
10
|
+
declare const middleware: (req: Request, res: Response, next: NextFunction) => void;
|
|
11
|
+
export default middleware;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { BadRequestError } from '../httpErrors.js';
|
|
2
|
+
const CREDENTIALS_HEADER = 'X-Unito-Credentials';
|
|
3
|
+
const middleware = (req, res, next) => {
|
|
4
|
+
const credentialsHeader = req.header(CREDENTIALS_HEADER);
|
|
5
|
+
if (credentialsHeader) {
|
|
6
|
+
let credentials;
|
|
7
|
+
try {
|
|
8
|
+
credentials = JSON.parse(Buffer.from(credentialsHeader, 'base64').toString('utf8'));
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
throw new BadRequestError(`Malformed HTTP header ${CREDENTIALS_HEADER}`);
|
|
12
|
+
}
|
|
13
|
+
res.locals.credentials = credentials;
|
|
14
|
+
}
|
|
15
|
+
next();
|
|
16
|
+
};
|
|
17
|
+
export default middleware;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { HttpError } from '../httpErrors.js';
|
|
2
|
+
const middleware = (err, _req, res, next) => {
|
|
3
|
+
if (res.headersSent) {
|
|
4
|
+
return next(err);
|
|
5
|
+
}
|
|
6
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
7
|
+
res.locals.logger.error(err);
|
|
8
|
+
}
|
|
9
|
+
if (err instanceof HttpError) {
|
|
10
|
+
return res.status(err.status).json(err.message);
|
|
11
|
+
}
|
|
12
|
+
const originalError = {
|
|
13
|
+
code: err.name,
|
|
14
|
+
message: err.message,
|
|
15
|
+
};
|
|
16
|
+
res.status(500).json({
|
|
17
|
+
code: '500',
|
|
18
|
+
message: 'Oops! Something went wrong',
|
|
19
|
+
originalError,
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
export default middleware;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { OperatorType } from '@unito/integration-api';
|
|
2
|
+
import { Request, Response, NextFunction } from 'express';
|
|
3
|
+
declare global {
|
|
4
|
+
namespace Express {
|
|
5
|
+
interface Locals {
|
|
6
|
+
filters: Filter[];
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export interface Filter {
|
|
11
|
+
field: string;
|
|
12
|
+
operator: OperatorType;
|
|
13
|
+
values: string[] | undefined;
|
|
14
|
+
}
|
|
15
|
+
declare const middleware: (req: Request, res: Response, next: NextFunction) => void;
|
|
16
|
+
export default middleware;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { OperatorType } from '@unito/integration-api';
|
|
2
|
+
// The operators are ordered by their symbol length, in descending order.
|
|
3
|
+
// This is necessary because the symbol of an operator can be
|
|
4
|
+
// a subset of the symbol of another operator.
|
|
5
|
+
//
|
|
6
|
+
// For example, the symbol "=" (EQUAL) is a subset of the symbol "!=" (NOT_EQUAL).
|
|
7
|
+
const ORDERED_OPERATORS = Object.values(OperatorType).sort((o1, o2) => o1.length - o2.length);
|
|
8
|
+
const middleware = (req, res, next) => {
|
|
9
|
+
const rawFilters = req.query.filter;
|
|
10
|
+
res.locals.filters = [];
|
|
11
|
+
if (typeof rawFilters === 'string') {
|
|
12
|
+
for (const rawFilter of rawFilters.split(',')) {
|
|
13
|
+
for (const operator of ORDERED_OPERATORS) {
|
|
14
|
+
if (rawFilter.includes(operator)) {
|
|
15
|
+
const [field, valuesRaw] = rawFilter.split(operator, 2);
|
|
16
|
+
const values = valuesRaw ? valuesRaw.split('|').map(decodeURIComponent) : [];
|
|
17
|
+
res.locals.filters.push({ field: field, operator, values });
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
next();
|
|
24
|
+
};
|
|
25
|
+
export default middleware;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import Logger from '../resources/logger.js';
|
|
3
|
+
declare global {
|
|
4
|
+
namespace Express {
|
|
5
|
+
interface Locals {
|
|
6
|
+
logger: Logger;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
declare const middleware: (req: Request, res: Response, next: NextFunction) => void;
|
|
11
|
+
export default middleware;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const middleware = (req, res, next) => {
|
|
2
|
+
if (req.originalUrl !== '/health') {
|
|
3
|
+
res.on('finish', function () {
|
|
4
|
+
const loggerLevel = res.statusCode >= 500 ? 'error' : 'info';
|
|
5
|
+
res.locals.logger[loggerLevel](`${req.method} ${req.originalUrl} ${res.statusCode}`);
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
next();
|
|
9
|
+
};
|
|
10
|
+
export default middleware;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import Logger from '../resources/logger.js';
|
|
3
|
+
declare global {
|
|
4
|
+
namespace Express {
|
|
5
|
+
interface Locals {
|
|
6
|
+
correlationId: string;
|
|
7
|
+
logger: Logger;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
declare const middleware: (req: Request, res: Response, next: NextFunction) => void;
|
|
12
|
+
export default middleware;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import Logger from '../resources/logger.js';
|
|
2
|
+
const ADDITIONAL_CONTEXT_HEADER = 'X-Unito-Additional-Logging-Context';
|
|
3
|
+
const middleware = (req, res, next) => {
|
|
4
|
+
const logger = new Logger({ correlation_id: res.locals.correlationId });
|
|
5
|
+
res.locals.logger = logger;
|
|
6
|
+
const rawAdditionalContext = req.header(ADDITIONAL_CONTEXT_HEADER);
|
|
7
|
+
if (typeof rawAdditionalContext === 'string') {
|
|
8
|
+
try {
|
|
9
|
+
const additionalContext = JSON.parse(Buffer.from(rawAdditionalContext, 'base64').toString('utf8'));
|
|
10
|
+
logger.decorate(additionalContext);
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
logger.warn(`Malformed HTTP header ${ADDITIONAL_CONTEXT_HEADER}: ${rawAdditionalContext}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
next();
|
|
17
|
+
};
|
|
18
|
+
export default middleware;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
declare global {
|
|
3
|
+
namespace Express {
|
|
4
|
+
interface Locals {
|
|
5
|
+
selects: string[];
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
declare const middleware: (req: Request, res: Response, next: NextFunction) => void;
|
|
10
|
+
export default middleware;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../src/resources/cache.ts"],"names":[],"mappings":";AAAA,OAAO,EAAiC,aAAa,EAAE,MAAM,UAAU,CAAC;AAMxE,eAAO,IAAI,QAAQ,EAAE,aAAa,CAAC;AACnC,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC;AACxD,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,UAAU,CAAC;AAElF,wBAAgB,eAAe,IAAI,aAAa,CAyB/C;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,EAAE,SAAS,GAAE,iBAA2B,GAAG,MAAM,CAwBnG"}
|