@robosystems/client 1.12.2 → 1.13.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/artifacts/InvestorClient.d.ts +7 -0
- package/artifacts/InvestorClient.ts +7 -0
- package/artifacts/LedgerClient.d.ts +7 -0
- package/artifacts/LedgerClient.ts +7 -0
- package/artifacts/LibraryClient.d.ts +7 -0
- package/artifacts/LibraryClient.ts +7 -0
- package/artifacts/OperationClient.d.ts +2 -0
- package/artifacts/OperationClient.ts +3 -0
- package/artifacts/OperatorClient.d.ts +40 -6
- package/artifacts/OperatorClient.js +171 -105
- package/artifacts/OperatorClient.ts +231 -134
- package/artifacts/QueryClient.d.ts +2 -0
- package/artifacts/QueryClient.ts +3 -0
- package/artifacts/SSEClient.d.ts +26 -0
- package/artifacts/SSEClient.js +28 -2
- package/artifacts/SSEClient.ts +51 -3
- package/artifacts/graphql/client.d.ts +8 -0
- package/artifacts/graphql/client.js +8 -1
- package/artifacts/graphql/client.ts +17 -1
- package/artifacts/hooks.js +5 -0
- package/artifacts/hooks.ts +5 -0
- package/artifacts/index.d.ts +7 -3
- package/artifacts/index.js +38 -3
- package/artifacts/index.ts +47 -5
- package/artifacts/retry.d.ts +59 -0
- package/artifacts/retry.js +121 -0
- package/artifacts/retry.ts +134 -0
- package/package.json +1 -1
|
@@ -25,6 +25,7 @@ exports.createGraphQLClient = createGraphQLClient;
|
|
|
25
25
|
* from the query files in clients/graphql/queries/.
|
|
26
26
|
*/
|
|
27
27
|
const graphql_request_1 = require("graphql-request");
|
|
28
|
+
const retry_1 = require("../retry");
|
|
28
29
|
/**
|
|
29
30
|
* Default request timeout for GraphQL calls, in milliseconds.
|
|
30
31
|
*
|
|
@@ -124,7 +125,13 @@ function createGraphQLClient(config, graphId) {
|
|
|
124
125
|
const staticHeaders = {
|
|
125
126
|
...(config.headers ?? {}),
|
|
126
127
|
};
|
|
127
|
-
|
|
128
|
+
// Retry wraps timeout, not the other way round, so each replay gets
|
|
129
|
+
// its own full timeout rather than sharing one across attempts.
|
|
130
|
+
const timeoutFetch = (0, retry_1.createRetryingFetch)({
|
|
131
|
+
fetch: createTimeoutFetch(config.timeout ?? exports.DEFAULT_GRAPHQL_TIMEOUT_MS),
|
|
132
|
+
maxRetries: config.maxRetries ?? retry_1.DEFAULT_MAX_RETRIES,
|
|
133
|
+
retryDelay: config.retryDelay ?? retry_1.DEFAULT_RETRY_DELAY_MS,
|
|
134
|
+
});
|
|
128
135
|
// Dynamic-token path: defer credential injection to a per-request
|
|
129
136
|
// middleware so JWT refreshes are picked up without rebuilding or
|
|
130
137
|
// clearing the client. This is the recommended path for browser
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
|
|
24
24
|
import { ClientError, GraphQLClient } from 'graphql-request'
|
|
25
25
|
|
|
26
|
+
import { createRetryingFetch, DEFAULT_MAX_RETRIES, DEFAULT_RETRY_DELAY_MS } from '../retry'
|
|
27
|
+
|
|
26
28
|
/**
|
|
27
29
|
* Default request timeout for GraphQL calls, in milliseconds.
|
|
28
30
|
*
|
|
@@ -113,6 +115,14 @@ export interface GraphQLClientConfig {
|
|
|
113
115
|
* client). Applied per request via `AbortSignal.timeout(...)`.
|
|
114
116
|
*/
|
|
115
117
|
timeout?: number
|
|
118
|
+
/**
|
|
119
|
+
* Replays of a rate-limited (429) request. Defaults to
|
|
120
|
+
* `DEFAULT_MAX_RETRIES`; set `0` to surface the rejection
|
|
121
|
+
* immediately. See `../retry`.
|
|
122
|
+
*/
|
|
123
|
+
maxRetries?: number
|
|
124
|
+
/** Base of the retry backoff, in milliseconds. */
|
|
125
|
+
retryDelay?: number
|
|
116
126
|
}
|
|
117
127
|
|
|
118
128
|
/**
|
|
@@ -175,7 +185,13 @@ export function createGraphQLClient(config: GraphQLClientConfig, graphId: string
|
|
|
175
185
|
const staticHeaders: Record<string, string> = {
|
|
176
186
|
...(config.headers ?? {}),
|
|
177
187
|
}
|
|
178
|
-
|
|
188
|
+
// Retry wraps timeout, not the other way round, so each replay gets
|
|
189
|
+
// its own full timeout rather than sharing one across attempts.
|
|
190
|
+
const timeoutFetch = createRetryingFetch({
|
|
191
|
+
fetch: createTimeoutFetch(config.timeout ?? DEFAULT_GRAPHQL_TIMEOUT_MS),
|
|
192
|
+
maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
193
|
+
retryDelay: config.retryDelay ?? DEFAULT_RETRY_DELAY_MS,
|
|
194
|
+
})
|
|
179
195
|
|
|
180
196
|
// Dynamic-token path: defer credential injection to a per-request
|
|
181
197
|
// middleware so JWT refreshes are picked up without rebuilding or
|
package/artifacts/hooks.js
CHANGED
|
@@ -45,6 +45,7 @@ function useQuery(graphId) {
|
|
|
45
45
|
credentials: sdkConfig.credentials,
|
|
46
46
|
headers: sdkConfig.headers,
|
|
47
47
|
token,
|
|
48
|
+
tokenProvider: sdkConfig.tokenProvider,
|
|
48
49
|
});
|
|
49
50
|
return () => {
|
|
50
51
|
clientRef.current?.close();
|
|
@@ -122,6 +123,7 @@ function useStreamingQuery(graphId) {
|
|
|
122
123
|
credentials: sdkConfig.credentials,
|
|
123
124
|
headers: sdkConfig.headers,
|
|
124
125
|
token: sdkConfig.token,
|
|
126
|
+
tokenProvider: sdkConfig.tokenProvider,
|
|
125
127
|
});
|
|
126
128
|
return () => {
|
|
127
129
|
clientRef.current?.close();
|
|
@@ -201,6 +203,7 @@ function useOperation(operationId) {
|
|
|
201
203
|
baseUrl: sdkConfig.baseUrl || clientConfig.baseUrl || 'http://localhost:8000',
|
|
202
204
|
credentials: sdkConfig.credentials,
|
|
203
205
|
token,
|
|
206
|
+
tokenProvider: sdkConfig.tokenProvider,
|
|
204
207
|
maxRetries: sdkConfig.maxRetries,
|
|
205
208
|
retryDelay: sdkConfig.retryDelay,
|
|
206
209
|
});
|
|
@@ -300,6 +303,7 @@ function useMultipleOperations() {
|
|
|
300
303
|
baseUrl: sdkConfig.baseUrl || clientConfig.baseUrl || 'http://localhost:8000',
|
|
301
304
|
credentials: sdkConfig.credentials,
|
|
302
305
|
token,
|
|
306
|
+
tokenProvider: sdkConfig.tokenProvider,
|
|
303
307
|
maxRetries: sdkConfig.maxRetries,
|
|
304
308
|
retryDelay: sdkConfig.retryDelay,
|
|
305
309
|
});
|
|
@@ -368,6 +372,7 @@ function useSDKClients() {
|
|
|
368
372
|
credentials: sdkConfig.credentials,
|
|
369
373
|
headers: sdkConfig.headers,
|
|
370
374
|
token,
|
|
375
|
+
tokenProvider: sdkConfig.tokenProvider,
|
|
371
376
|
};
|
|
372
377
|
const queryClient = new QueryClient_1.QueryClient(baseConfig);
|
|
373
378
|
const operationsClient = new OperationClient_1.OperationClient(baseConfig);
|
package/artifacts/hooks.ts
CHANGED
|
@@ -46,6 +46,7 @@ export function useQuery(graphId: string) {
|
|
|
46
46
|
credentials: sdkConfig.credentials,
|
|
47
47
|
headers: sdkConfig.headers,
|
|
48
48
|
token,
|
|
49
|
+
tokenProvider: sdkConfig.tokenProvider,
|
|
49
50
|
})
|
|
50
51
|
|
|
51
52
|
return () => {
|
|
@@ -143,6 +144,7 @@ export function useStreamingQuery(graphId: string) {
|
|
|
143
144
|
credentials: sdkConfig.credentials,
|
|
144
145
|
headers: sdkConfig.headers,
|
|
145
146
|
token: sdkConfig.token,
|
|
147
|
+
tokenProvider: sdkConfig.tokenProvider,
|
|
146
148
|
})
|
|
147
149
|
|
|
148
150
|
return () => {
|
|
@@ -240,6 +242,7 @@ export function useOperation<T = any>(operationId?: string) {
|
|
|
240
242
|
baseUrl: sdkConfig.baseUrl || clientConfig.baseUrl || 'http://localhost:8000',
|
|
241
243
|
credentials: sdkConfig.credentials,
|
|
242
244
|
token,
|
|
245
|
+
tokenProvider: sdkConfig.tokenProvider,
|
|
243
246
|
maxRetries: sdkConfig.maxRetries,
|
|
244
247
|
retryDelay: sdkConfig.retryDelay,
|
|
245
248
|
})
|
|
@@ -353,6 +356,7 @@ export function useMultipleOperations<T = any>() {
|
|
|
353
356
|
baseUrl: sdkConfig.baseUrl || clientConfig.baseUrl || 'http://localhost:8000',
|
|
354
357
|
credentials: sdkConfig.credentials,
|
|
355
358
|
token,
|
|
359
|
+
tokenProvider: sdkConfig.tokenProvider,
|
|
356
360
|
maxRetries: sdkConfig.maxRetries,
|
|
357
361
|
retryDelay: sdkConfig.retryDelay,
|
|
358
362
|
})
|
|
@@ -439,6 +443,7 @@ export function useSDKClients() {
|
|
|
439
443
|
credentials: sdkConfig.credentials,
|
|
440
444
|
headers: sdkConfig.headers,
|
|
441
445
|
token,
|
|
446
|
+
tokenProvider: sdkConfig.tokenProvider,
|
|
442
447
|
}
|
|
443
448
|
|
|
444
449
|
const queryClient = new QueryClient(baseConfig)
|
package/artifacts/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { QueryClient } from './QueryClient';
|
|
|
12
12
|
import { SSEClient } from './SSEClient';
|
|
13
13
|
export type { TokenProvider } from './graphql/client';
|
|
14
14
|
export { DEFAULT_GRAPHQL_TIMEOUT_MS, GraphQLError } from './graphql/client';
|
|
15
|
+
export { createRetryingFetch, DEFAULT_MAX_RETRIES, DEFAULT_RETRY_DELAY_MS, MAX_BACKOFF_MS, type RetryOptions, } from './retry';
|
|
15
16
|
export interface RoboSystemsClientConfig {
|
|
16
17
|
baseUrl?: string;
|
|
17
18
|
credentials?: 'include' | 'same-origin' | 'omit';
|
|
@@ -22,9 +23,12 @@ export interface RoboSystemsClientConfig {
|
|
|
22
23
|
*/
|
|
23
24
|
token?: string;
|
|
24
25
|
/**
|
|
25
|
-
* Dynamic credential callback invoked on every GraphQL request
|
|
26
|
-
* When set, JWT refreshes are picked up
|
|
27
|
-
* to rebuild or clear cached clients after a
|
|
26
|
+
* Dynamic credential callback invoked on every GraphQL request and
|
|
27
|
+
* every SSE connect. When set, JWT refreshes are picked up
|
|
28
|
+
* automatically — no need to rebuild or clear cached clients after a
|
|
29
|
+
* refresh. Browser sessions need this: the backend revokes the previous
|
|
30
|
+
* JWT on each refresh, so a static `token` captured at construction
|
|
31
|
+
* stops opening progress streams the moment the session rotates.
|
|
28
32
|
*/
|
|
29
33
|
tokenProvider?: TokenProvider;
|
|
30
34
|
headers?: Record<string, string>;
|
package/artifacts/index.js
CHANGED
|
@@ -18,7 +18,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
18
18
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
19
19
|
};
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
-
exports.operatorQuery = exports.streamQuery = exports.executeQuery = exports.monitorOperation = exports.clients = exports.useStreamingQuery = exports.useSDKClients = exports.useQuery = exports.useOperation = exports.useMultipleOperations = exports.SSEClient = exports.QueryClient = exports.OperatorClient = exports.OperationClient = exports.LibraryClient = exports.LedgerClient = exports.InvestorClient = exports.RoboSystemsClients = exports.GraphQLError = exports.DEFAULT_GRAPHQL_TIMEOUT_MS = void 0;
|
|
21
|
+
exports.operatorQuery = exports.streamQuery = exports.executeQuery = exports.monitorOperation = exports.clients = exports.useStreamingQuery = exports.useSDKClients = exports.useQuery = exports.useOperation = exports.useMultipleOperations = exports.SSEClient = exports.QueryClient = exports.OperatorClient = exports.OperationClient = exports.LibraryClient = exports.LedgerClient = exports.InvestorClient = exports.RoboSystemsClients = exports.MAX_BACKOFF_MS = exports.DEFAULT_RETRY_DELAY_MS = exports.DEFAULT_MAX_RETRIES = exports.createRetryingFetch = exports.GraphQLError = exports.DEFAULT_GRAPHQL_TIMEOUT_MS = void 0;
|
|
22
22
|
const client_gen_1 = require("../client.gen");
|
|
23
23
|
const config_1 = require("./config");
|
|
24
24
|
const InvestorClient_1 = require("./InvestorClient");
|
|
@@ -33,6 +33,7 @@ const OperatorClient_1 = require("./OperatorClient");
|
|
|
33
33
|
Object.defineProperty(exports, "OperatorClient", { enumerable: true, get: function () { return OperatorClient_1.OperatorClient; } });
|
|
34
34
|
const QueryClient_1 = require("./QueryClient");
|
|
35
35
|
Object.defineProperty(exports, "QueryClient", { enumerable: true, get: function () { return QueryClient_1.QueryClient; } });
|
|
36
|
+
const retry_1 = require("./retry");
|
|
36
37
|
const SSEClient_1 = require("./SSEClient");
|
|
37
38
|
Object.defineProperty(exports, "SSEClient", { enumerable: true, get: function () { return SSEClient_1.SSEClient; } });
|
|
38
39
|
// Structured GraphQL error thrown by facade reads (LedgerClient /
|
|
@@ -40,10 +41,31 @@ Object.defineProperty(exports, "SSEClient", { enumerable: true, get: function ()
|
|
|
40
41
|
var client_1 = require("./graphql/client");
|
|
41
42
|
Object.defineProperty(exports, "DEFAULT_GRAPHQL_TIMEOUT_MS", { enumerable: true, get: function () { return client_1.DEFAULT_GRAPHQL_TIMEOUT_MS; } });
|
|
42
43
|
Object.defineProperty(exports, "GraphQLError", { enumerable: true, get: function () { return client_1.GraphQLError; } });
|
|
44
|
+
// A `fetch` that replays rate-limited (429) requests. The facade
|
|
45
|
+
// clients install it themselves; export it so callers reaching for the
|
|
46
|
+
// raw generated SDK can opt in with
|
|
47
|
+
// `client.setConfig({ fetch: createRetryingFetch() })`.
|
|
48
|
+
var retry_2 = require("./retry");
|
|
49
|
+
Object.defineProperty(exports, "createRetryingFetch", { enumerable: true, get: function () { return retry_2.createRetryingFetch; } });
|
|
50
|
+
Object.defineProperty(exports, "DEFAULT_MAX_RETRIES", { enumerable: true, get: function () { return retry_2.DEFAULT_MAX_RETRIES; } });
|
|
51
|
+
Object.defineProperty(exports, "DEFAULT_RETRY_DELAY_MS", { enumerable: true, get: function () { return retry_2.DEFAULT_RETRY_DELAY_MS; } });
|
|
52
|
+
Object.defineProperty(exports, "MAX_BACKOFF_MS", { enumerable: true, get: function () { return retry_2.MAX_BACKOFF_MS; } });
|
|
43
53
|
class RoboSystemsClients {
|
|
44
54
|
constructor(config = {}) {
|
|
45
55
|
// Get base URL from SDK client config or use provided/default
|
|
46
56
|
const sdkConfig = client_gen_1.client.getConfig();
|
|
57
|
+
// Facade REST calls run through the generated ops, which share this
|
|
58
|
+
// module-level client — its `fetch` is the only interception point,
|
|
59
|
+
// since the ops take no per-call transport. A caller that already
|
|
60
|
+
// supplied one keeps it; we never override an explicit choice.
|
|
61
|
+
if (!sdkConfig.fetch) {
|
|
62
|
+
client_gen_1.client.setConfig({
|
|
63
|
+
fetch: (0, retry_1.createRetryingFetch)({
|
|
64
|
+
maxRetries: config.maxRetries ?? retry_1.DEFAULT_MAX_RETRIES,
|
|
65
|
+
retryDelay: config.retryDelay ?? retry_1.DEFAULT_RETRY_DELAY_MS,
|
|
66
|
+
}),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
47
69
|
// Extract JWT token using centralized logic
|
|
48
70
|
const token = config.token || (0, config_1.extractTokenFromSDKClient)();
|
|
49
71
|
// `tokenProvider` falls back to the global SDK clients config so
|
|
@@ -60,27 +82,33 @@ class RoboSystemsClients {
|
|
|
60
82
|
retryDelay: config.retryDelay || 1000,
|
|
61
83
|
timeout: config.timeout,
|
|
62
84
|
};
|
|
85
|
+
// Every client that opens an SSE stream gets the tokenProvider too:
|
|
86
|
+
// the stream endpoint authenticates the JWT from the URL, and a
|
|
87
|
+
// captured static token is dead after the first session refresh.
|
|
63
88
|
this.query = new QueryClient_1.QueryClient({
|
|
64
89
|
baseUrl: this.config.baseUrl,
|
|
65
90
|
credentials: this.config.credentials,
|
|
66
91
|
token: this.config.token,
|
|
92
|
+
tokenProvider: this.config.tokenProvider,
|
|
67
93
|
headers: this.config.headers,
|
|
68
94
|
});
|
|
69
95
|
this.operator = new OperatorClient_1.OperatorClient({
|
|
70
96
|
baseUrl: this.config.baseUrl,
|
|
71
97
|
credentials: this.config.credentials,
|
|
72
98
|
token: this.config.token,
|
|
99
|
+
tokenProvider: this.config.tokenProvider,
|
|
73
100
|
headers: this.config.headers,
|
|
74
101
|
});
|
|
75
102
|
this.operations = new OperationClient_1.OperationClient({
|
|
76
103
|
baseUrl: this.config.baseUrl,
|
|
77
104
|
credentials: this.config.credentials,
|
|
78
105
|
token: this.config.token,
|
|
106
|
+
tokenProvider: this.config.tokenProvider,
|
|
79
107
|
maxRetries: this.config.maxRetries,
|
|
80
108
|
retryDelay: this.config.retryDelay,
|
|
81
109
|
});
|
|
82
|
-
// LedgerClient / InvestorClient use GraphQL internally
|
|
83
|
-
//
|
|
110
|
+
// LedgerClient / InvestorClient / LibraryClient use GraphQL internally
|
|
111
|
+
// and consult the tokenProvider on every request.
|
|
84
112
|
this.ledger = new LedgerClient_1.LedgerClient({
|
|
85
113
|
baseUrl: this.config.baseUrl,
|
|
86
114
|
credentials: this.config.credentials,
|
|
@@ -88,6 +116,8 @@ class RoboSystemsClients {
|
|
|
88
116
|
tokenProvider: this.config.tokenProvider,
|
|
89
117
|
headers: this.config.headers,
|
|
90
118
|
timeout: this.config.timeout,
|
|
119
|
+
maxRetries: this.config.maxRetries,
|
|
120
|
+
retryDelay: this.config.retryDelay,
|
|
91
121
|
});
|
|
92
122
|
this.investor = new InvestorClient_1.InvestorClient({
|
|
93
123
|
baseUrl: this.config.baseUrl,
|
|
@@ -96,6 +126,8 @@ class RoboSystemsClients {
|
|
|
96
126
|
tokenProvider: this.config.tokenProvider,
|
|
97
127
|
headers: this.config.headers,
|
|
98
128
|
timeout: this.config.timeout,
|
|
129
|
+
maxRetries: this.config.maxRetries,
|
|
130
|
+
retryDelay: this.config.retryDelay,
|
|
99
131
|
});
|
|
100
132
|
// Library uses GraphQL and accepts graphId per-call — pass either
|
|
101
133
|
// the `"library"` sentinel (canonical) or any tenant graph_id
|
|
@@ -107,6 +139,8 @@ class RoboSystemsClients {
|
|
|
107
139
|
tokenProvider: this.config.tokenProvider,
|
|
108
140
|
headers: this.config.headers,
|
|
109
141
|
timeout: this.config.timeout,
|
|
142
|
+
maxRetries: this.config.maxRetries,
|
|
143
|
+
retryDelay: this.config.retryDelay,
|
|
110
144
|
});
|
|
111
145
|
// Reports consolidated into LedgerClient — alias for backward compat
|
|
112
146
|
this.reports = this.ledger;
|
|
@@ -125,6 +159,7 @@ class RoboSystemsClients {
|
|
|
125
159
|
baseUrl: this.config.baseUrl,
|
|
126
160
|
credentials: this.config.credentials,
|
|
127
161
|
token: this.config.token,
|
|
162
|
+
tokenProvider: this.config.tokenProvider,
|
|
128
163
|
headers: this.config.headers,
|
|
129
164
|
maxRetries: this.config.maxRetries,
|
|
130
165
|
retryDelay: this.config.retryDelay,
|
package/artifacts/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { LibraryClient } from './LibraryClient'
|
|
|
12
12
|
import { OperationClient } from './OperationClient'
|
|
13
13
|
import { OperatorClient } from './OperatorClient'
|
|
14
14
|
import { QueryClient } from './QueryClient'
|
|
15
|
+
import { createRetryingFetch, DEFAULT_MAX_RETRIES, DEFAULT_RETRY_DELAY_MS } from './retry'
|
|
15
16
|
import { SSEClient } from './SSEClient'
|
|
16
17
|
|
|
17
18
|
// Re-export the `TokenProvider` type so consumers who want to type
|
|
@@ -24,6 +25,18 @@ export type { TokenProvider } from './graphql/client'
|
|
|
24
25
|
// InvestorClient / LibraryClient), plus the default request timeout.
|
|
25
26
|
export { DEFAULT_GRAPHQL_TIMEOUT_MS, GraphQLError } from './graphql/client'
|
|
26
27
|
|
|
28
|
+
// A `fetch` that replays rate-limited (429) requests. The facade
|
|
29
|
+
// clients install it themselves; export it so callers reaching for the
|
|
30
|
+
// raw generated SDK can opt in with
|
|
31
|
+
// `client.setConfig({ fetch: createRetryingFetch() })`.
|
|
32
|
+
export {
|
|
33
|
+
createRetryingFetch,
|
|
34
|
+
DEFAULT_MAX_RETRIES,
|
|
35
|
+
DEFAULT_RETRY_DELAY_MS,
|
|
36
|
+
MAX_BACKOFF_MS,
|
|
37
|
+
type RetryOptions,
|
|
38
|
+
} from './retry'
|
|
39
|
+
|
|
27
40
|
export interface RoboSystemsClientConfig {
|
|
28
41
|
baseUrl?: string
|
|
29
42
|
credentials?: 'include' | 'same-origin' | 'omit'
|
|
@@ -34,9 +47,12 @@ export interface RoboSystemsClientConfig {
|
|
|
34
47
|
*/
|
|
35
48
|
token?: string
|
|
36
49
|
/**
|
|
37
|
-
* Dynamic credential callback invoked on every GraphQL request
|
|
38
|
-
* When set, JWT refreshes are picked up
|
|
39
|
-
* to rebuild or clear cached clients after a
|
|
50
|
+
* Dynamic credential callback invoked on every GraphQL request and
|
|
51
|
+
* every SSE connect. When set, JWT refreshes are picked up
|
|
52
|
+
* automatically — no need to rebuild or clear cached clients after a
|
|
53
|
+
* refresh. Browser sessions need this: the backend revokes the previous
|
|
54
|
+
* JWT on each refresh, so a static `token` captured at construction
|
|
55
|
+
* stops opening progress streams the moment the session rotates.
|
|
40
56
|
*/
|
|
41
57
|
tokenProvider?: TokenProvider
|
|
42
58
|
headers?: Record<string, string>
|
|
@@ -79,6 +95,19 @@ export class RoboSystemsClients {
|
|
|
79
95
|
// Get base URL from SDK client config or use provided/default
|
|
80
96
|
const sdkConfig = client.getConfig()
|
|
81
97
|
|
|
98
|
+
// Facade REST calls run through the generated ops, which share this
|
|
99
|
+
// module-level client — its `fetch` is the only interception point,
|
|
100
|
+
// since the ops take no per-call transport. A caller that already
|
|
101
|
+
// supplied one keeps it; we never override an explicit choice.
|
|
102
|
+
if (!sdkConfig.fetch) {
|
|
103
|
+
client.setConfig({
|
|
104
|
+
fetch: createRetryingFetch({
|
|
105
|
+
maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
106
|
+
retryDelay: config.retryDelay ?? DEFAULT_RETRY_DELAY_MS,
|
|
107
|
+
}),
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
82
111
|
// Extract JWT token using centralized logic
|
|
83
112
|
const token = config.token || extractTokenFromSDKClient()
|
|
84
113
|
|
|
@@ -98,10 +127,14 @@ export class RoboSystemsClients {
|
|
|
98
127
|
timeout: config.timeout,
|
|
99
128
|
}
|
|
100
129
|
|
|
130
|
+
// Every client that opens an SSE stream gets the tokenProvider too:
|
|
131
|
+
// the stream endpoint authenticates the JWT from the URL, and a
|
|
132
|
+
// captured static token is dead after the first session refresh.
|
|
101
133
|
this.query = new QueryClient({
|
|
102
134
|
baseUrl: this.config.baseUrl,
|
|
103
135
|
credentials: this.config.credentials,
|
|
104
136
|
token: this.config.token,
|
|
137
|
+
tokenProvider: this.config.tokenProvider,
|
|
105
138
|
headers: this.config.headers,
|
|
106
139
|
})
|
|
107
140
|
|
|
@@ -109,6 +142,7 @@ export class RoboSystemsClients {
|
|
|
109
142
|
baseUrl: this.config.baseUrl,
|
|
110
143
|
credentials: this.config.credentials,
|
|
111
144
|
token: this.config.token,
|
|
145
|
+
tokenProvider: this.config.tokenProvider,
|
|
112
146
|
headers: this.config.headers,
|
|
113
147
|
})
|
|
114
148
|
|
|
@@ -116,12 +150,13 @@ export class RoboSystemsClients {
|
|
|
116
150
|
baseUrl: this.config.baseUrl,
|
|
117
151
|
credentials: this.config.credentials,
|
|
118
152
|
token: this.config.token,
|
|
153
|
+
tokenProvider: this.config.tokenProvider,
|
|
119
154
|
maxRetries: this.config.maxRetries,
|
|
120
155
|
retryDelay: this.config.retryDelay,
|
|
121
156
|
})
|
|
122
157
|
|
|
123
|
-
// LedgerClient / InvestorClient use GraphQL internally
|
|
124
|
-
//
|
|
158
|
+
// LedgerClient / InvestorClient / LibraryClient use GraphQL internally
|
|
159
|
+
// and consult the tokenProvider on every request.
|
|
125
160
|
this.ledger = new LedgerClient({
|
|
126
161
|
baseUrl: this.config.baseUrl,
|
|
127
162
|
credentials: this.config.credentials,
|
|
@@ -129,6 +164,8 @@ export class RoboSystemsClients {
|
|
|
129
164
|
tokenProvider: this.config.tokenProvider,
|
|
130
165
|
headers: this.config.headers,
|
|
131
166
|
timeout: this.config.timeout,
|
|
167
|
+
maxRetries: this.config.maxRetries,
|
|
168
|
+
retryDelay: this.config.retryDelay,
|
|
132
169
|
})
|
|
133
170
|
|
|
134
171
|
this.investor = new InvestorClient({
|
|
@@ -138,6 +175,8 @@ export class RoboSystemsClients {
|
|
|
138
175
|
tokenProvider: this.config.tokenProvider,
|
|
139
176
|
headers: this.config.headers,
|
|
140
177
|
timeout: this.config.timeout,
|
|
178
|
+
maxRetries: this.config.maxRetries,
|
|
179
|
+
retryDelay: this.config.retryDelay,
|
|
141
180
|
})
|
|
142
181
|
|
|
143
182
|
// Library uses GraphQL and accepts graphId per-call — pass either
|
|
@@ -150,6 +189,8 @@ export class RoboSystemsClients {
|
|
|
150
189
|
tokenProvider: this.config.tokenProvider,
|
|
151
190
|
headers: this.config.headers,
|
|
152
191
|
timeout: this.config.timeout,
|
|
192
|
+
maxRetries: this.config.maxRetries,
|
|
193
|
+
retryDelay: this.config.retryDelay,
|
|
153
194
|
})
|
|
154
195
|
|
|
155
196
|
// Reports consolidated into LedgerClient — alias for backward compat
|
|
@@ -171,6 +212,7 @@ export class RoboSystemsClients {
|
|
|
171
212
|
baseUrl: this.config.baseUrl,
|
|
172
213
|
credentials: this.config.credentials,
|
|
173
214
|
token: this.config.token,
|
|
215
|
+
tokenProvider: this.config.tokenProvider,
|
|
174
216
|
headers: this.config.headers,
|
|
175
217
|
maxRetries: this.config.maxRetries,
|
|
176
218
|
retryDelay: this.config.retryDelay,
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate-limit-aware `fetch` wrapper shared by the facade clients.
|
|
3
|
+
*
|
|
4
|
+
* The API rate-limits per user per endpoint category and answers an
|
|
5
|
+
* exhausted budget with `429` plus `Retry-After` / `X-RateLimit-*`
|
|
6
|
+
* headers. That rejection is raised by a request dependency *before*
|
|
7
|
+
* the endpoint handler runs, so the request had no effect and is always
|
|
8
|
+
* safe to replay — including a `POST` carrying no idempotency key.
|
|
9
|
+
* Nothing other than `429` is retried here, precisely because nothing
|
|
10
|
+
* else carries that guarantee.
|
|
11
|
+
*
|
|
12
|
+
* Mirrors `robosystems_client/clients/retry.py` in the Python client.
|
|
13
|
+
*/
|
|
14
|
+
/** The one status a rejected request is known to have had no effect for. */
|
|
15
|
+
export declare const RETRY_STATUS_CODES: Set<number>;
|
|
16
|
+
export declare const DEFAULT_MAX_RETRIES = 5;
|
|
17
|
+
export declare const DEFAULT_RETRY_DELAY_MS = 1000;
|
|
18
|
+
export declare const MAX_BACKOFF_MS = 30000;
|
|
19
|
+
export interface RetryOptions {
|
|
20
|
+
/** Replays after the first attempt. `0` disables retrying entirely. */
|
|
21
|
+
maxRetries?: number;
|
|
22
|
+
/** Base of the exponential backoff, in milliseconds. */
|
|
23
|
+
retryDelay?: number;
|
|
24
|
+
/** Underlying fetch to wrap. Defaults to the global one, resolved per call. */
|
|
25
|
+
fetch?: typeof fetch;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Parse `Retry-After` as a delta-seconds value, in milliseconds.
|
|
29
|
+
*
|
|
30
|
+
* The API always sends the numeric form. The HTTP-date form is ignored
|
|
31
|
+
* rather than parsed, since treating an unreadable value as "no hint"
|
|
32
|
+
* degrades to plain backoff instead of to a wrong sleep.
|
|
33
|
+
*/
|
|
34
|
+
export declare function retryAfterMs(response: Response): number | null;
|
|
35
|
+
/**
|
|
36
|
+
* Milliseconds to wait before replaying a rate-limited request.
|
|
37
|
+
*
|
|
38
|
+
* Exponential with full jitter, and `Retry-After` applied as a
|
|
39
|
+
* *ceiling* rather than as the delay itself. The limiter is a sliding
|
|
40
|
+
* window, so `Retry-After` reports the whole window — the worst case
|
|
41
|
+
* for a client that filled its budget instantaneously. A caller that
|
|
42
|
+
* merely ran at the sustained rate has slots freeing up within a second
|
|
43
|
+
* or two, and obeying the header literally would turn a handful of
|
|
44
|
+
* rejections into minutes of idling.
|
|
45
|
+
*/
|
|
46
|
+
export declare function backoffMs(attempt: number, retryDelay: number, retryAfter: number | null): number;
|
|
47
|
+
/**
|
|
48
|
+
* Wrap a `fetch` so rate-limited requests are replayed.
|
|
49
|
+
*
|
|
50
|
+
* Composes over an existing fetch rather than replacing it, so a
|
|
51
|
+
* per-request timeout wrapper stays in place and each attempt gets its
|
|
52
|
+
* own timeout. The global `fetch` is resolved at call time (not
|
|
53
|
+
* captured) so test harnesses that swap `globalThis.fetch` keep
|
|
54
|
+
* working.
|
|
55
|
+
*
|
|
56
|
+
* Set `maxRetries: 0` to opt out — an interactive surface may well
|
|
57
|
+
* prefer to surface the rejection immediately rather than wait.
|
|
58
|
+
*/
|
|
59
|
+
export declare function createRetryingFetch(options?: RetryOptions): typeof fetch;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Rate-limit-aware `fetch` wrapper shared by the facade clients.
|
|
4
|
+
*
|
|
5
|
+
* The API rate-limits per user per endpoint category and answers an
|
|
6
|
+
* exhausted budget with `429` plus `Retry-After` / `X-RateLimit-*`
|
|
7
|
+
* headers. That rejection is raised by a request dependency *before*
|
|
8
|
+
* the endpoint handler runs, so the request had no effect and is always
|
|
9
|
+
* safe to replay — including a `POST` carrying no idempotency key.
|
|
10
|
+
* Nothing other than `429` is retried here, precisely because nothing
|
|
11
|
+
* else carries that guarantee.
|
|
12
|
+
*
|
|
13
|
+
* Mirrors `robosystems_client/clients/retry.py` in the Python client.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.MAX_BACKOFF_MS = exports.DEFAULT_RETRY_DELAY_MS = exports.DEFAULT_MAX_RETRIES = exports.RETRY_STATUS_CODES = void 0;
|
|
17
|
+
exports.retryAfterMs = retryAfterMs;
|
|
18
|
+
exports.backoffMs = backoffMs;
|
|
19
|
+
exports.createRetryingFetch = createRetryingFetch;
|
|
20
|
+
/** The one status a rejected request is known to have had no effect for. */
|
|
21
|
+
exports.RETRY_STATUS_CODES = new Set([429]);
|
|
22
|
+
exports.DEFAULT_MAX_RETRIES = 5;
|
|
23
|
+
exports.DEFAULT_RETRY_DELAY_MS = 1000;
|
|
24
|
+
exports.MAX_BACKOFF_MS = 30000;
|
|
25
|
+
/**
|
|
26
|
+
* Parse `Retry-After` as a delta-seconds value, in milliseconds.
|
|
27
|
+
*
|
|
28
|
+
* The API always sends the numeric form. The HTTP-date form is ignored
|
|
29
|
+
* rather than parsed, since treating an unreadable value as "no hint"
|
|
30
|
+
* degrades to plain backoff instead of to a wrong sleep.
|
|
31
|
+
*/
|
|
32
|
+
function retryAfterMs(response) {
|
|
33
|
+
const raw = response.headers.get('retry-after');
|
|
34
|
+
if (!raw) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const seconds = Number(raw.trim());
|
|
38
|
+
if (!Number.isFinite(seconds) || seconds < 0) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return seconds * 1000;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Milliseconds to wait before replaying a rate-limited request.
|
|
45
|
+
*
|
|
46
|
+
* Exponential with full jitter, and `Retry-After` applied as a
|
|
47
|
+
* *ceiling* rather than as the delay itself. The limiter is a sliding
|
|
48
|
+
* window, so `Retry-After` reports the whole window — the worst case
|
|
49
|
+
* for a client that filled its budget instantaneously. A caller that
|
|
50
|
+
* merely ran at the sustained rate has slots freeing up within a second
|
|
51
|
+
* or two, and obeying the header literally would turn a handful of
|
|
52
|
+
* rejections into minutes of idling.
|
|
53
|
+
*/
|
|
54
|
+
function backoffMs(attempt, retryDelay, retryAfter) {
|
|
55
|
+
let ceiling = Math.min(retryDelay * 2 ** attempt, exports.MAX_BACKOFF_MS);
|
|
56
|
+
if (retryAfter !== null) {
|
|
57
|
+
ceiling = Math.min(ceiling, retryAfter);
|
|
58
|
+
}
|
|
59
|
+
return ceiling / 2 + Math.random() * (ceiling / 2);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Whether a request can be sent a second time.
|
|
63
|
+
*
|
|
64
|
+
* A `ReadableStream` body is consumed by the first attempt, so
|
|
65
|
+
* replaying it would send nothing. Strings, `FormData`, `Blob` and
|
|
66
|
+
* typed arrays — everything the SDK and the GraphQL client actually
|
|
67
|
+
* produce — are re-readable.
|
|
68
|
+
*/
|
|
69
|
+
function isReplayable(init) {
|
|
70
|
+
const body = init?.body;
|
|
71
|
+
return !(typeof ReadableStream !== 'undefined' && body instanceof ReadableStream);
|
|
72
|
+
}
|
|
73
|
+
function sleep(ms, signal) {
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
const timer = setTimeout(() => {
|
|
76
|
+
signal?.removeEventListener('abort', onAbort);
|
|
77
|
+
resolve();
|
|
78
|
+
}, ms);
|
|
79
|
+
function onAbort() {
|
|
80
|
+
clearTimeout(timer);
|
|
81
|
+
reject(signal?.reason ?? new DOMException('Aborted', 'AbortError'));
|
|
82
|
+
}
|
|
83
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Wrap a `fetch` so rate-limited requests are replayed.
|
|
88
|
+
*
|
|
89
|
+
* Composes over an existing fetch rather than replacing it, so a
|
|
90
|
+
* per-request timeout wrapper stays in place and each attempt gets its
|
|
91
|
+
* own timeout. The global `fetch` is resolved at call time (not
|
|
92
|
+
* captured) so test harnesses that swap `globalThis.fetch` keep
|
|
93
|
+
* working.
|
|
94
|
+
*
|
|
95
|
+
* Set `maxRetries: 0` to opt out — an interactive surface may well
|
|
96
|
+
* prefer to surface the rejection immediately rather than wait.
|
|
97
|
+
*/
|
|
98
|
+
function createRetryingFetch(options = {}) {
|
|
99
|
+
const maxRetries = Math.max(0, options.maxRetries ?? exports.DEFAULT_MAX_RETRIES);
|
|
100
|
+
const retryDelay = Math.max(1, options.retryDelay ?? exports.DEFAULT_RETRY_DELAY_MS);
|
|
101
|
+
const inner = options.fetch;
|
|
102
|
+
return async (input, init) => {
|
|
103
|
+
const send = () => (inner ?? fetch)(input, init);
|
|
104
|
+
let response = await send();
|
|
105
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
106
|
+
if (!exports.RETRY_STATUS_CODES.has(response.status) || !isReplayable(init)) {
|
|
107
|
+
return response;
|
|
108
|
+
}
|
|
109
|
+
if (init?.signal?.aborted) {
|
|
110
|
+
return response;
|
|
111
|
+
}
|
|
112
|
+
const delay = backoffMs(attempt, retryDelay, retryAfterMs(response));
|
|
113
|
+
// The rejection body goes unused, but leaving it undrained keeps
|
|
114
|
+
// the connection pinned in some runtimes.
|
|
115
|
+
await response.body?.cancel().catch(() => { });
|
|
116
|
+
await sleep(delay, init?.signal);
|
|
117
|
+
response = await send();
|
|
118
|
+
}
|
|
119
|
+
return response;
|
|
120
|
+
};
|
|
121
|
+
}
|