@private.me/xbind 1.2.0 → 1.2.1
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/README.md +13 -24
- package/dist-standalone/_deps/ux-helpers/cjs/errors.d.ts +4 -0
- package/dist-standalone/_deps/ux-helpers/cjs/errors.d.ts.map +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/errors.js +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/errors.js.map +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/pagination.js +1 -83
- package/dist-standalone/_deps/ux-helpers/cjs/progress.js +1 -143
- package/dist-standalone/_deps/ux-helpers/cjs/search.js +1 -119
- package/dist-standalone/_deps/ux-helpers/cjs/types.js +1 -8
- package/dist-standalone/_deps/ux-helpers/errors.d.ts +4 -0
- package/dist-standalone/_deps/ux-helpers/errors.d.ts.map +1 -1
- package/dist-standalone/_deps/ux-helpers/errors.js +1 -253
- package/dist-standalone/_deps/ux-helpers/errors.js.map +1 -1
- package/dist-standalone/_deps/ux-helpers/index.js +1 -16
- package/dist-standalone/_deps/ux-helpers/pagination.js +1 -79
- package/dist-standalone/_deps/ux-helpers/progress.js +1 -138
- package/dist-standalone/_deps/ux-helpers/search.js +1 -116
- package/dist-standalone/_deps/ux-helpers/types.js +1 -7
- package/dist-standalone/agent.d.ts +9 -4
- package/dist-standalone/agent.js +20 -4
- package/dist-standalone/cjs/agent.js +20 -4
- package/dist-standalone/cjs/correlation-id.js +339 -0
- package/dist-standalone/cjs/http-status-map.js +571 -0
- package/dist-standalone/cjs/index.js +14 -1
- package/dist-standalone/cjs/types/error-response.js +56 -0
- package/dist-standalone/correlation-id.d.ts +222 -0
- package/dist-standalone/correlation-id.js +326 -0
- package/dist-standalone/http-status-map.d.ts +136 -0
- package/dist-standalone/http-status-map.js +561 -0
- package/dist-standalone/index.d.ts +2 -0
- package/dist-standalone/index.js +1 -0
- package/package.json +2 -2
- package/share1.dat +0 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module correlation-id
|
|
3
|
+
* Client-side correlation ID utilities for request tracking
|
|
4
|
+
*
|
|
5
|
+
* Correlation IDs enable distributed tracing across xBind agent operations,
|
|
6
|
+
* making it easier to debug issues, track requests across microservices,
|
|
7
|
+
* and correlate logs between client and server.
|
|
8
|
+
*
|
|
9
|
+
* Format: `req_{timestamp}_{random}`
|
|
10
|
+
* Example: `req_1716234567890_a3f5c9d2`
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { generateCorrelationId, attachCorrelationId } from '@private.me/xbind';
|
|
15
|
+
*
|
|
16
|
+
* // Generate a new correlation ID
|
|
17
|
+
* const id = generateCorrelationId();
|
|
18
|
+
*
|
|
19
|
+
* // Attach to request headers
|
|
20
|
+
* const headers = attachCorrelationId(new Headers(), id);
|
|
21
|
+
*
|
|
22
|
+
* // Validate format
|
|
23
|
+
* if (validateCorrelationId(id)) {
|
|
24
|
+
* console.log('Valid correlation ID');
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Correlation ID format specification
|
|
30
|
+
*/
|
|
31
|
+
export interface CorrelationIdSpec {
|
|
32
|
+
/** Prefix (always 'req') */
|
|
33
|
+
prefix: string;
|
|
34
|
+
/** Unix timestamp in milliseconds */
|
|
35
|
+
timestamp: number;
|
|
36
|
+
/** Random hex string (8 characters) */
|
|
37
|
+
random: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Standard header name for correlation ID
|
|
41
|
+
*/
|
|
42
|
+
export declare const CORRELATION_ID_HEADER = "X-Correlation-ID";
|
|
43
|
+
/**
|
|
44
|
+
* Alternative header names for compatibility
|
|
45
|
+
*/
|
|
46
|
+
export declare const CORRELATION_ID_ALIASES: readonly ["X-Request-ID", "X-Trace-ID", "X-Transaction-ID"];
|
|
47
|
+
/**
|
|
48
|
+
* Generate a new correlation ID
|
|
49
|
+
*
|
|
50
|
+
* Format: `req_{timestamp}_{random}`
|
|
51
|
+
* - `req`: Static prefix for identification
|
|
52
|
+
* - `timestamp`: Unix timestamp in milliseconds (13 digits)
|
|
53
|
+
* - `random`: 8-character hex string for uniqueness
|
|
54
|
+
*
|
|
55
|
+
* @returns New correlation ID string
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* const id = generateCorrelationId();
|
|
60
|
+
* // => "req_1716234567890_a3f5c9d2"
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export declare function generateCorrelationId(): string;
|
|
64
|
+
/**
|
|
65
|
+
* Validate correlation ID format
|
|
66
|
+
*
|
|
67
|
+
* Checks if the provided string matches the expected correlation ID format.
|
|
68
|
+
*
|
|
69
|
+
* @param id - String to validate
|
|
70
|
+
* @returns True if valid, false otherwise
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* validateCorrelationId('req_1716234567890_a3f5c9d2'); // => true
|
|
75
|
+
* validateCorrelationId('invalid'); // => false
|
|
76
|
+
* validateCorrelationId('req_123_abc'); // => false (wrong lengths)
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export declare function validateCorrelationId(id: unknown): id is string;
|
|
80
|
+
/**
|
|
81
|
+
* Parse correlation ID into components
|
|
82
|
+
*
|
|
83
|
+
* Extracts the timestamp and random components from a correlation ID.
|
|
84
|
+
*
|
|
85
|
+
* @param id - Correlation ID to parse
|
|
86
|
+
* @returns Parsed components or null if invalid
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* const spec = parseCorrelationId('req_1716234567890_a3f5c9d2');
|
|
91
|
+
* // => { prefix: 'req', timestamp: 1716234567890, random: 'a3f5c9d2' }
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export declare function parseCorrelationId(id: string): CorrelationIdSpec | null;
|
|
95
|
+
/**
|
|
96
|
+
* Attach correlation ID to request headers
|
|
97
|
+
*
|
|
98
|
+
* Adds the correlation ID to the X-Correlation-ID header.
|
|
99
|
+
* If no ID is provided, generates a new one.
|
|
100
|
+
* Compatible with both Headers API and plain objects.
|
|
101
|
+
*
|
|
102
|
+
* @param headers - Headers object or plain object
|
|
103
|
+
* @param id - Correlation ID (generates new if not provided)
|
|
104
|
+
* @returns Updated headers object
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* // With Headers API
|
|
109
|
+
* const headers = new Headers();
|
|
110
|
+
* attachCorrelationId(headers);
|
|
111
|
+
*
|
|
112
|
+
* // With plain object
|
|
113
|
+
* const headers = { 'Content-Type': 'application/json' };
|
|
114
|
+
* attachCorrelationId(headers, 'req_1716234567890_a3f5c9d2');
|
|
115
|
+
*
|
|
116
|
+
* // With existing correlation ID
|
|
117
|
+
* const id = generateCorrelationId();
|
|
118
|
+
* attachCorrelationId(headers, id);
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
export declare function attachCorrelationId<T extends Headers | Record<string, string>>(headers: T, id?: string): T;
|
|
122
|
+
/**
|
|
123
|
+
* Extract correlation ID from request headers
|
|
124
|
+
*
|
|
125
|
+
* Checks the standard header and common aliases.
|
|
126
|
+
*
|
|
127
|
+
* @param headers - Headers object or plain object
|
|
128
|
+
* @returns Correlation ID if found, undefined otherwise
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```typescript
|
|
132
|
+
* const headers = new Headers({
|
|
133
|
+
* 'X-Correlation-ID': 'req_1716234567890_a3f5c9d2'
|
|
134
|
+
* });
|
|
135
|
+
* const id = extractCorrelationId(headers);
|
|
136
|
+
* // => 'req_1716234567890_a3f5c9d2'
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
export declare function extractCorrelationId(headers: Headers | Record<string, string>): string | undefined;
|
|
140
|
+
/**
|
|
141
|
+
* Get or create correlation ID from headers
|
|
142
|
+
*
|
|
143
|
+
* Returns existing correlation ID from headers, or generates a new one if not found.
|
|
144
|
+
* Does NOT modify the input headers.
|
|
145
|
+
*
|
|
146
|
+
* @param headers - Headers to check
|
|
147
|
+
* @returns Existing or new correlation ID
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```typescript
|
|
151
|
+
* const headers = new Headers();
|
|
152
|
+
* const id = getOrCreateCorrelationId(headers);
|
|
153
|
+
* // => Generates new ID if not found in headers
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
export declare function getOrCreateCorrelationId(headers?: Headers | Record<string, string>): string;
|
|
157
|
+
/**
|
|
158
|
+
* Create a correlation ID from a timestamp
|
|
159
|
+
*
|
|
160
|
+
* Useful for testing or when you need deterministic IDs.
|
|
161
|
+
*
|
|
162
|
+
* @param timestamp - Unix timestamp in milliseconds
|
|
163
|
+
* @param random - Optional random component (generates if not provided)
|
|
164
|
+
* @returns Correlation ID
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```typescript
|
|
168
|
+
* const id = createCorrelationIdFromTimestamp(1716234567890);
|
|
169
|
+
* // => 'req_1716234567890_a3f5c9d2'
|
|
170
|
+
*
|
|
171
|
+
* const deterministicId = createCorrelationIdFromTimestamp(1716234567890, 'aaaaaaaa');
|
|
172
|
+
* // => 'req_1716234567890_aaaaaaaa'
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
export declare function createCorrelationIdFromTimestamp(timestamp: number, random?: string): string;
|
|
176
|
+
/**
|
|
177
|
+
* Calculate age of correlation ID in milliseconds
|
|
178
|
+
*
|
|
179
|
+
* @param id - Correlation ID
|
|
180
|
+
* @returns Age in milliseconds, or null if invalid
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```typescript
|
|
184
|
+
* const id = generateCorrelationId();
|
|
185
|
+
* setTimeout(() => {
|
|
186
|
+
* const age = getCorrelationIdAge(id);
|
|
187
|
+
* console.log(`Request age: ${age}ms`);
|
|
188
|
+
* }, 1000);
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
export declare function getCorrelationIdAge(id: string): number | null;
|
|
192
|
+
/**
|
|
193
|
+
* Check if correlation ID is expired
|
|
194
|
+
*
|
|
195
|
+
* @param id - Correlation ID
|
|
196
|
+
* @param maxAgeMs - Maximum age in milliseconds (default: 5 minutes)
|
|
197
|
+
* @returns True if expired, false otherwise
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```typescript
|
|
201
|
+
* const id = generateCorrelationId();
|
|
202
|
+
* if (isCorrelationIdExpired(id, 60000)) {
|
|
203
|
+
* console.log('Request older than 1 minute');
|
|
204
|
+
* }
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
export declare function isCorrelationIdExpired(id: string, maxAgeMs?: number): boolean;
|
|
208
|
+
/**
|
|
209
|
+
* Middleware helper for Express/Koa-style frameworks
|
|
210
|
+
*
|
|
211
|
+
* Automatically attaches correlation ID to incoming requests.
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* ```typescript
|
|
215
|
+
* import express from 'express';
|
|
216
|
+
* import { correlationIdMiddleware } from '@private.me/xbind';
|
|
217
|
+
*
|
|
218
|
+
* const app = express();
|
|
219
|
+
* app.use(correlationIdMiddleware());
|
|
220
|
+
* ```
|
|
221
|
+
*/
|
|
222
|
+
export declare function correlationIdMiddleware(): (req: any, res: any, next: any) => void;
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module correlation-id
|
|
3
|
+
* Client-side correlation ID utilities for request tracking
|
|
4
|
+
*
|
|
5
|
+
* Correlation IDs enable distributed tracing across xBind agent operations,
|
|
6
|
+
* making it easier to debug issues, track requests across microservices,
|
|
7
|
+
* and correlate logs between client and server.
|
|
8
|
+
*
|
|
9
|
+
* Format: `req_{timestamp}_{random}`
|
|
10
|
+
* Example: `req_1716234567890_a3f5c9d2`
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { generateCorrelationId, attachCorrelationId } from '@private.me/xbind';
|
|
15
|
+
*
|
|
16
|
+
* // Generate a new correlation ID
|
|
17
|
+
* const id = generateCorrelationId();
|
|
18
|
+
*
|
|
19
|
+
* // Attach to request headers
|
|
20
|
+
* const headers = attachCorrelationId(new Headers(), id);
|
|
21
|
+
*
|
|
22
|
+
* // Validate format
|
|
23
|
+
* if (validateCorrelationId(id)) {
|
|
24
|
+
* console.log('Valid correlation ID');
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Standard header name for correlation ID
|
|
30
|
+
*/
|
|
31
|
+
export const CORRELATION_ID_HEADER = 'X-Correlation-ID';
|
|
32
|
+
/**
|
|
33
|
+
* Alternative header names for compatibility
|
|
34
|
+
*/
|
|
35
|
+
export const CORRELATION_ID_ALIASES = [
|
|
36
|
+
'X-Request-ID',
|
|
37
|
+
'X-Trace-ID',
|
|
38
|
+
'X-Transaction-ID',
|
|
39
|
+
];
|
|
40
|
+
/**
|
|
41
|
+
* Regular expression for validating correlation ID format
|
|
42
|
+
*/
|
|
43
|
+
const CORRELATION_ID_PATTERN = /^req_\d{13}_[a-f0-9]{8}$/;
|
|
44
|
+
/**
|
|
45
|
+
* Generate a new correlation ID
|
|
46
|
+
*
|
|
47
|
+
* Format: `req_{timestamp}_{random}`
|
|
48
|
+
* - `req`: Static prefix for identification
|
|
49
|
+
* - `timestamp`: Unix timestamp in milliseconds (13 digits)
|
|
50
|
+
* - `random`: 8-character hex string for uniqueness
|
|
51
|
+
*
|
|
52
|
+
* @returns New correlation ID string
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* const id = generateCorrelationId();
|
|
57
|
+
* // => "req_1716234567890_a3f5c9d2"
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export function generateCorrelationId() {
|
|
61
|
+
const timestamp = Date.now();
|
|
62
|
+
const random = generateRandomHex(8);
|
|
63
|
+
return `req_${timestamp}_${random}`;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Validate correlation ID format
|
|
67
|
+
*
|
|
68
|
+
* Checks if the provided string matches the expected correlation ID format.
|
|
69
|
+
*
|
|
70
|
+
* @param id - String to validate
|
|
71
|
+
* @returns True if valid, false otherwise
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```typescript
|
|
75
|
+
* validateCorrelationId('req_1716234567890_a3f5c9d2'); // => true
|
|
76
|
+
* validateCorrelationId('invalid'); // => false
|
|
77
|
+
* validateCorrelationId('req_123_abc'); // => false (wrong lengths)
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export function validateCorrelationId(id) {
|
|
81
|
+
if (typeof id !== 'string')
|
|
82
|
+
return false;
|
|
83
|
+
return CORRELATION_ID_PATTERN.test(id);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Parse correlation ID into components
|
|
87
|
+
*
|
|
88
|
+
* Extracts the timestamp and random components from a correlation ID.
|
|
89
|
+
*
|
|
90
|
+
* @param id - Correlation ID to parse
|
|
91
|
+
* @returns Parsed components or null if invalid
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* const spec = parseCorrelationId('req_1716234567890_a3f5c9d2');
|
|
96
|
+
* // => { prefix: 'req', timestamp: 1716234567890, random: 'a3f5c9d2' }
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export function parseCorrelationId(id) {
|
|
100
|
+
if (!validateCorrelationId(id))
|
|
101
|
+
return null;
|
|
102
|
+
const parts = id.split('_');
|
|
103
|
+
return {
|
|
104
|
+
prefix: parts[0] ?? 'req',
|
|
105
|
+
timestamp: parseInt(parts[1] ?? '0', 10),
|
|
106
|
+
random: parts[2] ?? '',
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Attach correlation ID to request headers
|
|
111
|
+
*
|
|
112
|
+
* Adds the correlation ID to the X-Correlation-ID header.
|
|
113
|
+
* If no ID is provided, generates a new one.
|
|
114
|
+
* Compatible with both Headers API and plain objects.
|
|
115
|
+
*
|
|
116
|
+
* @param headers - Headers object or plain object
|
|
117
|
+
* @param id - Correlation ID (generates new if not provided)
|
|
118
|
+
* @returns Updated headers object
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```typescript
|
|
122
|
+
* // With Headers API
|
|
123
|
+
* const headers = new Headers();
|
|
124
|
+
* attachCorrelationId(headers);
|
|
125
|
+
*
|
|
126
|
+
* // With plain object
|
|
127
|
+
* const headers = { 'Content-Type': 'application/json' };
|
|
128
|
+
* attachCorrelationId(headers, 'req_1716234567890_a3f5c9d2');
|
|
129
|
+
*
|
|
130
|
+
* // With existing correlation ID
|
|
131
|
+
* const id = generateCorrelationId();
|
|
132
|
+
* attachCorrelationId(headers, id);
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
export function attachCorrelationId(headers, id) {
|
|
136
|
+
const correlationId = id ?? generateCorrelationId();
|
|
137
|
+
if (!validateCorrelationId(correlationId)) {
|
|
138
|
+
throw new Error(`Invalid correlation ID format: ${correlationId}. Expected format: req_{timestamp}_{random}`);
|
|
139
|
+
}
|
|
140
|
+
if (headers instanceof Headers) {
|
|
141
|
+
headers.set(CORRELATION_ID_HEADER, correlationId);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
headers[CORRELATION_ID_HEADER] = correlationId;
|
|
145
|
+
}
|
|
146
|
+
return headers;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Extract correlation ID from request headers
|
|
150
|
+
*
|
|
151
|
+
* Checks the standard header and common aliases.
|
|
152
|
+
*
|
|
153
|
+
* @param headers - Headers object or plain object
|
|
154
|
+
* @returns Correlation ID if found, undefined otherwise
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```typescript
|
|
158
|
+
* const headers = new Headers({
|
|
159
|
+
* 'X-Correlation-ID': 'req_1716234567890_a3f5c9d2'
|
|
160
|
+
* });
|
|
161
|
+
* const id = extractCorrelationId(headers);
|
|
162
|
+
* // => 'req_1716234567890_a3f5c9d2'
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
export function extractCorrelationId(headers) {
|
|
166
|
+
// Check primary header
|
|
167
|
+
const primary = headers instanceof Headers
|
|
168
|
+
? headers.get(CORRELATION_ID_HEADER)
|
|
169
|
+
: headers[CORRELATION_ID_HEADER];
|
|
170
|
+
if (primary && validateCorrelationId(primary)) {
|
|
171
|
+
return primary;
|
|
172
|
+
}
|
|
173
|
+
// Check aliases
|
|
174
|
+
for (const alias of CORRELATION_ID_ALIASES) {
|
|
175
|
+
const value = headers instanceof Headers ? headers.get(alias) : headers[alias];
|
|
176
|
+
if (value && validateCorrelationId(value)) {
|
|
177
|
+
return value;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Get or create correlation ID from headers
|
|
184
|
+
*
|
|
185
|
+
* Returns existing correlation ID from headers, or generates a new one if not found.
|
|
186
|
+
* Does NOT modify the input headers.
|
|
187
|
+
*
|
|
188
|
+
* @param headers - Headers to check
|
|
189
|
+
* @returns Existing or new correlation ID
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```typescript
|
|
193
|
+
* const headers = new Headers();
|
|
194
|
+
* const id = getOrCreateCorrelationId(headers);
|
|
195
|
+
* // => Generates new ID if not found in headers
|
|
196
|
+
* ```
|
|
197
|
+
*/
|
|
198
|
+
export function getOrCreateCorrelationId(headers) {
|
|
199
|
+
if (!headers)
|
|
200
|
+
return generateCorrelationId();
|
|
201
|
+
const existing = extractCorrelationId(headers);
|
|
202
|
+
return existing ?? generateCorrelationId();
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Create a correlation ID from a timestamp
|
|
206
|
+
*
|
|
207
|
+
* Useful for testing or when you need deterministic IDs.
|
|
208
|
+
*
|
|
209
|
+
* @param timestamp - Unix timestamp in milliseconds
|
|
210
|
+
* @param random - Optional random component (generates if not provided)
|
|
211
|
+
* @returns Correlation ID
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* ```typescript
|
|
215
|
+
* const id = createCorrelationIdFromTimestamp(1716234567890);
|
|
216
|
+
* // => 'req_1716234567890_a3f5c9d2'
|
|
217
|
+
*
|
|
218
|
+
* const deterministicId = createCorrelationIdFromTimestamp(1716234567890, 'aaaaaaaa');
|
|
219
|
+
* // => 'req_1716234567890_aaaaaaaa'
|
|
220
|
+
* ```
|
|
221
|
+
*/
|
|
222
|
+
export function createCorrelationIdFromTimestamp(timestamp, random) {
|
|
223
|
+
const randomComponent = random ?? generateRandomHex(8);
|
|
224
|
+
if (!/^[a-f0-9]{8}$/.test(randomComponent)) {
|
|
225
|
+
throw new Error(`Invalid random component: ${randomComponent}. Expected 8 hex characters`);
|
|
226
|
+
}
|
|
227
|
+
return `req_${timestamp}_${randomComponent}`;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Calculate age of correlation ID in milliseconds
|
|
231
|
+
*
|
|
232
|
+
* @param id - Correlation ID
|
|
233
|
+
* @returns Age in milliseconds, or null if invalid
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* ```typescript
|
|
237
|
+
* const id = generateCorrelationId();
|
|
238
|
+
* setTimeout(() => {
|
|
239
|
+
* const age = getCorrelationIdAge(id);
|
|
240
|
+
* console.log(`Request age: ${age}ms`);
|
|
241
|
+
* }, 1000);
|
|
242
|
+
* ```
|
|
243
|
+
*/
|
|
244
|
+
export function getCorrelationIdAge(id) {
|
|
245
|
+
const spec = parseCorrelationId(id);
|
|
246
|
+
if (!spec)
|
|
247
|
+
return null;
|
|
248
|
+
const now = Date.now();
|
|
249
|
+
return now - spec.timestamp;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Check if correlation ID is expired
|
|
253
|
+
*
|
|
254
|
+
* @param id - Correlation ID
|
|
255
|
+
* @param maxAgeMs - Maximum age in milliseconds (default: 5 minutes)
|
|
256
|
+
* @returns True if expired, false otherwise
|
|
257
|
+
*
|
|
258
|
+
* @example
|
|
259
|
+
* ```typescript
|
|
260
|
+
* const id = generateCorrelationId();
|
|
261
|
+
* if (isCorrelationIdExpired(id, 60000)) {
|
|
262
|
+
* console.log('Request older than 1 minute');
|
|
263
|
+
* }
|
|
264
|
+
* ```
|
|
265
|
+
*/
|
|
266
|
+
export function isCorrelationIdExpired(id, maxAgeMs = 5 * 60 * 1000) {
|
|
267
|
+
const age = getCorrelationIdAge(id);
|
|
268
|
+
return age !== null && age > maxAgeMs;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Generate random hex string
|
|
272
|
+
*
|
|
273
|
+
* Uses Web Crypto API in browser, Node.js crypto in Node.
|
|
274
|
+
* Falls back to Math.random() if neither available (NOT cryptographically secure).
|
|
275
|
+
*
|
|
276
|
+
* @param length - Number of hex characters to generate
|
|
277
|
+
* @returns Random hex string
|
|
278
|
+
*
|
|
279
|
+
* @internal
|
|
280
|
+
*/
|
|
281
|
+
function generateRandomHex(length) {
|
|
282
|
+
const bytes = Math.ceil(length / 2);
|
|
283
|
+
// Try Web Crypto API (browser)
|
|
284
|
+
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
|
|
285
|
+
const buffer = new Uint8Array(bytes);
|
|
286
|
+
crypto.getRandomValues(buffer);
|
|
287
|
+
return Array.from(buffer)
|
|
288
|
+
.map((b) => b.toString(16).padStart(2, '0'))
|
|
289
|
+
.join('')
|
|
290
|
+
.substring(0, length);
|
|
291
|
+
}
|
|
292
|
+
// Try Node.js crypto
|
|
293
|
+
try {
|
|
294
|
+
const nodeCrypto = require('node:crypto');
|
|
295
|
+
return nodeCrypto.randomBytes(bytes).toString('hex').substring(0, length);
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
// SECURITY: Never fall back to Math.random() in production (OWASP violation)
|
|
299
|
+
// Correlation IDs must be cryptographically random to prevent enumeration attacks
|
|
300
|
+
throw new Error('Cryptographic random generation unavailable. ' +
|
|
301
|
+
'Install crypto polyfill or use environment with crypto support.');
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Middleware helper for Express/Koa-style frameworks
|
|
306
|
+
*
|
|
307
|
+
* Automatically attaches correlation ID to incoming requests.
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* ```typescript
|
|
311
|
+
* import express from 'express';
|
|
312
|
+
* import { correlationIdMiddleware } from '@private.me/xbind';
|
|
313
|
+
*
|
|
314
|
+
* const app = express();
|
|
315
|
+
* app.use(correlationIdMiddleware());
|
|
316
|
+
* ```
|
|
317
|
+
*/
|
|
318
|
+
export function correlationIdMiddleware() {
|
|
319
|
+
return (req, res, next) => {
|
|
320
|
+
const id = getOrCreateCorrelationId(req.headers);
|
|
321
|
+
req.correlationId = id;
|
|
322
|
+
res.setHeader(CORRELATION_ID_HEADER, id);
|
|
323
|
+
if (typeof next === 'function')
|
|
324
|
+
next();
|
|
325
|
+
};
|
|
326
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module http-status-map
|
|
3
|
+
* Maps xBind error codes to HTTP status codes for REST API responses.
|
|
4
|
+
*
|
|
5
|
+
* This mapping provides:
|
|
6
|
+
* - Consistent HTTP status codes for all 52 xBind error codes
|
|
7
|
+
* - Retryability information for client retry logic
|
|
8
|
+
* - Error categorization (client vs server errors)
|
|
9
|
+
* - Human-readable status text
|
|
10
|
+
*
|
|
11
|
+
* Based on RFC 9110 HTTP Semantics and REST API best practices.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { getHttpStatus, isRetryable } from '@private.me/xbind/http-status-map';
|
|
16
|
+
*
|
|
17
|
+
* const status = getHttpStatus('VERIFY_FAILED'); // 401
|
|
18
|
+
* const canRetry = isRetryable('NETWORK_ERROR'); // true
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* HTTP status mapping entry with metadata.
|
|
23
|
+
*/
|
|
24
|
+
export interface HttpStatusMapping {
|
|
25
|
+
/** HTTP status code (e.g., 400, 401, 500) */
|
|
26
|
+
readonly statusCode: number;
|
|
27
|
+
/** HTTP status text (e.g., 'Bad Request', 'Unauthorized') */
|
|
28
|
+
readonly statusText: string;
|
|
29
|
+
/** Whether the operation should be retried by the client */
|
|
30
|
+
readonly retryable: boolean;
|
|
31
|
+
/** Error type classification */
|
|
32
|
+
readonly errorType: 'Client' | 'Server';
|
|
33
|
+
/** Error category for grouping related errors */
|
|
34
|
+
readonly category: 'Identity' | 'Envelope' | 'Transport' | 'Registry' | 'Key Agreement' | 'Split Channel' | 'Xchange' | 'Agent';
|
|
35
|
+
/** Rationale for the HTTP status code choice */
|
|
36
|
+
readonly rationale: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Complete mapping of xBind error codes to HTTP status codes.
|
|
40
|
+
* All 52 error codes are mapped for 100% coverage.
|
|
41
|
+
*/
|
|
42
|
+
export declare const HTTP_STATUS_MAP: Record<string, HttpStatusMapping>;
|
|
43
|
+
/**
|
|
44
|
+
* Get HTTP status code for a given xBind error code.
|
|
45
|
+
* Handles colon-separated sub-codes (e.g., 'DECRYPT_FAILED:KEY_AGREEMENT').
|
|
46
|
+
*
|
|
47
|
+
* @param errorCode - xBind error code (e.g., 'VERIFY_FAILED')
|
|
48
|
+
* @returns HTTP status code (e.g., 401) or 500 if unknown
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* getHttpStatus('VERIFY_FAILED') // 401
|
|
53
|
+
* getHttpStatus('DECRYPT_FAILED:NETWORK') // 401 (ignores sub-code)
|
|
54
|
+
* getHttpStatus('UNKNOWN_ERROR') // 500 (fallback)
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export declare function getHttpStatus(errorCode: string): number;
|
|
58
|
+
/**
|
|
59
|
+
* Get HTTP status text for a given xBind error code.
|
|
60
|
+
*
|
|
61
|
+
* @param errorCode - xBind error code (e.g., 'VERIFY_FAILED')
|
|
62
|
+
* @returns HTTP status text (e.g., 'Unauthorized') or 'Internal Server Error' if unknown
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* getStatusText('VERIFY_FAILED') // 'Unauthorized'
|
|
67
|
+
* getStatusText('NOT_FOUND') // 'Not Found'
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export declare function getStatusText(errorCode: string): string;
|
|
71
|
+
/**
|
|
72
|
+
* Check if an error is retryable by the client.
|
|
73
|
+
*
|
|
74
|
+
* @param errorCode - xBind error code (e.g., 'NETWORK_ERROR')
|
|
75
|
+
* @returns True if the client should retry the operation
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* isRetryable('NETWORK_ERROR') // true (503 - retry with backoff)
|
|
80
|
+
* isRetryable('VERIFY_FAILED') // false (401 - authentication failure)
|
|
81
|
+
* isRetryable('KEYGEN_FAILED') // true (500 - transient server issue)
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export declare function isRetryable(errorCode: string): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Get complete mapping information for an error code.
|
|
87
|
+
*
|
|
88
|
+
* @param errorCode - xBind error code (e.g., 'VERIFY_FAILED')
|
|
89
|
+
* @returns Complete mapping information or null if not found
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* const mapping = getMapping('VERIFY_FAILED');
|
|
94
|
+
* if (mapping) {
|
|
95
|
+
* console.log(`${mapping.statusCode} ${mapping.statusText}`);
|
|
96
|
+
* console.log(`Retryable: ${mapping.retryable}`);
|
|
97
|
+
* console.log(`Rationale: ${mapping.rationale}`);
|
|
98
|
+
* }
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export declare function getMapping(errorCode: string): HttpStatusMapping | null;
|
|
102
|
+
/**
|
|
103
|
+
* Get all error codes in a specific category.
|
|
104
|
+
*
|
|
105
|
+
* @param category - Error category (e.g., 'Identity', 'Transport')
|
|
106
|
+
* @returns Array of error codes in that category
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```typescript
|
|
110
|
+
* const identityErrors = getErrorCodesByCategory('Identity');
|
|
111
|
+
* // ['KEYGEN_FAILED', 'SIGN_FAILED', 'VERIFY_FAILED', ...]
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
export declare function getErrorCodesByCategory(category: HttpStatusMapping['category']): string[];
|
|
115
|
+
/**
|
|
116
|
+
* Get all retryable error codes.
|
|
117
|
+
*
|
|
118
|
+
* @returns Array of error codes that are retryable
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```typescript
|
|
122
|
+
* const retryableErrors = getRetryableErrorCodes();
|
|
123
|
+
* // ['KEYGEN_FAILED', 'EXPORT_FAILED', 'ENCRYPT_FAILED', ...]
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
export declare function getRetryableErrorCodes(): string[];
|
|
127
|
+
/**
|
|
128
|
+
* Validate that all error codes from the CSV are mapped.
|
|
129
|
+
* This function is used for testing purposes to ensure 100% coverage.
|
|
130
|
+
*
|
|
131
|
+
* @param csvErrorCodes - Array of error codes from the CSV file
|
|
132
|
+
* @returns Array of missing error codes (empty if all mapped)
|
|
133
|
+
*
|
|
134
|
+
* @internal
|
|
135
|
+
*/
|
|
136
|
+
export declare function validateCoverage(csvErrorCodes: string[]): string[];
|