@pellux/goodvibes-transport-http 0.18.3 → 0.30.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/README.md +14 -7
- package/dist/auth.d.ts +25 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +55 -0
- package/dist/backoff.d.ts +7 -0
- package/dist/backoff.d.ts.map +1 -1
- package/dist/backoff.js +10 -2
- package/dist/client-plumbing.d.ts +32 -0
- package/dist/client-plumbing.d.ts.map +1 -0
- package/dist/client-plumbing.js +243 -0
- package/dist/contract-client.d.ts +11 -1
- package/dist/contract-client.d.ts.map +1 -1
- package/dist/contract-client.js +20 -5
- package/dist/http-core.d.ts +42 -1
- package/dist/http-core.d.ts.map +1 -1
- package/dist/http-core.js +240 -76
- package/dist/http.d.ts +3 -3
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +46 -5
- package/dist/index.d.ts +11 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +3 -2
- package/dist/reconnect.d.ts +2 -0
- package/dist/reconnect.d.ts.map +1 -1
- package/dist/reconnect.js +4 -3
- package/dist/retry.d.ts +15 -0
- package/dist/retry.d.ts.map +1 -1
- package/dist/retry.js +21 -2
- package/dist/sse-stream.d.ts +9 -2
- package/dist/sse-stream.d.ts.map +1 -1
- package/dist/sse-stream.js +44 -25
- package/dist/sse.d.ts +1 -1
- package/dist/sse.d.ts.map +1 -1
- package/dist/sse.js +6 -12
- package/package.json +53 -4
package/dist/http-core.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-core.d.ts","sourceRoot":"","sources":["../src/http-core.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"http-core.d.ts","sourceRoot":"","sources":["../src/http-core.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwD,eAAe,EAAyB,MAAM,0BAA0B,CAAC;AAExI,OAAO,EAA2E,KAAK,iBAAiB,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AACjJ,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAkC,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AACjF,OAAO,EAOL,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACvB,MAAM,kCAAkC,CAAC;AAE1C,YAAY,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACxE,YAAY,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAE9F,MAAM,MAAM,SAAS,GACjB,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ;IAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GACrC,SAAS,SAAS,EAAE,CAAC;AAEzB,MAAM,MAAM,UAAU,GAAG;IAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAE/D;;;;GAIG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C;AAKD,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,YAAY,CAAC,EAAE,iBAAiB,CAAC;IAC1C,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC;IAC/B,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,CAAC;IACrC,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IACtC,qEAAqE;IACrE,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAC;CACtD;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,eAAe,CAAC;IACzC;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,SAAS,EAAE,OAAO,KAAK,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,YAAY,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACvC,WAAW,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAChF,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,uBAAuB,CAAC;IAC/G,+DAA+D;IAC/D,GAAG,CAAC,UAAU,EAAE,mBAAmB,GAAG,IAAI,CAAC;CAC5C;AAMD,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,YAAY,CAAC,EAAE,MAAM,GACpB,MAAM,GAAG,SAAS,CAapB;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO,EACb,YAAY,CAAC,EAAE,MAAM,GACpB,eAAe,GAAG;IAAE,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAA;CAAE,CAc9D;AAED,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,GACb,eAAe,GAAG;IAAE,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAA;CAAE,CAuB9D;AAgDD,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAChC,IAAI,CAAC,EAAE,OAAO,EACd,MAAM,SAAQ,EACd,OAAO,GAAE,WAAgB,EACzB,MAAM,CAAC,EAAE,WAAW,EACpB,cAAc,GAAE,WAAgB,GAC/B,WAAW,CAab;AAED,eAAO,MAAM,cAAc,8BAAwB,CAAC;AAEpD,wBAAgB,WAAW,CAAC,SAAS,CAAC,EAAE,OAAO,KAAK,EAAE,aAAa,CAAC,EAAE,OAAO,KAAK,GAAG,OAAO,KAAK,CAMhG;AAmBD,wBAAsB,YAAY,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CASvE;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,CAAC,EACjC,SAAS,EAAE,OAAO,KAAK,EACvB,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC,CAAC,CAAC,CAaZ;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB,GAAG,iBAAiB,CAkN5F"}
|
package/dist/http-core.js
CHANGED
|
@@ -1,88 +1,90 @@
|
|
|
1
|
-
|
|
1
|
+
import { ConfigurationError, ContractError, GoodVibesSdkError, HttpStatusError, createHttpStatusError } from '@pellux/goodvibes-errors';
|
|
2
2
|
import { sleepWithSignal } from './backoff.js';
|
|
3
|
-
import {
|
|
4
|
-
import { getHttpRetryDelay, isRetryableHttpStatus, isRetryableNetworkError, resolveHttpRetryPolicy, } from './retry.js';
|
|
3
|
+
import { mergeHeaderRecord, normalizeAuthToken, resolveAuthToken, resolveHeaders } from './auth.js';
|
|
4
|
+
import { applyPerMethodPolicy, getHttpRetryDelay, isRetryableHttpStatus, isRetryableNetworkError, resolveHttpRetryPolicy, } from './retry.js';
|
|
5
5
|
import { buildUrl, createTransportPaths } from './paths.js';
|
|
6
|
+
import { composeMiddleware, createUuidV4, injectTraceparentAsync, invokeTransportObserver, transportErrorFromUnknown, } from '@pellux/goodvibes-transport-core';
|
|
7
|
+
/**
|
|
8
|
+
* Generate a UUID v4 idempotency key.
|
|
9
|
+
* Uses `crypto.randomUUID()` when available (Bun, browsers, Workers, Node 14.17+).
|
|
10
|
+
* Falls back to a manual RFC 4122 v4 implementation otherwise.
|
|
11
|
+
*/
|
|
12
|
+
export function generateIdempotencyKey() {
|
|
13
|
+
return createUuidV4();
|
|
14
|
+
}
|
|
15
|
+
/** Methods that are safe to send idempotency keys for (all non-GET requests). */
|
|
16
|
+
const IDEMPOTENCY_KEY_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
|
|
6
17
|
function isPlainObject(value) {
|
|
7
18
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
8
19
|
}
|
|
9
|
-
function
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
for (const [key, value] of Object.entries(source)) {
|
|
28
|
-
if (value !== undefined) {
|
|
29
|
-
target[key] = value;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
function mergeHeaderRecord(...sources) {
|
|
34
|
-
const merged = {};
|
|
35
|
-
for (const source of sources) {
|
|
36
|
-
applyHeaderSource(merged, source);
|
|
20
|
+
export function inferTransportHint(status, url, retryAfterMs) {
|
|
21
|
+
if (status === 0)
|
|
22
|
+
return `Transport could not reach ${url}. Verify the baseUrl is reachable.`;
|
|
23
|
+
if (status === 401)
|
|
24
|
+
return 'Check your authentication token or credentials.';
|
|
25
|
+
if (status === 403)
|
|
26
|
+
return 'Valid credentials but insufficient permissions for this operation.';
|
|
27
|
+
if (status === 404)
|
|
28
|
+
return 'The requested resource was not found.';
|
|
29
|
+
if (status === 408)
|
|
30
|
+
return 'The request timed out. Consider retrying.';
|
|
31
|
+
if (status === 429) {
|
|
32
|
+
return retryAfterMs !== undefined
|
|
33
|
+
? `Rate limit exceeded. Retry after ${retryAfterMs}ms.`
|
|
34
|
+
: 'Rate limit exceeded. Back off and retry.';
|
|
37
35
|
}
|
|
38
|
-
|
|
36
|
+
if (status >= 500)
|
|
37
|
+
return 'Remote server error. The service may be temporarily unavailable.';
|
|
38
|
+
return undefined;
|
|
39
39
|
}
|
|
40
|
-
function
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return Object.assign(
|
|
51
|
-
transport:
|
|
52
|
-
|
|
53
|
-
body,
|
|
54
|
-
url,
|
|
55
|
-
method,
|
|
56
|
-
},
|
|
40
|
+
export function createTransportError(status, url, method, body, retryAfterMs) {
|
|
41
|
+
const inferred = inferTransportHint(status, url, retryAfterMs);
|
|
42
|
+
const baseError = createHttpStatusError(status, url, method, body, inferred);
|
|
43
|
+
const transportPayload = {
|
|
44
|
+
status,
|
|
45
|
+
body,
|
|
46
|
+
url,
|
|
47
|
+
method,
|
|
48
|
+
...(retryAfterMs !== undefined ? { retryAfterMs } : {}),
|
|
49
|
+
};
|
|
50
|
+
return Object.assign(baseError, {
|
|
51
|
+
transport: transportPayload,
|
|
52
|
+
...(retryAfterMs !== undefined ? { retryAfterMs } : {}),
|
|
57
53
|
});
|
|
58
54
|
}
|
|
59
|
-
function createNetworkTransportError(error, url, method) {
|
|
55
|
+
export function createNetworkTransportError(error, url, method) {
|
|
60
56
|
const message = error instanceof Error && error.message.trim()
|
|
61
57
|
? error.message.trim()
|
|
62
58
|
: `Transport request failed before receiving a response for ${url}`;
|
|
63
|
-
|
|
59
|
+
const hint = `Transport could not reach ${url}. Verify the baseUrl is reachable.`;
|
|
60
|
+
const networkError = new HttpStatusError(message, {
|
|
61
|
+
category: 'network',
|
|
62
|
+
source: 'transport',
|
|
63
|
+
recoverable: true,
|
|
64
|
+
url,
|
|
65
|
+
method,
|
|
66
|
+
body: { error: message },
|
|
67
|
+
hint,
|
|
64
68
|
cause: error,
|
|
65
|
-
transport: {
|
|
66
|
-
status: 0,
|
|
67
|
-
body: { error: message },
|
|
68
|
-
url,
|
|
69
|
-
method,
|
|
70
|
-
},
|
|
71
69
|
});
|
|
70
|
+
const transportPayload = {
|
|
71
|
+
status: 0,
|
|
72
|
+
body: { error: message },
|
|
73
|
+
url,
|
|
74
|
+
method,
|
|
75
|
+
cause: error,
|
|
76
|
+
};
|
|
77
|
+
return Object.assign(networkError, { transport: transportPayload });
|
|
72
78
|
}
|
|
73
79
|
function toStringValue(value, key) {
|
|
74
80
|
if (value === undefined || value === null) {
|
|
75
|
-
throw new
|
|
81
|
+
throw new ContractError(`Missing required path parameter "${key}". Ensure the input object includes a non-null value for this field before invoking the route.`);
|
|
76
82
|
}
|
|
77
83
|
return String(value);
|
|
78
84
|
}
|
|
79
85
|
function addQueryValue(url, key, value) {
|
|
80
|
-
if (value === undefined)
|
|
81
|
-
return;
|
|
82
|
-
if (value === null) {
|
|
83
|
-
url.searchParams.append(key, 'null');
|
|
86
|
+
if (value === undefined || value === null)
|
|
84
87
|
return;
|
|
85
|
-
}
|
|
86
88
|
if (Array.isArray(value)) {
|
|
87
89
|
for (const item of value) {
|
|
88
90
|
addQueryValue(url, key, item);
|
|
@@ -90,18 +92,28 @@ function addQueryValue(url, key, value) {
|
|
|
90
92
|
return;
|
|
91
93
|
}
|
|
92
94
|
if (typeof value === 'object') {
|
|
95
|
+
// Contract query parameters are primitive or repeated primitive values.
|
|
96
|
+
// Object values are preserved as JSON strings so callers do not silently
|
|
97
|
+
// lose structured filters when a route explicitly accepts them.
|
|
93
98
|
url.searchParams.append(key, JSON.stringify(value));
|
|
94
99
|
return;
|
|
95
100
|
}
|
|
96
101
|
url.searchParams.append(key, String(value));
|
|
97
102
|
}
|
|
103
|
+
function hasHeader(headers, name) {
|
|
104
|
+
const normalized = name.toLowerCase();
|
|
105
|
+
return Object.keys(headers).some((key) => key.toLowerCase() === normalized);
|
|
106
|
+
}
|
|
98
107
|
function splitContractInput(path, input = {}) {
|
|
99
108
|
const remaining = { ...input };
|
|
100
|
-
const interpolatedPath = path.replace(/\{([
|
|
109
|
+
const interpolatedPath = path.replace(/\{([A-Za-z_][A-Za-z0-9_.-]*)\}/g, (_match, key) => {
|
|
101
110
|
const value = toStringValue(remaining[key], key);
|
|
102
111
|
delete remaining[key];
|
|
103
112
|
return encodeURIComponent(value);
|
|
104
113
|
});
|
|
114
|
+
if (/[{}]/.test(interpolatedPath)) {
|
|
115
|
+
throw new ContractError(`Malformed contract path "${path}". Path parameters must use "{name}" with identifier-like names.`);
|
|
116
|
+
}
|
|
105
117
|
return { interpolatedPath, remaining };
|
|
106
118
|
}
|
|
107
119
|
export function createJsonRequestInit(token, body, method = 'GET', headers = {}, signal, defaultHeaders = {}) {
|
|
@@ -117,10 +129,27 @@ export const createJsonInit = createJsonRequestInit;
|
|
|
117
129
|
export function createFetch(fetchImpl, fallbackFetch) {
|
|
118
130
|
const resolved = fetchImpl ?? fallbackFetch ?? globalThis.fetch;
|
|
119
131
|
if (typeof resolved !== 'function') {
|
|
120
|
-
throw new
|
|
132
|
+
throw new ConfigurationError('Fetch implementation is required. Pass a fetch option (e.g. options.fetch) or ensure globalThis.fetch is available in your runtime.');
|
|
121
133
|
}
|
|
122
134
|
return resolved.bind(globalThis);
|
|
123
135
|
}
|
|
136
|
+
function parseRetryAfterMs(headers) {
|
|
137
|
+
const retryAfter = headers.get('retry-after');
|
|
138
|
+
if (!retryAfter)
|
|
139
|
+
return undefined;
|
|
140
|
+
// Numeric seconds
|
|
141
|
+
const seconds = Number(retryAfter);
|
|
142
|
+
if (!Number.isNaN(seconds) && seconds >= 0) {
|
|
143
|
+
return Math.ceil(seconds * 1000);
|
|
144
|
+
}
|
|
145
|
+
// HTTP-date
|
|
146
|
+
const date = new Date(retryAfter);
|
|
147
|
+
if (!Number.isNaN(date.getTime())) {
|
|
148
|
+
const ms = date.getTime() - Date.now();
|
|
149
|
+
return ms > 0 ? ms : 0;
|
|
150
|
+
}
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
124
153
|
export async function readJsonBody(response) {
|
|
125
154
|
const text = await response.text();
|
|
126
155
|
if (!text.trim())
|
|
@@ -128,10 +157,16 @@ export async function readJsonBody(response) {
|
|
|
128
157
|
try {
|
|
129
158
|
return JSON.parse(text);
|
|
130
159
|
}
|
|
131
|
-
catch {
|
|
160
|
+
catch (error) {
|
|
161
|
+
void error;
|
|
132
162
|
return text;
|
|
133
163
|
}
|
|
134
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* @internal Low-level one-shot helper retained for legacy internal callers.
|
|
167
|
+
* Public code should use `createHttpTransport(...).requestJson()` so auth,
|
|
168
|
+
* middleware, retry policy, idempotency keys, and observers are applied.
|
|
169
|
+
*/
|
|
135
170
|
export async function requestJson(fetchImpl, url, init = {}) {
|
|
136
171
|
let response;
|
|
137
172
|
try {
|
|
@@ -142,7 +177,8 @@ export async function requestJson(fetchImpl, url, init = {}) {
|
|
|
142
177
|
}
|
|
143
178
|
const body = await readJsonBody(response);
|
|
144
179
|
if (!response.ok) {
|
|
145
|
-
|
|
180
|
+
const retryAfterMs = parseRetryAfterMs(response.headers);
|
|
181
|
+
throw createTransportError(response.status, url, init.method ?? 'GET', body, retryAfterMs);
|
|
146
182
|
}
|
|
147
183
|
return body;
|
|
148
184
|
}
|
|
@@ -150,31 +186,156 @@ export function createHttpJsonTransport(options) {
|
|
|
150
186
|
const baseUrl = options.baseUrl.trim();
|
|
151
187
|
const fetchImpl = createFetch(options.fetchImpl, options.fetch);
|
|
152
188
|
const authToken = options.authToken ?? null;
|
|
189
|
+
// Normalize at the boundary: downstream always works with a single resolver.
|
|
190
|
+
const getAuthToken = options.getAuthToken ?? normalizeAuthToken(options.authToken ?? undefined);
|
|
153
191
|
const defaultHeaders = options.headers;
|
|
154
192
|
const retryPolicy = options.retry;
|
|
155
193
|
const paths = createTransportPaths(baseUrl);
|
|
194
|
+
const observer = options.observer;
|
|
195
|
+
// Persistent middleware chain — mutated via use().
|
|
196
|
+
const middlewareChain = [...(options.middleware ?? [])];
|
|
156
197
|
const requestJsonForTransport = async (pathOrUrl, requestOptions = {}) => {
|
|
157
198
|
const url = pathOrUrl.startsWith('http://') || pathOrUrl.startsWith('https://')
|
|
158
199
|
? pathOrUrl
|
|
159
200
|
: buildUrl(baseUrl, pathOrUrl);
|
|
160
201
|
const method = requestOptions.method ?? (requestOptions.body === undefined ? 'GET' : 'POST');
|
|
161
|
-
const
|
|
202
|
+
const methodId = requestOptions.methodId;
|
|
203
|
+
// Resolve idempotent flag from request options (set by contract-client from contract.idempotent).
|
|
204
|
+
const contractIdempotent = requestOptions.idempotent === true;
|
|
205
|
+
// Apply per-method retry policy override if a methodId is provided.
|
|
206
|
+
const baseRetry = resolveHttpRetryPolicy(retryPolicy, requestOptions.retry);
|
|
207
|
+
const resolvedRetry = methodId ? applyPerMethodPolicy(baseRetry, methodId) : baseRetry;
|
|
208
|
+
// Determine idempotency: non-GET mutations without an explicit idempotent flag do NOT retry.
|
|
209
|
+
// This is enforced below by gating the retry check on method type.
|
|
210
|
+
const isMutatingMethod = IDEMPOTENCY_KEY_METHODS.has(method.toUpperCase());
|
|
211
|
+
const idempotencyKey = isMutatingMethod ? generateIdempotencyKey() : undefined;
|
|
162
212
|
let attempt = 0;
|
|
163
213
|
while (true) {
|
|
164
214
|
attempt += 1;
|
|
165
|
-
const token = await
|
|
166
|
-
const
|
|
215
|
+
const token = (await getAuthToken()) ?? null;
|
|
216
|
+
const resolvedHeaders = await resolveHeaders(defaultHeaders, options.getHeaders);
|
|
217
|
+
// Build merged headers record: default + per-request, then inject cross-cutting headers.
|
|
218
|
+
const mergedHeaders = mergeHeaderRecord(resolvedHeaders ?? {}, requestOptions.headers ?? {});
|
|
219
|
+
if (token) {
|
|
220
|
+
mergedHeaders['Authorization'] = `Bearer ${token}`;
|
|
221
|
+
}
|
|
222
|
+
if (requestOptions.body !== undefined) {
|
|
223
|
+
mergedHeaders['Content-Type'] = 'application/json';
|
|
224
|
+
}
|
|
225
|
+
// Inject W3C traceparent if OTel is active. This path is async so pure
|
|
226
|
+
// ESM OpenTelemetry providers can be loaded before the request is sent.
|
|
227
|
+
await injectTraceparentAsync(mergedHeaders);
|
|
228
|
+
// Inject idempotency key for mutating methods.
|
|
229
|
+
if (idempotencyKey && !hasHeader(mergedHeaders, 'Idempotency-Key')) {
|
|
230
|
+
mergedHeaders['Idempotency-Key'] = idempotencyKey;
|
|
231
|
+
}
|
|
232
|
+
// Notify observer before dispatching the request.
|
|
233
|
+
invokeTransportObserver(() => observer?.onTransportActivity?.({ direction: 'send', url, kind: 'http' }));
|
|
234
|
+
const sendAt = Date.now();
|
|
235
|
+
// Build the middleware context for this attempt.
|
|
236
|
+
const ctx = {
|
|
237
|
+
method,
|
|
238
|
+
url,
|
|
239
|
+
headers: mergedHeaders,
|
|
240
|
+
body: requestOptions.body,
|
|
241
|
+
options: requestOptions,
|
|
242
|
+
signal: requestOptions.signal,
|
|
243
|
+
};
|
|
244
|
+
// Build the inner fetch that middleware wraps (and also used directly without middleware).
|
|
245
|
+
const innerFetch = async (c) => {
|
|
246
|
+
const init = {
|
|
247
|
+
method: c.method,
|
|
248
|
+
credentials: 'include',
|
|
249
|
+
signal: c.signal,
|
|
250
|
+
headers: c.headers,
|
|
251
|
+
...(c.body !== undefined ? { body: JSON.stringify(c.body) } : {}),
|
|
252
|
+
};
|
|
253
|
+
let response;
|
|
254
|
+
try {
|
|
255
|
+
response = await fetchImpl(c.url, init);
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
throw createNetworkTransportError(error, c.url, c.method);
|
|
259
|
+
}
|
|
260
|
+
const body = await readJsonBody(response);
|
|
261
|
+
if (!response.ok) {
|
|
262
|
+
const retryAfterMs = parseRetryAfterMs(response.headers);
|
|
263
|
+
throw createTransportError(response.status, c.url, c.method, body, retryAfterMs);
|
|
264
|
+
}
|
|
265
|
+
// Return synthetic Response carrying parsed body so callers can .json() it.
|
|
266
|
+
return new Response(JSON.stringify(body), { status: response.status });
|
|
267
|
+
};
|
|
167
268
|
try {
|
|
168
|
-
|
|
269
|
+
if (middlewareChain.length > 0) {
|
|
270
|
+
// Middleware path — compose chain around innerFetch.
|
|
271
|
+
const composed = composeMiddleware(middlewareChain, innerFetch);
|
|
272
|
+
await composed(ctx);
|
|
273
|
+
if (ctx.error)
|
|
274
|
+
throw ctx.error;
|
|
275
|
+
if (!ctx.response) {
|
|
276
|
+
throw new GoodVibesSdkError('HTTP middleware chain completed without producing a response.', {
|
|
277
|
+
category: 'protocol',
|
|
278
|
+
source: 'transport',
|
|
279
|
+
recoverable: false,
|
|
280
|
+
url,
|
|
281
|
+
method,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
const result = await ctx.response.json();
|
|
285
|
+
invokeTransportObserver(() => observer?.onTransportActivity?.({
|
|
286
|
+
direction: 'recv',
|
|
287
|
+
url,
|
|
288
|
+
kind: 'http',
|
|
289
|
+
durationMs: ctx.durationMs,
|
|
290
|
+
}));
|
|
291
|
+
return result;
|
|
292
|
+
}
|
|
293
|
+
// No-middleware fast path — directly invoke innerFetch with ctx.
|
|
294
|
+
const rawResponse = await innerFetch(ctx);
|
|
295
|
+
const result = await rawResponse.json();
|
|
296
|
+
// Notify observer after a successful response.
|
|
297
|
+
invokeTransportObserver(() => observer?.onTransportActivity?.({
|
|
298
|
+
direction: 'recv',
|
|
299
|
+
url,
|
|
300
|
+
kind: 'http',
|
|
301
|
+
durationMs: Date.now() - sendAt,
|
|
302
|
+
}));
|
|
303
|
+
return result;
|
|
169
304
|
}
|
|
170
305
|
catch (error) {
|
|
171
|
-
|
|
172
|
-
|
|
306
|
+
// Wrap middleware errors as SDKError{kind:'unknown'} with middleware identity in cause.
|
|
307
|
+
// ALL errors originating from the middleware chain are wrapped — including HttpStatusError.
|
|
308
|
+
const wrappedError = (() => {
|
|
309
|
+
if (ctx.middlewareError) {
|
|
310
|
+
// Error came from within the middleware chain — wrap regardless of error type.
|
|
311
|
+
const msg = transportErrorFromUnknown(error, 'transport middleware error').message;
|
|
312
|
+
const middlewareName = ctx.activeMiddlewareName ?? 'unknown';
|
|
313
|
+
const wrapped = new GoodVibesSdkError(`Transport middleware error: ${msg}`, {
|
|
314
|
+
category: 'unknown',
|
|
315
|
+
source: 'transport',
|
|
316
|
+
recoverable: false,
|
|
317
|
+
cause: { middleware: middlewareName, originalError: error },
|
|
318
|
+
});
|
|
319
|
+
return wrapped;
|
|
320
|
+
}
|
|
321
|
+
if (error instanceof GoodVibesSdkError)
|
|
322
|
+
return error;
|
|
323
|
+
return error;
|
|
324
|
+
})();
|
|
325
|
+
// Notify observer of the transport error before deciding to retry or rethrow.
|
|
326
|
+
invokeTransportObserver(() => observer?.onError?.(transportErrorFromUnknown(wrappedError, 'HTTP transport error')));
|
|
327
|
+
const status = typeof wrappedError === 'object' && wrappedError !== null && 'transport' in wrappedError
|
|
328
|
+
? wrappedError.transport?.status
|
|
173
329
|
: undefined;
|
|
174
|
-
|
|
330
|
+
// Mutating methods (POST/PUT/PATCH/DELETE) without idempotent contract mark:
|
|
331
|
+
// do NOT retry on 5xx to avoid duplicate side effects.
|
|
332
|
+
// Precedence: explicit perMethodPolicy > contract.idempotent flag > HTTP-verb default.
|
|
333
|
+
const hasPerMethodOverride = methodId !== undefined && baseRetry.perMethodPolicy[methodId] !== undefined;
|
|
334
|
+
const canRetry = !isMutatingMethod || hasPerMethodOverride || contractIdempotent;
|
|
335
|
+
const shouldRetry = canRetry && attempt < resolvedRetry.maxAttempts && ((typeof status === 'number' && status > 0 && isRetryableHttpStatus(method, status, resolvedRetry))
|
|
175
336
|
|| (typeof status === 'number' && status === 0 && isRetryableNetworkError(method, resolvedRetry)));
|
|
176
337
|
if (!shouldRetry) {
|
|
177
|
-
throw
|
|
338
|
+
throw wrappedError;
|
|
178
339
|
}
|
|
179
340
|
await sleepWithSignal(getHttpRetryDelay(attempt + 1, resolvedRetry), requestOptions.signal);
|
|
180
341
|
}
|
|
@@ -209,9 +370,12 @@ export function createHttpJsonTransport(options) {
|
|
|
209
370
|
return buildUrl(baseUrl, path);
|
|
210
371
|
},
|
|
211
372
|
async getAuthToken() {
|
|
212
|
-
return await
|
|
373
|
+
return (await getAuthToken()) ?? null;
|
|
213
374
|
},
|
|
214
375
|
requestJson: requestJsonForTransport,
|
|
215
376
|
resolveContractRequest,
|
|
377
|
+
use(middleware) {
|
|
378
|
+
middlewareChain.push(middleware);
|
|
379
|
+
},
|
|
216
380
|
};
|
|
217
381
|
}
|
package/dist/http.d.ts
CHANGED
|
@@ -2,11 +2,11 @@ import { type AuthTokenResolver, type HeaderResolver, type MaybePromise, mergeHe
|
|
|
2
2
|
import { type BackoffPolicy, type ResolvedBackoffPolicy, computeBackoffDelay, normalizeBackoffPolicy, sleepWithSignal } from './backoff.js';
|
|
3
3
|
import { type HttpRetryPolicy, type ResolvedHttpRetryPolicy, DEFAULT_HTTP_RETRY_POLICY, getHttpRetryDelay, isRetryableHttpStatus, isRetryableNetworkError, normalizeHttpRetryPolicy, resolveHttpRetryPolicy } from './retry.js';
|
|
4
4
|
import { type ResolvedStreamReconnectPolicy, type StreamReconnectPolicy, DEFAULT_STREAM_RECONNECT_POLICY, getStreamReconnectDelay, normalizeStreamReconnectPolicy } from './reconnect.js';
|
|
5
|
-
import { createFetch, createJsonInit, createJsonRequestInit, readJsonBody, type HttpJsonRequestOptions, type HttpJsonTransport, type HttpJsonTransportOptions, type JsonObject, type JsonValue, type ResolvedContractRequest } from './http-core.js';
|
|
6
|
-
export type { AuthTokenResolver, BackoffPolicy, HeaderResolver, HttpJsonRequestOptions, HttpRetryPolicy, JsonObject, JsonValue, MaybePromise, ResolvedBackoffPolicy, ResolvedContractRequest, ResolvedHttpRetryPolicy, ResolvedStreamReconnectPolicy, StreamReconnectPolicy, };
|
|
5
|
+
import { createFetch, createJsonInit, createJsonRequestInit, generateIdempotencyKey, readJsonBody, requestJson, type HttpJsonRequestOptions, type HttpJsonTransport, type HttpJsonTransportOptions, type JsonObject, type JsonValue, type ResolvedContractRequest, type TransportContext, type TransportMiddleware, type TransportJsonError } from './http-core.js';
|
|
6
|
+
export type { AuthTokenResolver, BackoffPolicy, HeaderResolver, HttpJsonRequestOptions, HttpRetryPolicy, JsonObject, JsonValue, MaybePromise, ResolvedBackoffPolicy, ResolvedContractRequest, ResolvedHttpRetryPolicy, ResolvedStreamReconnectPolicy, StreamReconnectPolicy, TransportContext, TransportJsonError, TransportMiddleware, };
|
|
7
7
|
export type HttpTransportOptions = HttpJsonTransportOptions;
|
|
8
8
|
export type HttpTransport = HttpJsonTransport;
|
|
9
|
-
export { createFetch, createJsonInit, createJsonRequestInit, readJsonBody, mergeHeaders, resolveAuthToken, resolveHeaders, computeBackoffDelay, normalizeBackoffPolicy, sleepWithSignal, DEFAULT_HTTP_RETRY_POLICY, getHttpRetryDelay, isRetryableHttpStatus, isRetryableNetworkError, normalizeHttpRetryPolicy, resolveHttpRetryPolicy, DEFAULT_STREAM_RECONNECT_POLICY, getStreamReconnectDelay, normalizeStreamReconnectPolicy, };
|
|
9
|
+
export { createFetch, createJsonInit, createJsonRequestInit, generateIdempotencyKey, readJsonBody, requestJson, mergeHeaders, resolveAuthToken, resolveHeaders, computeBackoffDelay, normalizeBackoffPolicy, sleepWithSignal, DEFAULT_HTTP_RETRY_POLICY, getHttpRetryDelay, isRetryableHttpStatus, isRetryableNetworkError, normalizeHttpRetryPolicy, resolveHttpRetryPolicy, DEFAULT_STREAM_RECONNECT_POLICY, getStreamReconnectDelay, normalizeStreamReconnectPolicy, };
|
|
10
10
|
export declare function normalizeTransportError(error: unknown): Error;
|
|
11
11
|
export declare function createHttpTransport(options: HttpTransportOptions): HttpTransport;
|
|
12
12
|
//# sourceMappingURL=http.d.ts.map
|
package/dist/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACf,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,qBAAqB,EAC1B,mBAAmB,EACnB,sBAAsB,EACtB,eAAe,EAChB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,yBAAyB,EACzB,iBAAiB,EACjB,qBAAqB,EACrB,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,EACvB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,KAAK,6BAA6B,EAClC,KAAK,qBAAqB,EAC1B,+BAA+B,EAC/B,uBAAuB,EACvB,8BAA8B,EAC/B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,WAAW,EAEX,cAAc,EACd,qBAAqB,EACrB,YAAY,EACZ,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,uBAAuB,
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACf,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,qBAAqB,EAC1B,mBAAmB,EACnB,sBAAsB,EACtB,eAAe,EAChB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,yBAAyB,EACzB,iBAAiB,EACjB,qBAAqB,EACrB,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,EACvB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,KAAK,6BAA6B,EAClC,KAAK,qBAAqB,EAC1B,+BAA+B,EAC/B,uBAAuB,EACvB,8BAA8B,EAC/B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,WAAW,EAEX,cAAc,EACd,qBAAqB,EACrB,sBAAsB,EAEtB,YAAY,EACZ,WAAW,EACX,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,uBAAuB,EAC5B,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACxB,MAAM,gBAAgB,CAAC;AAExB,YAAY,EACV,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,sBAAsB,EACtB,eAAe,EACf,UAAU,EACV,SAAS,EACT,YAAY,EACZ,qBAAqB,EACrB,uBAAuB,EACvB,uBAAuB,EACvB,6BAA6B,EAC7B,qBAAqB,EACrB,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,GACpB,CAAC;AACF,MAAM,MAAM,oBAAoB,GAAG,wBAAwB,CAAC;AAC5D,MAAM,MAAM,aAAa,GAAG,iBAAiB,CAAC;AAC9C,OAAO,EACL,WAAW,EACX,cAAc,EACd,qBAAqB,EACrB,sBAAsB,EACtB,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,mBAAmB,EACnB,sBAAsB,EACtB,eAAe,EACf,yBAAyB,EACzB,iBAAiB,EACjB,qBAAqB,EACrB,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,EACtB,+BAA+B,EAC/B,uBAAuB,EACvB,8BAA8B,GAC/B,CAAC;AAwBF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CA8D7D;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,aAAa,CAmBhF"}
|
package/dist/http.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { ConfigurationError, ContractError, createHttpStatusError } from '@pellux/goodvibes-errors';
|
|
1
|
+
import { ConfigurationError, ContractError, GoodVibesSdkError, HttpStatusError, createHttpStatusError } from '@pellux/goodvibes-errors';
|
|
2
2
|
import { mergeHeaders, resolveAuthToken, resolveHeaders, } from './auth.js';
|
|
3
3
|
import { computeBackoffDelay, normalizeBackoffPolicy, sleepWithSignal, } from './backoff.js';
|
|
4
4
|
import { DEFAULT_HTTP_RETRY_POLICY, getHttpRetryDelay, isRetryableHttpStatus, isRetryableNetworkError, normalizeHttpRetryPolicy, resolveHttpRetryPolicy, } from './retry.js';
|
|
5
5
|
import { DEFAULT_STREAM_RECONNECT_POLICY, getStreamReconnectDelay, normalizeStreamReconnectPolicy, } from './reconnect.js';
|
|
6
|
-
import { createFetch, createHttpJsonTransport, createJsonInit, createJsonRequestInit, readJsonBody, } from './http-core.js';
|
|
7
|
-
export { createFetch, createJsonInit, createJsonRequestInit, readJsonBody, mergeHeaders, resolveAuthToken, resolveHeaders, computeBackoffDelay, normalizeBackoffPolicy, sleepWithSignal, DEFAULT_HTTP_RETRY_POLICY, getHttpRetryDelay, isRetryableHttpStatus, isRetryableNetworkError, normalizeHttpRetryPolicy, resolveHttpRetryPolicy, DEFAULT_STREAM_RECONNECT_POLICY, getStreamReconnectDelay, normalizeStreamReconnectPolicy, };
|
|
6
|
+
import { createFetch, createHttpJsonTransport, createJsonInit, createJsonRequestInit, generateIdempotencyKey, inferTransportHint, readJsonBody, requestJson, } from './http-core.js';
|
|
7
|
+
export { createFetch, createJsonInit, createJsonRequestInit, generateIdempotencyKey, readJsonBody, requestJson, mergeHeaders, resolveAuthToken, resolveHeaders, computeBackoffDelay, normalizeBackoffPolicy, sleepWithSignal, DEFAULT_HTTP_RETRY_POLICY, getHttpRetryDelay, isRetryableHttpStatus, isRetryableNetworkError, normalizeHttpRetryPolicy, resolveHttpRetryPolicy, DEFAULT_STREAM_RECONNECT_POLICY, getStreamReconnectDelay, normalizeStreamReconnectPolicy, };
|
|
8
8
|
function isTransportError(error) {
|
|
9
9
|
return Boolean(error
|
|
10
10
|
&& typeof error === 'object'
|
|
@@ -14,10 +14,45 @@ function isTransportError(error) {
|
|
|
14
14
|
&& typeof error.transport.url === 'string');
|
|
15
15
|
}
|
|
16
16
|
export function normalizeTransportError(error) {
|
|
17
|
+
// Fast path: already a structured SDK error — return directly, no re-wrapping needed.
|
|
18
|
+
// Covers HttpStatusError (subclass) and GoodVibesSdkError (e.g. SSE stream errors) alike.
|
|
19
|
+
if (error instanceof GoodVibesSdkError) {
|
|
20
|
+
return error;
|
|
21
|
+
}
|
|
17
22
|
if (isTransportError(error)) {
|
|
18
|
-
|
|
23
|
+
const { status, url, body, method, retryAfterMs, cause } = error.transport;
|
|
24
|
+
const resolvedMethod = typeof method === 'string' ? method : 'GET';
|
|
25
|
+
const hint = inferTransportHint(status, url, retryAfterMs);
|
|
26
|
+
if (status === 0) {
|
|
27
|
+
// Network-level failure: no HTTP response received
|
|
28
|
+
const networkError = new HttpStatusError(error instanceof Error ? error.message : `Transport could not reach ${url}`, {
|
|
29
|
+
status: undefined,
|
|
30
|
+
url,
|
|
31
|
+
method: resolvedMethod,
|
|
32
|
+
body,
|
|
33
|
+
category: 'network',
|
|
34
|
+
source: 'transport',
|
|
35
|
+
recoverable: true,
|
|
36
|
+
hint,
|
|
37
|
+
...(retryAfterMs !== undefined ? { retryAfterMs } : {}),
|
|
38
|
+
});
|
|
39
|
+
if (cause !== undefined) {
|
|
40
|
+
Object.defineProperty(networkError, 'cause', { value: cause, writable: true, configurable: true });
|
|
41
|
+
}
|
|
42
|
+
return Object.assign(networkError, { transport: error.transport });
|
|
43
|
+
}
|
|
44
|
+
const baseError = createHttpStatusError(status, url, resolvedMethod, body);
|
|
45
|
+
// Only apply inferred hint if the daemon body didn't supply one already
|
|
46
|
+
const effectiveHint = baseError.hint ?? hint;
|
|
47
|
+
return Object.assign(baseError, {
|
|
48
|
+
transport: error.transport,
|
|
49
|
+
...(effectiveHint !== undefined ? { hint: effectiveHint } : {}),
|
|
50
|
+
...(retryAfterMs !== undefined ? { retryAfterMs } : {}),
|
|
51
|
+
});
|
|
19
52
|
}
|
|
20
53
|
if (error instanceof Error) {
|
|
54
|
+
// Defensive string-match fallback for non-SDK errors that slip through.
|
|
55
|
+
// With structured throws in http-core.ts, these paths are rarely exercised.
|
|
21
56
|
if (error.message === 'Fetch implementation is required' || error.message === 'Transport baseUrl is required') {
|
|
22
57
|
return new ConfigurationError(error.message);
|
|
23
58
|
}
|
|
@@ -25,7 +60,13 @@ export function normalizeTransportError(error) {
|
|
|
25
60
|
return new ContractError(error.message);
|
|
26
61
|
}
|
|
27
62
|
}
|
|
28
|
-
return error instanceof Error
|
|
63
|
+
return error instanceof Error
|
|
64
|
+
? error
|
|
65
|
+
: new GoodVibesSdkError(`Transport operation failed with a non-Error value: ${String(error)}`, {
|
|
66
|
+
category: 'network',
|
|
67
|
+
source: 'transport',
|
|
68
|
+
recoverable: true,
|
|
69
|
+
});
|
|
29
70
|
}
|
|
30
71
|
export function createHttpTransport(options) {
|
|
31
72
|
const transport = createHttpJsonTransport(options);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
export type { ContractInvokeOptions, ContractRouteDefinition, ContractRouteLike, ContractStreamOptions, } from './contract-client.js';
|
|
2
2
|
export { buildContractInput, invokeContractRoute, openContractRouteStream, requireContractRoute, } from './contract-client.js';
|
|
3
|
-
export type {
|
|
4
|
-
export {
|
|
3
|
+
export type { JsonSchemaValidationFailure, MethodArgs, RequiredKeys, WithoutKeys } from './client-plumbing.js';
|
|
4
|
+
export { clientInputRecord, firstJsonSchemaFailure, mergeClientInput, splitClientArgs } from './client-plumbing.js';
|
|
5
|
+
export type { HttpJsonRequestOptions, HttpTransport, HttpTransportOptions, JsonObject, JsonValue, ResolvedContractRequest, TransportJsonError, } from './http.js';
|
|
6
|
+
export { createFetch, createHttpTransport, createJsonInit, createJsonRequestInit, normalizeTransportError, readJsonBody, requestJson, } from './http.js';
|
|
7
|
+
export type { TransportContext, TransportMiddleware } from './http-core.js';
|
|
8
|
+
export { generateIdempotencyKey } from './http-core.js';
|
|
5
9
|
export type { ServerSentEventHandlers, ServerSentEventOptions } from './sse.js';
|
|
6
10
|
export { openServerSentEventStream } from './sse.js';
|
|
7
11
|
export type { ServerSentEventHandlers as RawServerSentEventHandlers, ServerSentEventOptions as RawServerSentEventOptions } from './sse-stream.js';
|
|
8
|
-
export {
|
|
9
|
-
export type { AuthTokenResolver, HeaderResolver, MaybePromise } from './auth.js';
|
|
10
|
-
export { mergeHeaders, resolveAuthToken, resolveHeaders } from './auth.js';
|
|
12
|
+
export { openRawServerSentEventStream } from './sse-stream.js';
|
|
13
|
+
export type { AuthTokenInput, AuthTokenResolver, HeaderResolver, MaybePromise } from './auth.js';
|
|
14
|
+
export { mergeHeaderRecord, mergeHeaders, normalizeAuthToken, resolveAuthToken, resolveHeaders } from './auth.js';
|
|
11
15
|
export type { BackoffPolicy, ResolvedBackoffPolicy } from './backoff.js';
|
|
12
16
|
export { computeBackoffDelay, normalizeBackoffPolicy, sleepWithSignal } from './backoff.js';
|
|
13
|
-
export type { HttpRetryPolicy, ResolvedHttpRetryPolicy } from './retry.js';
|
|
14
|
-
export { DEFAULT_HTTP_RETRY_POLICY, getHttpRetryDelay, isRetryableHttpStatus, isRetryableNetworkError, normalizeHttpRetryPolicy, resolveHttpRetryPolicy } from './retry.js';
|
|
17
|
+
export type { HttpRetryPolicy, PerMethodRetryPolicy, ResolvedHttpRetryPolicy } from './retry.js';
|
|
18
|
+
export { DEFAULT_HTTP_RETRY_POLICY, applyPerMethodPolicy, getHttpRetryDelay, isRetryableHttpStatus, isRetryableNetworkError, normalizeHttpRetryPolicy, resolveHttpRetryPolicy } from './retry.js';
|
|
15
19
|
export type { StreamReconnectPolicy, ResolvedStreamReconnectPolicy } from './reconnect.js';
|
|
16
20
|
export { DEFAULT_STREAM_RECONNECT_POLICY, getStreamReconnectDelay, normalizeStreamReconnectPolicy } from './reconnect.js';
|
|
17
21
|
export type { TransportPaths } from './paths.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,qBAAqB,EACrB,uBAAuB,EACvB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,sBAAsB,EACtB,aAAa,EACb,oBAAoB,EACpB,UAAU,EACV,SAAS,EACT,uBAAuB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,qBAAqB,EACrB,uBAAuB,EACvB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,2BAA2B,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC/G,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACpH,YAAY,EACV,sBAAsB,EACtB,aAAa,EACb,oBAAoB,EACpB,UAAU,EACV,SAAS,EACT,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,YAAY,EACZ,WAAW,GACZ,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACxD,YAAY,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAChF,OAAO,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AACrD,YAAY,EAAE,uBAAuB,IAAI,0BAA0B,EAAE,sBAAsB,IAAI,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAClJ,OAAO,EAAE,4BAA4B,EAAE,MAAM,iBAAiB,CAAC;AAC/D,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACjG,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAClH,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC5F,YAAY,EAAE,eAAe,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AACjG,OAAO,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAClM,YAAY,EAAE,qBAAqB,EAAE,6BAA6B,EAAE,MAAM,gBAAgB,CAAC;AAC3F,OAAO,EAAE,+BAA+B,EAAE,uBAAuB,EAAE,8BAA8B,EAAE,MAAM,gBAAgB,CAAC;AAC1H,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export { buildContractInput, invokeContractRoute, openContractRouteStream, requireContractRoute, } from './contract-client.js';
|
|
2
|
-
export {
|
|
2
|
+
export { clientInputRecord, firstJsonSchemaFailure, mergeClientInput, splitClientArgs } from './client-plumbing.js';
|
|
3
|
+
export { createFetch, createHttpTransport, createJsonInit, createJsonRequestInit, normalizeTransportError, readJsonBody, requestJson, } from './http.js';
|
|
4
|
+
export { generateIdempotencyKey } from './http-core.js';
|
|
3
5
|
export { openServerSentEventStream } from './sse.js';
|
|
4
|
-
export {
|
|
5
|
-
export { mergeHeaders, resolveAuthToken, resolveHeaders } from './auth.js';
|
|
6
|
+
export { openRawServerSentEventStream } from './sse-stream.js';
|
|
7
|
+
export { mergeHeaderRecord, mergeHeaders, normalizeAuthToken, resolveAuthToken, resolveHeaders } from './auth.js';
|
|
6
8
|
export { computeBackoffDelay, normalizeBackoffPolicy, sleepWithSignal } from './backoff.js';
|
|
7
|
-
export { DEFAULT_HTTP_RETRY_POLICY, getHttpRetryDelay, isRetryableHttpStatus, isRetryableNetworkError, normalizeHttpRetryPolicy, resolveHttpRetryPolicy } from './retry.js';
|
|
9
|
+
export { DEFAULT_HTTP_RETRY_POLICY, applyPerMethodPolicy, getHttpRetryDelay, isRetryableHttpStatus, isRetryableNetworkError, normalizeHttpRetryPolicy, resolveHttpRetryPolicy } from './retry.js';
|
|
8
10
|
export { DEFAULT_STREAM_RECONNECT_POLICY, getStreamReconnectDelay, normalizeStreamReconnectPolicy } from './reconnect.js';
|
|
9
11
|
export { buildUrl, createTransportPaths, normalizeBaseUrl } from './paths.js';
|
package/dist/paths.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,uBAAuB,EAAE,MAAM,CAAC;IACzC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAMxD;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAG9D;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CA8BpE"}
|
package/dist/paths.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { ConfigurationError } from '@pellux/goodvibes-errors';
|
|
1
2
|
export function normalizeBaseUrl(baseUrl) {
|
|
2
3
|
const normalized = baseUrl.trim();
|
|
3
4
|
if (!normalized) {
|
|
4
|
-
throw new
|
|
5
|
+
throw new ConfigurationError('Transport baseUrl is required. Pass a non-empty baseUrl string to your transport or SDK options.', { code: 'SDK_TRANSPORT_BASE_URL_REQUIRED' });
|
|
5
6
|
}
|
|
6
|
-
return normalized.
|
|
7
|
+
return normalized.replace(/\/+$/, '');
|
|
7
8
|
}
|
|
8
9
|
export function buildUrl(baseUrl, path) {
|
|
9
10
|
const normalized = normalizeBaseUrl(baseUrl);
|
package/dist/reconnect.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export interface StreamReconnectPolicy extends BackoffPolicy {
|
|
|
5
5
|
export interface ResolvedStreamReconnectPolicy extends ResolvedBackoffPolicy {
|
|
6
6
|
readonly enabled: boolean;
|
|
7
7
|
}
|
|
8
|
+
/** Maximum reconnect attempts when reconnect is enabled and the caller does not set a limit. */
|
|
9
|
+
export declare const DEFAULT_STREAM_MAX_ATTEMPTS = 10;
|
|
8
10
|
export declare const DEFAULT_STREAM_RECONNECT_POLICY: ResolvedStreamReconnectPolicy;
|
|
9
11
|
export declare function normalizeStreamReconnectPolicy(policy?: StreamReconnectPolicy): ResolvedStreamReconnectPolicy;
|
|
10
12
|
export declare function getStreamReconnectDelay(attempt: number, policy: ResolvedStreamReconnectPolicy): number;
|