@discover-cloud/shared 1.0.4 → 1.0.6
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/dist/http/service-client.d.ts +20 -15
- package/dist/http/service-client.js +43 -25
- package/dist/middleware/index.d.ts +1 -0
- package/dist/middleware/index.js +1 -0
- package/dist/middleware/validated-merge.middleware.d.ts +20 -0
- package/dist/middleware/validated-merge.middleware.js +34 -0
- package/package.json +1 -1
|
@@ -3,29 +3,34 @@ import { Request } from "express";
|
|
|
3
3
|
/**
|
|
4
4
|
* SERVICE CLIENT
|
|
5
5
|
* ───────────────
|
|
6
|
-
* HTTP client for service-to-service
|
|
7
|
-
* Forwards the internal JWT and request ID from the current Express request.
|
|
6
|
+
* HTTP client for service-to-service communication.
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
8
|
+
* Two call patterns:
|
|
9
|
+
*
|
|
10
|
+
* 1. JWT-forwarding (user-initiated requests)
|
|
11
|
+
* Pass the current Express Request — Authorization + x-request-id
|
|
12
|
+
* are forwarded automatically.
|
|
13
|
+
* Use: getWithAuth, postWithAuth, patchWithAuth, deleteWithAuth
|
|
14
|
+
*
|
|
15
|
+
* 2. Internal calls (service-to-service, no incoming request)
|
|
16
|
+
* Pass explicit headers — used by UserServiceClient in auth service
|
|
17
|
+
* where calls originate from service logic, not from an HTTP handler.
|
|
18
|
+
* Use: get, post, patch, delete
|
|
19
|
+
*
|
|
20
|
+
* Retry strategy: network errors + 5xx only. Never retries 4xx —
|
|
21
|
+
* those are deterministic caller errors.
|
|
17
22
|
*/
|
|
18
23
|
export declare class ServiceClient {
|
|
19
24
|
readonly http: AxiosInstance;
|
|
20
25
|
constructor(baseURL: string);
|
|
21
|
-
private setupRetry;
|
|
22
26
|
getWithAuth<T = unknown>(url: string, req: Request, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
|
|
23
27
|
postWithAuth<T = unknown>(url: string, data: unknown, req: Request, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
|
|
24
28
|
patchWithAuth<T = unknown>(url: string, data: unknown, req: Request, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
|
|
25
29
|
deleteWithAuth<T = unknown>(url: string, req: Request, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
get<T = unknown>(url: string, headers: Record<string, string>, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
|
|
31
|
+
post<T = unknown>(url: string, data: unknown, headers: Record<string, string>, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
|
|
32
|
+
patch<T = unknown>(url: string, data: unknown, headers: Record<string, string>, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
|
|
33
|
+
delete<T = unknown>(url: string, headers: Record<string, string>, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
|
|
30
34
|
private mergeAuth;
|
|
35
|
+
private mergeHeaders;
|
|
31
36
|
}
|
|
@@ -9,17 +9,22 @@ const axios_retry_1 = __importDefault(require("axios-retry"));
|
|
|
9
9
|
/**
|
|
10
10
|
* SERVICE CLIENT
|
|
11
11
|
* ───────────────
|
|
12
|
-
* HTTP client for service-to-service
|
|
13
|
-
* Forwards the internal JWT and request ID from the current Express request.
|
|
12
|
+
* HTTP client for service-to-service communication.
|
|
14
13
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
14
|
+
* Two call patterns:
|
|
15
|
+
*
|
|
16
|
+
* 1. JWT-forwarding (user-initiated requests)
|
|
17
|
+
* Pass the current Express Request — Authorization + x-request-id
|
|
18
|
+
* are forwarded automatically.
|
|
19
|
+
* Use: getWithAuth, postWithAuth, patchWithAuth, deleteWithAuth
|
|
20
|
+
*
|
|
21
|
+
* 2. Internal calls (service-to-service, no incoming request)
|
|
22
|
+
* Pass explicit headers — used by UserServiceClient in auth service
|
|
23
|
+
* where calls originate from service logic, not from an HTTP handler.
|
|
24
|
+
* Use: get, post, patch, delete
|
|
25
|
+
*
|
|
26
|
+
* Retry strategy: network errors + 5xx only. Never retries 4xx —
|
|
27
|
+
* those are deterministic caller errors.
|
|
23
28
|
*/
|
|
24
29
|
class ServiceClient {
|
|
25
30
|
constructor(baseURL) {
|
|
@@ -27,15 +32,10 @@ class ServiceClient {
|
|
|
27
32
|
baseURL,
|
|
28
33
|
timeout: 8000,
|
|
29
34
|
});
|
|
30
|
-
this.setupRetry();
|
|
31
|
-
}
|
|
32
|
-
setupRetry() {
|
|
33
35
|
(0, axios_retry_1.default)(this.http, {
|
|
34
36
|
retries: 3,
|
|
35
37
|
retryDelay: axios_retry_1.default.exponentialDelay,
|
|
36
38
|
retryCondition: (err) => {
|
|
37
|
-
// Retry on network errors or 5xx only
|
|
38
|
-
// Do NOT retry 4xx — those are deterministic failures
|
|
39
39
|
if (axios_retry_1.default.isNetworkError(err))
|
|
40
40
|
return true;
|
|
41
41
|
const status = err.response?.status ?? 0;
|
|
@@ -44,10 +44,8 @@ class ServiceClient {
|
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
46
|
/* ----------------------------------------------------------------
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
- Authorization: Bearer <internal-jwt>
|
|
50
|
-
- x-request-id: <propagated request ID>
|
|
47
|
+
Pattern 1 — JWT-forwarding (pass Express req)
|
|
48
|
+
Forwards Authorization header + x-request-id automatically.
|
|
51
49
|
---------------------------------------------------------------- */
|
|
52
50
|
async getWithAuth(url, req, config) {
|
|
53
51
|
return this.http.get(url, this.mergeAuth(req, config));
|
|
@@ -61,17 +59,28 @@ class ServiceClient {
|
|
|
61
59
|
async deleteWithAuth(url, req, config) {
|
|
62
60
|
return this.http.delete(url, this.mergeAuth(req, config));
|
|
63
61
|
}
|
|
62
|
+
/* ----------------------------------------------------------------
|
|
63
|
+
Pattern 2 — Internal calls (explicit headers, no Express req)
|
|
64
|
+
Used for service-initiated calls (e.g. auth service → user service)
|
|
65
|
+
where there is no incoming HTTP request to forward from.
|
|
66
|
+
---------------------------------------------------------------- */
|
|
67
|
+
async get(url, headers, config) {
|
|
68
|
+
return this.http.get(url, this.mergeHeaders(headers, config));
|
|
69
|
+
}
|
|
70
|
+
async post(url, data, headers, config) {
|
|
71
|
+
return this.http.post(url, data, this.mergeHeaders(headers, config));
|
|
72
|
+
}
|
|
73
|
+
async patch(url, data, headers, config) {
|
|
74
|
+
return this.http.patch(url, data, this.mergeHeaders(headers, config));
|
|
75
|
+
}
|
|
76
|
+
async delete(url, headers, config) {
|
|
77
|
+
return this.http.delete(url, this.mergeHeaders(headers, config));
|
|
78
|
+
}
|
|
64
79
|
/* ----------------------------------------------------------------
|
|
65
80
|
Private helpers
|
|
66
81
|
---------------------------------------------------------------- */
|
|
67
|
-
/**
|
|
68
|
-
* Merges Authorization + x-request-id into any provided config.
|
|
69
|
-
* The internal JWT is forwarded as-is — the gateway already minted it.
|
|
70
|
-
*/
|
|
71
82
|
mergeAuth(req, config) {
|
|
72
83
|
const authHeader = req.headers.authorization;
|
|
73
|
-
// req.id is set by requestId middleware — always a string at this point.
|
|
74
|
-
// Fall back to x-request-id header if somehow req.id isn't set yet.
|
|
75
84
|
const upstream = req.headers["x-request-id"];
|
|
76
85
|
const requestId = req.id
|
|
77
86
|
?? (Array.isArray(upstream) ? upstream[0] : upstream)
|
|
@@ -85,5 +94,14 @@ class ServiceClient {
|
|
|
85
94
|
},
|
|
86
95
|
};
|
|
87
96
|
}
|
|
97
|
+
mergeHeaders(headers, config) {
|
|
98
|
+
return {
|
|
99
|
+
...config,
|
|
100
|
+
headers: {
|
|
101
|
+
...config?.headers,
|
|
102
|
+
...headers,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
88
106
|
}
|
|
89
107
|
exports.ServiceClient = ServiceClient;
|
package/dist/middleware/index.js
CHANGED
|
@@ -18,3 +18,4 @@ __exportStar(require("./error-handler.middleware"), exports);
|
|
|
18
18
|
__exportStar(require("./validate.middleware"), exports);
|
|
19
19
|
__exportStar(require("./request-id.middleware"), exports);
|
|
20
20
|
__exportStar(require("./authorize.middleware"), exports);
|
|
21
|
+
__exportStar(require("./validated-merge.middleware"), exports);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from "express";
|
|
2
|
+
/**
|
|
3
|
+
* VALIDATED MERGE HELPERS
|
|
4
|
+
* ─────────────────────────
|
|
5
|
+
* Used in routes that run two Validator.validate calls (e.g. params + body,
|
|
6
|
+
* or params + query). Each call overwrites req.validated, so values from
|
|
7
|
+
* the first validator must be captured before the second runs.
|
|
8
|
+
*
|
|
9
|
+
* Pattern:
|
|
10
|
+
* Validator.validate(paramsSchema, "params"), // req.validated = { id }
|
|
11
|
+
* captureValidated("id", "_capturedId"), // stashes id
|
|
12
|
+
* Validator.validate(bodySchema, "body"), // req.validated = { role }
|
|
13
|
+
* mergeValidated("id", "_capturedId"), // req.validated = { id, role }
|
|
14
|
+
*
|
|
15
|
+
* The cast through unknown is required — TypeScript won't allow a direct
|
|
16
|
+
* cast from Request to Record<string, unknown> because they don't overlap
|
|
17
|
+
* enough. Casting to unknown first tells TypeScript we know what we're doing.
|
|
18
|
+
*/
|
|
19
|
+
export declare const captureValidated: (key: string, tempKey: string) => (req: Request, _res: Response, next: NextFunction) => void;
|
|
20
|
+
export declare const mergeValidated: (key: string, tempKey: string) => (req: Request, _res: Response, next: NextFunction) => void;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mergeValidated = exports.captureValidated = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* VALIDATED MERGE HELPERS
|
|
6
|
+
* ─────────────────────────
|
|
7
|
+
* Used in routes that run two Validator.validate calls (e.g. params + body,
|
|
8
|
+
* or params + query). Each call overwrites req.validated, so values from
|
|
9
|
+
* the first validator must be captured before the second runs.
|
|
10
|
+
*
|
|
11
|
+
* Pattern:
|
|
12
|
+
* Validator.validate(paramsSchema, "params"), // req.validated = { id }
|
|
13
|
+
* captureValidated("id", "_capturedId"), // stashes id
|
|
14
|
+
* Validator.validate(bodySchema, "body"), // req.validated = { role }
|
|
15
|
+
* mergeValidated("id", "_capturedId"), // req.validated = { id, role }
|
|
16
|
+
*
|
|
17
|
+
* The cast through unknown is required — TypeScript won't allow a direct
|
|
18
|
+
* cast from Request to Record<string, unknown> because they don't overlap
|
|
19
|
+
* enough. Casting to unknown first tells TypeScript we know what we're doing.
|
|
20
|
+
*/
|
|
21
|
+
const captureValidated = (key, tempKey) => (req, _res, next) => {
|
|
22
|
+
req[tempKey] =
|
|
23
|
+
req.validated[key];
|
|
24
|
+
next();
|
|
25
|
+
};
|
|
26
|
+
exports.captureValidated = captureValidated;
|
|
27
|
+
const mergeValidated = (key, tempKey) => (req, _res, next) => {
|
|
28
|
+
req.validated = {
|
|
29
|
+
...req.validated,
|
|
30
|
+
[key]: req[tempKey],
|
|
31
|
+
};
|
|
32
|
+
next();
|
|
33
|
+
};
|
|
34
|
+
exports.mergeValidated = mergeValidated;
|