@0xinsider/mcp 1.3.1 → 2.0.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 +91 -0
- package/dist/cli-lib.d.ts.map +1 -1
- package/dist/cli-lib.js +9 -5
- package/dist/cli-lib.js.map +1 -1
- package/dist/cli.js +6 -1
- package/dist/cli.js.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/product-cli/errors.d.ts +7 -0
- package/dist/product-cli/errors.d.ts.map +1 -0
- package/dist/product-cli/errors.js +13 -0
- package/dist/product-cli/errors.js.map +1 -0
- package/dist/product-cli/index.d.ts +4 -0
- package/dist/product-cli/index.d.ts.map +1 -0
- package/dist/product-cli/index.js +372 -0
- package/dist/product-cli/index.js.map +1 -0
- package/dist/product-cli/oauth.d.ts +10 -0
- package/dist/product-cli/oauth.d.ts.map +1 -0
- package/dist/product-cli/oauth.js +245 -0
- package/dist/product-cli/oauth.js.map +1 -0
- package/dist/product-cli/storage.d.ts +20 -0
- package/dist/product-cli/storage.d.ts.map +1 -0
- package/dist/product-cli/storage.js +149 -0
- package/dist/product-cli/storage.js.map +1 -0
- package/dist/sdk/client.d.ts +765 -0
- package/dist/sdk/client.d.ts.map +1 -0
- package/dist/sdk/client.js +974 -0
- package/dist/sdk/client.js.map +1 -0
- package/dist/sdk/errors.d.ts +313 -0
- package/dist/sdk/errors.d.ts.map +1 -0
- package/dist/sdk/errors.js +521 -0
- package/dist/sdk/errors.js.map +1 -0
- package/dist/sdk/index.d.ts +26 -0
- package/dist/sdk/index.d.ts.map +1 -0
- package/dist/sdk/index.js +25 -0
- package/dist/sdk/index.js.map +1 -0
- package/dist/sdk/pagination.d.ts +76 -0
- package/dist/sdk/pagination.d.ts.map +1 -0
- package/dist/sdk/pagination.js +106 -0
- package/dist/sdk/pagination.js.map +1 -0
- package/dist/sdk/schema.d.ts +2372 -0
- package/dist/sdk/schema.d.ts.map +1 -0
- package/dist/sdk/schema.js +7 -0
- package/dist/sdk/schema.js.map +1 -0
- package/dist/sdk/stream.d.ts +182 -0
- package/dist/sdk/stream.d.ts.map +1 -0
- package/dist/sdk/stream.js +391 -0
- package/dist/sdk/stream.js.map +1 -0
- package/dist/sdk/webhooks.d.ts +163 -0
- package/dist/sdk/webhooks.d.ts.map +1 -0
- package/dist/sdk/webhooks.js +122 -0
- package/dist/sdk/webhooks.js.map +1 -0
- package/package.json +6 -3
|
@@ -0,0 +1,974 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standalone 0xinsider API V1 client.
|
|
3
|
+
*
|
|
4
|
+
* Ported from the app's original repo-owned client (`web/src/lib/api-client`,
|
|
5
|
+
* removed in #9060; this package is now the sole client implementation). The
|
|
6
|
+
* `API_CLIENT_OPERATIONS` table below IS the contract surface:
|
|
7
|
+
* `scripts/check-sdk-openapi-drift.mjs` asserts it equals the set of operations
|
|
8
|
+
* with a `200` response in `web/public/api/v1/openapi.json`, so this package
|
|
9
|
+
* cannot silently diverge from the spec. That enforcement used to be a vitest
|
|
10
|
+
* suite, which the repository does not run -- the table shipped three
|
|
11
|
+
* operations short before #9687 replaced it with the runnable script, and
|
|
12
|
+
* #11894 deleted the suite.
|
|
13
|
+
*
|
|
14
|
+
* Differences from the retired app client (intentional):
|
|
15
|
+
* - No `@/lib/api-contracts` import; the operation table is inlined so the
|
|
16
|
+
* package has zero workspace coupling.
|
|
17
|
+
* - Errors throw the typed subclass hierarchy in `errors.ts` (the app client
|
|
18
|
+
* threw a single `OxinsiderApiError`).
|
|
19
|
+
* - Extra typed convenience methods for list/candle/webhook-event/stream
|
|
20
|
+
* surfaces and a typed `Grade` enum.
|
|
21
|
+
*/
|
|
22
|
+
import { errorFromResponse, OxinsiderApiError, RequestTimeoutError, } from "./errors.js";
|
|
23
|
+
/**
|
|
24
|
+
* The full V1 operation table. One row per OpenAPI operation that returns a
|
|
25
|
+
* `200`. Kept in lockstep with `web/public/api/v1/openapi.json` by
|
|
26
|
+
* `scripts/check-sdk-openapi-drift.mjs`. Do not add a row without a matching
|
|
27
|
+
* spec operation, and do not remove a spec operation without removing its row.
|
|
28
|
+
*/
|
|
29
|
+
export const API_CLIENT_OPERATIONS = [
|
|
30
|
+
{ method: "GET", path: "/api/v1/me", operationId: "getAccountIdentity", auth: "bearer" },
|
|
31
|
+
{
|
|
32
|
+
method: "GET",
|
|
33
|
+
path: "/api/v1",
|
|
34
|
+
operationId: "getApiDiscovery",
|
|
35
|
+
auth: "none",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
method: "GET",
|
|
39
|
+
path: "/api/v1/trader/{address}",
|
|
40
|
+
operationId: "getTrader",
|
|
41
|
+
auth: "bearer",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
method: "POST",
|
|
45
|
+
path: "/api/v1/traders/batch",
|
|
46
|
+
operationId: "batchGetTraders",
|
|
47
|
+
auth: "bearer",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
method: "GET",
|
|
51
|
+
path: "/api/v1/trader/{address}/position-timeline",
|
|
52
|
+
operationId: "getPositionTimeline",
|
|
53
|
+
auth: "bearer",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
method: "GET",
|
|
57
|
+
path: "/api/v1/traders/{trader}/position-timeline",
|
|
58
|
+
operationId: "getPositionTimelineById",
|
|
59
|
+
auth: "bearer",
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
method: "GET",
|
|
63
|
+
path: "/api/v1/positions",
|
|
64
|
+
operationId: "listPositions",
|
|
65
|
+
auth: "bearer",
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
method: "GET",
|
|
69
|
+
path: "/api/v1/large-positions",
|
|
70
|
+
operationId: "listLargePositions",
|
|
71
|
+
auth: "bearer",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
method: "GET",
|
|
75
|
+
path: "/api/v1/trader/{address}/pnl",
|
|
76
|
+
operationId: "getTraderPnl",
|
|
77
|
+
auth: "bearer",
|
|
78
|
+
},
|
|
79
|
+
// Pre-existing op-table drift (surfaced by test/drift.test.ts, #6915): these
|
|
80
|
+
// 200-returning openapi operations were never mirrored into the client table.
|
|
81
|
+
// Added so the table stays contract-complete against the checked-in spec. All
|
|
82
|
+
// inherit the global `bearerAuth` security (no per-op `security: []` override,
|
|
83
|
+
// and none are in the backend public-path allowlist), so auth is "bearer".
|
|
84
|
+
// The drift check intentionally excludes the redirect-only routes (openapi-spec
|
|
85
|
+
// redirect + export/download, no 200 response), so they are NOT added here.
|
|
86
|
+
{
|
|
87
|
+
method: "GET",
|
|
88
|
+
path: "/api/v1/trader/{address}/context",
|
|
89
|
+
operationId: "getTraderContext",
|
|
90
|
+
auth: "bearer",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
method: "GET",
|
|
94
|
+
path: "/api/v1/trader/{address}/context.md",
|
|
95
|
+
operationId: "getTraderContextMarkdown",
|
|
96
|
+
auth: "bearer",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
method: "POST",
|
|
100
|
+
path: "/api/v1/trader/{address}/export",
|
|
101
|
+
operationId: "submitTraderExport",
|
|
102
|
+
auth: "bearer",
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
method: "GET",
|
|
106
|
+
path: "/api/v1/trader/{address}/export/status",
|
|
107
|
+
operationId: "getTraderExportStatus",
|
|
108
|
+
auth: "bearer",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
method: "GET",
|
|
112
|
+
path: "/api/v1/leaderboard/trending",
|
|
113
|
+
operationId: "listTrendingWallets",
|
|
114
|
+
auth: "bearer",
|
|
115
|
+
},
|
|
116
|
+
// Pre-existing gap, fixed here (#7209): the route has shipped in openapi.json,
|
|
117
|
+
// llms-full.txt, agents.md, and the discovery doc, but never in the SDK table --
|
|
118
|
+
// so `drift.test.ts` was RED on main and SDK users had no typed method for it.
|
|
119
|
+
{
|
|
120
|
+
method: "GET",
|
|
121
|
+
path: "/api/v1/sports-edge-signals",
|
|
122
|
+
operationId: "listSportsEdgeSignals",
|
|
123
|
+
auth: "bearer",
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
method: "GET",
|
|
127
|
+
path: "/api/v1/sports-edge-observations",
|
|
128
|
+
operationId: "listSportsEdgeObservations",
|
|
129
|
+
auth: "bearer",
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
method: "GET",
|
|
133
|
+
path: "/api/v1/whale-trades",
|
|
134
|
+
operationId: "listWhaleTrades",
|
|
135
|
+
auth: "bearer",
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
method: "GET",
|
|
139
|
+
path: "/api/v1/whale-trades/history",
|
|
140
|
+
operationId: "listWhaleTradeHistory",
|
|
141
|
+
auth: "bearer",
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
method: "GET",
|
|
145
|
+
path: "/api/v1/whale-trades/{id}/counterparties/executions",
|
|
146
|
+
operationId: "listWhaleTradeCounterpartyExecutions",
|
|
147
|
+
auth: "bearer",
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
method: "GET",
|
|
151
|
+
path: "/api/v1/whale-trades/{id}/counterparties/executions/{execution_id}/makers",
|
|
152
|
+
operationId: "listWhaleTradeCounterpartyMakers",
|
|
153
|
+
auth: "bearer",
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
method: "GET",
|
|
157
|
+
path: "/api/v1/content/search",
|
|
158
|
+
operationId: "searchContent",
|
|
159
|
+
auth: "bearer",
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
method: "GET",
|
|
163
|
+
path: "/api/v1/whale-trades/{id}",
|
|
164
|
+
operationId: "getWhaleTrade",
|
|
165
|
+
auth: "bearer",
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
method: "GET",
|
|
169
|
+
path: "/api/v1/leaderboard",
|
|
170
|
+
operationId: "listLeaderboard",
|
|
171
|
+
auth: "bearer",
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
method: "GET",
|
|
175
|
+
path: "/api/v1/markets/search",
|
|
176
|
+
operationId: "searchMarkets",
|
|
177
|
+
auth: "bearer",
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
method: "GET",
|
|
181
|
+
path: "/api/v1/markets/explore",
|
|
182
|
+
operationId: "exploreMarkets",
|
|
183
|
+
auth: "bearer",
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
method: "GET",
|
|
187
|
+
path: "/api/v1/markets/smart-money-flows",
|
|
188
|
+
operationId: "listSmartMoneyFlows",
|
|
189
|
+
auth: "bearer",
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
method: "GET",
|
|
193
|
+
path: "/api/v1/markets/sharp-money-flows",
|
|
194
|
+
operationId: "listSharpMoneyFlows",
|
|
195
|
+
auth: "bearer",
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
method: "GET",
|
|
199
|
+
path: "/api/v1/platforms",
|
|
200
|
+
operationId: "getPlatforms",
|
|
201
|
+
auth: "none",
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
method: "GET",
|
|
205
|
+
path: "/api/v1/market/{condition_id}/intel",
|
|
206
|
+
operationId: "getMarketIntel",
|
|
207
|
+
auth: "bearer",
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
method: "POST",
|
|
211
|
+
path: "/api/v1/markets/intel/batch",
|
|
212
|
+
operationId: "batchGetMarketIntel",
|
|
213
|
+
auth: "bearer",
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
method: "GET",
|
|
217
|
+
path: "/api/v1/market/{condition_id}/snapshot",
|
|
218
|
+
operationId: "getMarketSnapshot",
|
|
219
|
+
auth: "bearer",
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
method: "GET",
|
|
223
|
+
path: "/api/v1/market/{condition_id}/context.md",
|
|
224
|
+
operationId: "getMarketContextMarkdown",
|
|
225
|
+
auth: "bearer",
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
method: "GET",
|
|
229
|
+
path: "/api/v1/market/{condition_id}/candles",
|
|
230
|
+
operationId: "getMarketCandles",
|
|
231
|
+
auth: "bearer",
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
method: "GET",
|
|
235
|
+
path: "/api/v1/insider-radar",
|
|
236
|
+
operationId: "listInsiderRadar",
|
|
237
|
+
auth: "bearer",
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
method: "GET",
|
|
241
|
+
path: "/api/v1/insider-radar/{id}",
|
|
242
|
+
operationId: "getInsiderRadarFlag",
|
|
243
|
+
auth: "bearer",
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
method: "GET",
|
|
247
|
+
path: "/api/v1/events/feed/since",
|
|
248
|
+
operationId: "getEventReplaySince",
|
|
249
|
+
auth: "bearer",
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
method: "GET",
|
|
253
|
+
path: "/api/v1/stream",
|
|
254
|
+
operationId: "getStream",
|
|
255
|
+
auth: "bearer",
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
method: "GET",
|
|
259
|
+
path: "/api/v1/webhooks",
|
|
260
|
+
operationId: "listWebhooks",
|
|
261
|
+
auth: "bearer",
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
method: "POST",
|
|
265
|
+
path: "/api/v1/webhooks",
|
|
266
|
+
operationId: "createWebhook",
|
|
267
|
+
auth: "bearer",
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
method: "GET",
|
|
271
|
+
path: "/api/v1/webhooks/{id}",
|
|
272
|
+
operationId: "getWebhook",
|
|
273
|
+
auth: "bearer",
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
method: "PATCH",
|
|
277
|
+
path: "/api/v1/webhooks/{id}",
|
|
278
|
+
operationId: "updateWebhook",
|
|
279
|
+
auth: "bearer",
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
method: "DELETE",
|
|
283
|
+
path: "/api/v1/webhooks/{id}",
|
|
284
|
+
operationId: "deleteWebhook",
|
|
285
|
+
auth: "bearer",
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
method: "POST",
|
|
289
|
+
path: "/api/v1/webhooks/{id}/verify",
|
|
290
|
+
operationId: "verifyWebhook",
|
|
291
|
+
auth: "bearer",
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
method: "POST",
|
|
295
|
+
path: "/api/v1/webhooks/{id}/rotate-secret",
|
|
296
|
+
operationId: "rotateWebhookSecret",
|
|
297
|
+
auth: "bearer",
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
method: "GET",
|
|
301
|
+
path: "/api/v1/webhooks/events",
|
|
302
|
+
operationId: "listWebhookEvents",
|
|
303
|
+
auth: "bearer",
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
method: "GET",
|
|
307
|
+
path: "/api/v1/webhooks/{id}/deliveries",
|
|
308
|
+
operationId: "listWebhookDeliveries",
|
|
309
|
+
auth: "bearer",
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
method: "POST",
|
|
313
|
+
path: "/api/v1/webhooks/{id}/deliveries/{delivery_id}/redeliver",
|
|
314
|
+
operationId: "redeliverWebhookDelivery",
|
|
315
|
+
auth: "bearer",
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
method: "GET",
|
|
319
|
+
path: "/api/v1/health",
|
|
320
|
+
operationId: "getHealth",
|
|
321
|
+
auth: "none",
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
method: "POST",
|
|
325
|
+
path: "/api/v1/mcp",
|
|
326
|
+
operationId: "createMcpJsonRpcResponse",
|
|
327
|
+
auth: "bearer",
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
method: "GET",
|
|
331
|
+
path: "/api/v1/mcp",
|
|
332
|
+
operationId: "openMcpEventStream",
|
|
333
|
+
auth: "bearer",
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
method: "GET",
|
|
337
|
+
path: "/api/v1/reports",
|
|
338
|
+
operationId: "getReports",
|
|
339
|
+
auth: "bearer",
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
method: "GET",
|
|
343
|
+
path: "/api/v1/reports/daily",
|
|
344
|
+
operationId: "getDailyReportSnapshot",
|
|
345
|
+
auth: "bearer",
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
method: "GET",
|
|
349
|
+
path: "/api/v1/reports/weekly",
|
|
350
|
+
operationId: "getWeeklyReportSnapshot",
|
|
351
|
+
auth: "bearer",
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
method: "GET",
|
|
355
|
+
path: "/api/v1/reports/monthly",
|
|
356
|
+
operationId: "getMonthlyReportSnapshot",
|
|
357
|
+
auth: "bearer",
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
method: "GET",
|
|
361
|
+
path: "/api/v1/trader/{address}/export",
|
|
362
|
+
operationId: "getTraderExportSnapshot",
|
|
363
|
+
auth: "bearer",
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
method: "GET",
|
|
367
|
+
path: "/api/v1/usage",
|
|
368
|
+
operationId: "getUsage",
|
|
369
|
+
auth: "bearer",
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
method: "GET",
|
|
373
|
+
path: "/api/v1/pick-of-the-day",
|
|
374
|
+
operationId: "getPickOfTheDay",
|
|
375
|
+
auth: "bearer",
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
method: "GET",
|
|
379
|
+
path: "/api/v1/pick-of-the-day/archive",
|
|
380
|
+
operationId: "getPickOfTheDayArchive",
|
|
381
|
+
auth: "bearer",
|
|
382
|
+
},
|
|
383
|
+
];
|
|
384
|
+
/**
|
|
385
|
+
* Trader skill grade. Source of truth:
|
|
386
|
+
* `components.schemas.Trader.properties.grade.enum` (S highest, F lowest).
|
|
387
|
+
*/
|
|
388
|
+
export const GRADES = ["S", "A", "B", "C", "D", "F"];
|
|
389
|
+
/** Default production API base used when `baseUrl` is omitted. */
|
|
390
|
+
export const DEFAULT_BASE_URL = "https://api.0xinsider.com";
|
|
391
|
+
/** Default per-request deadline; see `ApiClientOptions.timeoutMs`. */
|
|
392
|
+
export const DEFAULT_TIMEOUT_MS = 15_000;
|
|
393
|
+
/** Default retry budget; see `ApiClientOptions.maxRetries`. */
|
|
394
|
+
export const DEFAULT_MAX_RETRIES = 2;
|
|
395
|
+
/** Statuses a retry can fix: rate limited, or a gateway/availability failure. */
|
|
396
|
+
const RETRYABLE_STATUSES = new Set([429, 502, 503, 504]);
|
|
397
|
+
const RETRY_BASE_DELAY_MS = 500;
|
|
398
|
+
const RETRY_MAX_BACKOFF_MS = 8_000;
|
|
399
|
+
const RETRY_JITTER_MS = 250;
|
|
400
|
+
/** A server-requested wait longer than this is the caller's to schedule. */
|
|
401
|
+
const RETRY_AFTER_CEILING_MS = 60_000;
|
|
402
|
+
function assertRetryCount(value, source) {
|
|
403
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
404
|
+
throw new Error(`${source} must be a non-negative integer, got ${String(value)}`);
|
|
405
|
+
}
|
|
406
|
+
return value;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Milliseconds to wait before retry `attempt` (0-based), or `null` when the
|
|
410
|
+
* server asked for a wait a retry loop should not hold.
|
|
411
|
+
*/
|
|
412
|
+
function retryDelayMs(attempt, retryAfter) {
|
|
413
|
+
if (retryAfter !== null) {
|
|
414
|
+
const requested = Math.max(0, retryAfter * 1000);
|
|
415
|
+
if (requested > RETRY_AFTER_CEILING_MS)
|
|
416
|
+
return null;
|
|
417
|
+
return requested + Math.random() * RETRY_JITTER_MS;
|
|
418
|
+
}
|
|
419
|
+
const ceiling = Math.min(RETRY_MAX_BACKOFF_MS, RETRY_BASE_DELAY_MS * 2 ** attempt);
|
|
420
|
+
return ceiling / 2 + Math.random() * (ceiling / 2);
|
|
421
|
+
}
|
|
422
|
+
/** Resolve after `ms`, or reject with the signal's reason the moment it aborts. */
|
|
423
|
+
function sleepUnlessAborted(ms, signal) {
|
|
424
|
+
return new Promise((resolve, reject) => {
|
|
425
|
+
if (signal?.aborted) {
|
|
426
|
+
reject(signal.reason);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
const onAbort = () => {
|
|
430
|
+
clearTimeout(timer);
|
|
431
|
+
reject(signal?.reason);
|
|
432
|
+
};
|
|
433
|
+
const timer = setTimeout(() => {
|
|
434
|
+
signal?.removeEventListener("abort", onAbort);
|
|
435
|
+
resolve();
|
|
436
|
+
}, ms);
|
|
437
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
function isLoopbackHost(hostname) {
|
|
441
|
+
const host = hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
442
|
+
return (host === "localhost" ||
|
|
443
|
+
host === "::1" ||
|
|
444
|
+
/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host));
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* The API key travels as a bearer header on every request, so the base URL
|
|
448
|
+
* decides who receives it. Only `https:` is accepted, with `http:` allowed for
|
|
449
|
+
* a loopback host (a local backend on `localhost` or `127.0.0.1`). Anything
|
|
450
|
+
* else throws from the constructor, before any request is sent (#11115).
|
|
451
|
+
*/
|
|
452
|
+
export function assertTrustedBaseUrl(baseUrl) {
|
|
453
|
+
let url;
|
|
454
|
+
try {
|
|
455
|
+
url = new URL(baseUrl);
|
|
456
|
+
}
|
|
457
|
+
catch {
|
|
458
|
+
throw new Error(`0xinsider API baseUrl is not a valid URL: ${baseUrl}`);
|
|
459
|
+
}
|
|
460
|
+
if (url.protocol === "https:")
|
|
461
|
+
return url;
|
|
462
|
+
if (url.protocol === "http:" && isLoopbackHost(url.hostname))
|
|
463
|
+
return url;
|
|
464
|
+
throw new Error(`Refusing to send the API key to ${url.origin}: baseUrl must use https: (http: is accepted only for a loopback host such as localhost or 127.0.0.1).`);
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* One signal that aborts on the deadline OR when the caller's signal aborts.
|
|
468
|
+
* Uses native composition where available. The manual fallback removes both
|
|
469
|
+
* source listeners when either source aborts.
|
|
470
|
+
*/
|
|
471
|
+
export function composeSignals(timeoutMs, caller) {
|
|
472
|
+
return composeRequestSignal(timeoutMs, caller).signal;
|
|
473
|
+
}
|
|
474
|
+
/** Keep cancellation live through body consumption, then release its listeners. */
|
|
475
|
+
function composeRequestSignal(timeoutMs, caller) {
|
|
476
|
+
const timeout = timeoutMs === null ? undefined : AbortSignal.timeout(timeoutMs);
|
|
477
|
+
if (!timeout)
|
|
478
|
+
return { signal: caller };
|
|
479
|
+
if (!caller)
|
|
480
|
+
return { signal: timeout };
|
|
481
|
+
if (typeof AbortSignal.any === "function") {
|
|
482
|
+
return { signal: AbortSignal.any([caller, timeout]) };
|
|
483
|
+
}
|
|
484
|
+
const controller = new AbortController();
|
|
485
|
+
const dispose = () => {
|
|
486
|
+
caller.removeEventListener("abort", onCallerAbort);
|
|
487
|
+
timeout.removeEventListener("abort", onTimeoutAbort);
|
|
488
|
+
};
|
|
489
|
+
const onCallerAbort = () => {
|
|
490
|
+
dispose();
|
|
491
|
+
controller.abort(caller.reason);
|
|
492
|
+
};
|
|
493
|
+
const onTimeoutAbort = () => {
|
|
494
|
+
dispose();
|
|
495
|
+
controller.abort(timeout.reason);
|
|
496
|
+
};
|
|
497
|
+
if (caller.aborted) {
|
|
498
|
+
onCallerAbort();
|
|
499
|
+
}
|
|
500
|
+
else if (timeout.aborted) {
|
|
501
|
+
onTimeoutAbort();
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
caller.addEventListener("abort", onCallerAbort, { once: true });
|
|
505
|
+
timeout.addEventListener("abort", onTimeoutAbort, { once: true });
|
|
506
|
+
}
|
|
507
|
+
return { signal: controller.signal, dispose };
|
|
508
|
+
}
|
|
509
|
+
function isTimeoutAbort(error) {
|
|
510
|
+
return (typeof error === "object" &&
|
|
511
|
+
error !== null &&
|
|
512
|
+
"name" in error &&
|
|
513
|
+
error.name === "TimeoutError");
|
|
514
|
+
}
|
|
515
|
+
/** `GET /api/v1/leaderboard` -> parameters -> strategy (`web/public/api/v1/openapi.json`). */
|
|
516
|
+
export const LEADERBOARD_STRATEGIES = [
|
|
517
|
+
"accumulator",
|
|
518
|
+
"algo_trader",
|
|
519
|
+
"arbitrageur",
|
|
520
|
+
"directional",
|
|
521
|
+
"event_driven",
|
|
522
|
+
"market_maker",
|
|
523
|
+
"momentum",
|
|
524
|
+
"scalper",
|
|
525
|
+
"speculator",
|
|
526
|
+
"swing_trader",
|
|
527
|
+
];
|
|
528
|
+
const operationsById = new Map(API_CLIENT_OPERATIONS.map((operation) => [operation.operationId, operation]));
|
|
529
|
+
export class OxinsiderApiClient {
|
|
530
|
+
baseUrl;
|
|
531
|
+
apiKey;
|
|
532
|
+
fetchImpl;
|
|
533
|
+
timeoutMs;
|
|
534
|
+
maxRetries;
|
|
535
|
+
constructor(options = {}) {
|
|
536
|
+
const base = assertTrustedBaseUrl(options.baseUrl ?? DEFAULT_BASE_URL);
|
|
537
|
+
this.baseUrl = base.toString().replace(/\/+$/, "");
|
|
538
|
+
this.apiKey = options.apiKey;
|
|
539
|
+
this.timeoutMs =
|
|
540
|
+
options.timeoutMs === undefined ? DEFAULT_TIMEOUT_MS : options.timeoutMs;
|
|
541
|
+
this.maxRetries = assertRetryCount(options.maxRetries ?? DEFAULT_MAX_RETRIES, "maxRetries");
|
|
542
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
543
|
+
if (typeof fetchImpl !== "function") {
|
|
544
|
+
throw new Error("No fetch implementation available. Pass `fetch` in OxinsiderApiClient options or run on Node 18+ / a fetch-capable runtime.");
|
|
545
|
+
}
|
|
546
|
+
// Bind so a passed-through `globalThis.fetch` keeps its receiver.
|
|
547
|
+
this.fetchImpl = fetchImpl.bind(globalThis);
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Execute any operation by id. Throws the matching `OxinsiderApiError`
|
|
551
|
+
* subclass on a non-2xx response, returns a typed `not_modified` result for a
|
|
552
|
+
* 304, and otherwise returns the parsed `{ object, data, meta }` envelope.
|
|
553
|
+
*/
|
|
554
|
+
async call(operationId, options = {}) {
|
|
555
|
+
const operation = operationsById.get(operationId);
|
|
556
|
+
if (!operation) {
|
|
557
|
+
throw new Error(`Unknown 0xinsider API operation: ${operationId}`);
|
|
558
|
+
}
|
|
559
|
+
if (operation.auth === "bearer" && !this.apiKey) {
|
|
560
|
+
throw new Error(`${operationId} requires an API key (oxi_sk_*)`);
|
|
561
|
+
}
|
|
562
|
+
const timeoutMs = options.timeoutMs === undefined ? this.timeoutMs : options.timeoutMs;
|
|
563
|
+
const maxRetries = options.maxRetries === undefined
|
|
564
|
+
? this.maxRetries
|
|
565
|
+
: assertRetryCount(options.maxRetries, "maxRetries");
|
|
566
|
+
const url = this.buildUrl(operation, options);
|
|
567
|
+
const headers = this.buildHeaders(operation, options);
|
|
568
|
+
const body = options.body === undefined ? undefined : JSON.stringify(options.body);
|
|
569
|
+
// Replaying a write without a key could repeat it (a second webhook
|
|
570
|
+
// endpoint, say), so only a GET or a keyed write is retried (#14281).
|
|
571
|
+
const retryable = operation.method === "GET" || headers.has("idempotency-key");
|
|
572
|
+
// One deadline for every attempt: a retry that cannot finish before it is
|
|
573
|
+
// not started, so the caller sees the real failure, not a timeout.
|
|
574
|
+
const deadlineAt = timeoutMs === null ? null : Date.now() + timeoutMs;
|
|
575
|
+
const retryDelayWithinBudget = (attempt, retryAfter) => {
|
|
576
|
+
if (!retryable || attempt >= maxRetries)
|
|
577
|
+
return null;
|
|
578
|
+
const delay = retryDelayMs(attempt, retryAfter);
|
|
579
|
+
if (delay === null)
|
|
580
|
+
return null;
|
|
581
|
+
if (deadlineAt !== null && Date.now() + delay >= deadlineAt)
|
|
582
|
+
return null;
|
|
583
|
+
return delay;
|
|
584
|
+
};
|
|
585
|
+
const requestSignal = composeRequestSignal(timeoutMs, options.signal);
|
|
586
|
+
try {
|
|
587
|
+
let response;
|
|
588
|
+
for (let attempt = 0;; attempt += 1) {
|
|
589
|
+
try {
|
|
590
|
+
response = await this.fetchImpl(url, {
|
|
591
|
+
method: operation.method,
|
|
592
|
+
headers,
|
|
593
|
+
body,
|
|
594
|
+
signal: requestSignal.signal,
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
catch (error) {
|
|
598
|
+
// A timeout or caller abort is final; the handler below maps it.
|
|
599
|
+
if (requestSignal.signal?.aborted || isTimeoutAbort(error))
|
|
600
|
+
throw error;
|
|
601
|
+
const delay = retryDelayWithinBudget(attempt, null);
|
|
602
|
+
if (delay === null)
|
|
603
|
+
throw error;
|
|
604
|
+
await sleepUnlessAborted(delay, requestSignal.signal);
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
if (response.ok || response.status === 304)
|
|
608
|
+
break;
|
|
609
|
+
const errorBody = await parseJson(response);
|
|
610
|
+
const retryAfter = retryAfterSeconds(response);
|
|
611
|
+
const delay = RETRYABLE_STATUSES.has(response.status)
|
|
612
|
+
? retryDelayWithinBudget(attempt, retryAfter)
|
|
613
|
+
: null;
|
|
614
|
+
if (delay === null) {
|
|
615
|
+
throw errorFromResponse(response.status, errorBody, retryAfter);
|
|
616
|
+
}
|
|
617
|
+
await sleepUnlessAborted(delay, requestSignal.signal);
|
|
618
|
+
}
|
|
619
|
+
if (response.status === 304) {
|
|
620
|
+
return notModifiedResponse(response);
|
|
621
|
+
}
|
|
622
|
+
const envelope = await parseJson(response);
|
|
623
|
+
if (isApiEnvelope(envelope)) {
|
|
624
|
+
const etag = response.headers.get("etag");
|
|
625
|
+
// Only ADD to a meta the server actually sent. The contract makes
|
|
626
|
+
// `meta` required on every envelope, so synthesizing one from `?? {}`
|
|
627
|
+
// would hand a caller a `ResponseMeta` missing its required fields --
|
|
628
|
+
// which is what the hand-written type hid before #14278.
|
|
629
|
+
if (etag && envelope.meta) {
|
|
630
|
+
envelope.meta = { ...envelope.meta, etag };
|
|
631
|
+
}
|
|
632
|
+
return envelope;
|
|
633
|
+
}
|
|
634
|
+
throw new OxinsiderApiError(response.status, envelope);
|
|
635
|
+
}
|
|
636
|
+
catch (error) {
|
|
637
|
+
if (timeoutMs !== null &&
|
|
638
|
+
(isTimeoutAbort(error) ||
|
|
639
|
+
(requestSignal.signal?.aborted &&
|
|
640
|
+
isTimeoutAbort(requestSignal.signal.reason)))) {
|
|
641
|
+
throw new RequestTimeoutError(operationId, timeoutMs);
|
|
642
|
+
}
|
|
643
|
+
if (requestSignal.signal?.aborted) {
|
|
644
|
+
throw requestSignal.signal.reason;
|
|
645
|
+
}
|
|
646
|
+
throw error;
|
|
647
|
+
}
|
|
648
|
+
finally {
|
|
649
|
+
requestSignal.dispose?.();
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Execute a Stripe-style list operation and return the list envelope.
|
|
654
|
+
* Use `paginate()` (in `pagination.ts`) to auto-follow `next_cursor`.
|
|
655
|
+
*/
|
|
656
|
+
async list(operationId, options = {}) {
|
|
657
|
+
const result = await this.call(operationId, options);
|
|
658
|
+
if (isNotModified(result)) {
|
|
659
|
+
throw new OxinsiderApiError(304, "list() received a 304 not_modified; use call() if you send If-None-Match on a list endpoint");
|
|
660
|
+
}
|
|
661
|
+
if (!isListEnvelope(result)) {
|
|
662
|
+
throw new OxinsiderApiError(200, `Operation ${operationId} did not return a list envelope`);
|
|
663
|
+
}
|
|
664
|
+
return result;
|
|
665
|
+
}
|
|
666
|
+
// --- Typed convenience methods (key surfaces) ---
|
|
667
|
+
getTrader(address, options = {}) {
|
|
668
|
+
return this.call("getTrader", { ...options, path: { address } });
|
|
669
|
+
}
|
|
670
|
+
getTraderPnl(address, options = {}) {
|
|
671
|
+
return this.call("getTraderPnl", { ...options, path: { address } });
|
|
672
|
+
}
|
|
673
|
+
batchGetTraders(identifiers, options = {}) {
|
|
674
|
+
return this.call("batchGetTraders", {
|
|
675
|
+
...options,
|
|
676
|
+
body: { identifiers },
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
listLeaderboard(params = {}) {
|
|
680
|
+
return this.list("listLeaderboard", { query: params });
|
|
681
|
+
}
|
|
682
|
+
listTrendingWallets(params = {}) {
|
|
683
|
+
return this.list("listTrendingWallets", { query: params });
|
|
684
|
+
}
|
|
685
|
+
listWhaleTrades(params = {}) {
|
|
686
|
+
return this.list("listWhaleTrades", { query: params });
|
|
687
|
+
}
|
|
688
|
+
listWhaleTradeHistory(params = {}) {
|
|
689
|
+
return this.list("listWhaleTradeHistory", { query: params });
|
|
690
|
+
}
|
|
691
|
+
listPositions(params = {}) {
|
|
692
|
+
return this.list("listPositions", { query: params });
|
|
693
|
+
}
|
|
694
|
+
listLargePositions(params = {}) {
|
|
695
|
+
return this.list("listLargePositions", { query: params });
|
|
696
|
+
}
|
|
697
|
+
listSmartMoneyFlows(params = {}) {
|
|
698
|
+
return this.list("listSmartMoneyFlows", { query: params });
|
|
699
|
+
}
|
|
700
|
+
/** Canonical alias of {@link listSmartMoneyFlows} (epic #6912). */
|
|
701
|
+
listSharpMoneyFlows(params = {}) {
|
|
702
|
+
return this.list("listSharpMoneyFlows", { query: params });
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* List the funded-primary sports signals with additive, shadow-only category
|
|
706
|
+
* evidence. `category_skill` never changes membership, ordering, or sizing.
|
|
707
|
+
*/
|
|
708
|
+
listSportsEdgeSignals(params = {}) {
|
|
709
|
+
return this.list("listSportsEdgeSignals", {
|
|
710
|
+
query: params,
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Read an explicitly observation-only sports cohort and its accountable
|
|
715
|
+
* per-sport funnel. This surface is isolated from the funded signals route.
|
|
716
|
+
* Healthy wider-holder and emerging-pile snapshots may be served for about
|
|
717
|
+
* 180 seconds; in-play cached snapshots are capped at about 30 seconds and
|
|
718
|
+
* stale provider live-board evidence fails closed. emerging-pile is an
|
|
719
|
+
* additive wider-holder projection, not an arrival-history or independent
|
|
720
|
+
* denominator view.
|
|
721
|
+
*/
|
|
722
|
+
async listSportsEdgeObservations(params, options = {}) {
|
|
723
|
+
const response = await this.list("listSportsEdgeObservations", { ...options, query: params });
|
|
724
|
+
return sportsEdgeObservationsResponse(response);
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Typed conditional-read variant of {@link listSportsEdgeObservations}.
|
|
728
|
+
* Returns the full observation response on 200 or a typed `not_modified`
|
|
729
|
+
* envelope on 304 instead of routing the latter through generic `call()`.
|
|
730
|
+
* The endpoint emits a weak semantic ETag over the stable response payload;
|
|
731
|
+
* request-specific `meta` is excluded. For emerging-pile, the opaque
|
|
732
|
+
* projection cutoff inside `next_cursor` is excluded while its stable page
|
|
733
|
+
* position remains covered.
|
|
734
|
+
*/
|
|
735
|
+
async listSportsEdgeObservationsConditional(params, options = {}) {
|
|
736
|
+
const response = await this.call("listSportsEdgeObservations", { ...options, query: params });
|
|
737
|
+
if (isNotModified(response)) {
|
|
738
|
+
return response;
|
|
739
|
+
}
|
|
740
|
+
if (!isListEnvelope(response)) {
|
|
741
|
+
throw new OxinsiderApiError(200, "listSportsEdgeObservations did not return a list envelope");
|
|
742
|
+
}
|
|
743
|
+
return sportsEdgeObservationsResponse(response);
|
|
744
|
+
}
|
|
745
|
+
searchMarkets(q, options = {}) {
|
|
746
|
+
return this.call("searchMarkets", {
|
|
747
|
+
...options,
|
|
748
|
+
query: { q, ...options.query },
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
exploreMarkets(params = {}) {
|
|
752
|
+
return this.list("exploreMarkets", { query: params });
|
|
753
|
+
}
|
|
754
|
+
getMarketIntel(conditionId, options = {}) {
|
|
755
|
+
return this.call("getMarketIntel", {
|
|
756
|
+
...options,
|
|
757
|
+
path: { condition_id: conditionId },
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
getMarketSnapshot(conditionId, options = {}) {
|
|
761
|
+
return this.call("getMarketSnapshot", {
|
|
762
|
+
...options,
|
|
763
|
+
path: { condition_id: conditionId },
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Fetch bucketed OHLC candles for a market's outcome tokens.
|
|
768
|
+
* Resolution defaults to the server default; pass `query.resolution` as
|
|
769
|
+
* `"1d"` or `"1w"` to override.
|
|
770
|
+
*/
|
|
771
|
+
getMarketCandles(conditionId, options = {}) {
|
|
772
|
+
return this.call("getMarketCandles", {
|
|
773
|
+
...options,
|
|
774
|
+
path: { condition_id: conditionId },
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
getInsiderRadarFlag(id, options = {}) {
|
|
778
|
+
return this.call("getInsiderRadarFlag", { ...options, path: { id } });
|
|
779
|
+
}
|
|
780
|
+
listInsiderRadar(params = {}) {
|
|
781
|
+
return this.list("listInsiderRadar", { query: params });
|
|
782
|
+
}
|
|
783
|
+
// --- Webhooks ---
|
|
784
|
+
listWebhooks(options = {}) {
|
|
785
|
+
return this.call("listWebhooks", options);
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Fetch the self-describing webhook event catalog (each entry's `id`,
|
|
789
|
+
* description, payload shape, and active/dormant status).
|
|
790
|
+
*/
|
|
791
|
+
listWebhookEvents(options = {}) {
|
|
792
|
+
return this.call("listWebhookEvents", options);
|
|
793
|
+
}
|
|
794
|
+
/** List delivery attempts for one webhook endpoint (Stripe-style list). */
|
|
795
|
+
listWebhookDeliveries(webhookId, params = {}) {
|
|
796
|
+
return this.list("listWebhookDeliveries", {
|
|
797
|
+
path: { id: webhookId },
|
|
798
|
+
query: params,
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Requeue one `dead_letter` delivery with a fresh attempt budget and return
|
|
803
|
+
* the delivery row. Re-enabling a disabled endpoint resends nothing, so this
|
|
804
|
+
* is how its dead-lettered deliveries are recovered. The API answers 409 when
|
|
805
|
+
* the delivery is already delivered or still queued, or when the endpoint is
|
|
806
|
+
* disabled, unverified, or no longer subscribed to the delivery's event type;
|
|
807
|
+
* the error message names the fix. Pass `idempotencyKey` to make a retry safe.
|
|
808
|
+
*/
|
|
809
|
+
redeliverWebhookDelivery(webhookId, deliveryId, options = {}) {
|
|
810
|
+
return this.call("redeliverWebhookDelivery", {
|
|
811
|
+
...options,
|
|
812
|
+
path: { id: webhookId, delivery_id: deliveryId },
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
// --- System ---
|
|
816
|
+
getHealth(options = {}) {
|
|
817
|
+
return this.call("getHealth", options);
|
|
818
|
+
}
|
|
819
|
+
getApiDiscovery(options = {}) {
|
|
820
|
+
return this.call("getApiDiscovery", options);
|
|
821
|
+
}
|
|
822
|
+
getAccountIdentity(options = {}) {
|
|
823
|
+
return this.call("getAccountIdentity", options);
|
|
824
|
+
}
|
|
825
|
+
getUsage(options = {}) {
|
|
826
|
+
return this.call("getUsage", options);
|
|
827
|
+
}
|
|
828
|
+
/** Fetch today's editorial Pick of the Day (single-object envelope). */
|
|
829
|
+
getPickOfTheDay(options = {}) {
|
|
830
|
+
return this.call("getPickOfTheDay", options);
|
|
831
|
+
}
|
|
832
|
+
/** Fetch the Pick of the Day archive with hit-rate (single-object envelope). */
|
|
833
|
+
getPickOfTheDayArchive(options = {}) {
|
|
834
|
+
return this.call("getPickOfTheDayArchive", options);
|
|
835
|
+
}
|
|
836
|
+
// --- Internals ---
|
|
837
|
+
/** Resolve the base URL (used by the SSE stream consumer). */
|
|
838
|
+
getBaseUrl() {
|
|
839
|
+
return this.baseUrl;
|
|
840
|
+
}
|
|
841
|
+
/** The configured API key, if any (used by the SSE stream consumer). */
|
|
842
|
+
getApiKey() {
|
|
843
|
+
return this.apiKey;
|
|
844
|
+
}
|
|
845
|
+
/** The resolved fetch implementation (used by the SSE stream consumer). */
|
|
846
|
+
getFetch() {
|
|
847
|
+
return this.fetchImpl;
|
|
848
|
+
}
|
|
849
|
+
buildUrl(operation, options) {
|
|
850
|
+
const url = new URL(interpolatePath(operation.path, options.path ?? {}), `${this.baseUrl}/`);
|
|
851
|
+
for (const [key, value] of Object.entries(options.query ?? {})) {
|
|
852
|
+
if (value === null || value === undefined) {
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
if (Array.isArray(value)) {
|
|
856
|
+
for (const item of value) {
|
|
857
|
+
url.searchParams.append(key, String(item));
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
else {
|
|
861
|
+
url.searchParams.set(key, String(value));
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
return url.toString();
|
|
865
|
+
}
|
|
866
|
+
buildHeaders(operation, options) {
|
|
867
|
+
const headers = new Headers(options.headers);
|
|
868
|
+
headers.set("accept", "application/json");
|
|
869
|
+
if (options.body !== undefined && !headers.has("content-type")) {
|
|
870
|
+
headers.set("content-type", "application/json");
|
|
871
|
+
}
|
|
872
|
+
if (operation.auth === "bearer" && this.apiKey) {
|
|
873
|
+
headers.set("authorization", `Bearer ${this.apiKey}`);
|
|
874
|
+
}
|
|
875
|
+
if (options.idempotencyKey !== undefined) {
|
|
876
|
+
headers.set("idempotency-key", options.idempotencyKey);
|
|
877
|
+
}
|
|
878
|
+
return headers;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
export function isApiNotModifiedResponse(response) {
|
|
882
|
+
return response.object === "not_modified";
|
|
883
|
+
}
|
|
884
|
+
function isNotModified(response) {
|
|
885
|
+
return response.object === "not_modified";
|
|
886
|
+
}
|
|
887
|
+
export function interpolatePath(path, params) {
|
|
888
|
+
return path.replace(/\{([^}]+)\}/g, (_, name) => {
|
|
889
|
+
const value = params[name];
|
|
890
|
+
if (value === undefined) {
|
|
891
|
+
throw new Error(`Missing path parameter: ${name}`);
|
|
892
|
+
}
|
|
893
|
+
return encodeURIComponent(String(value));
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
async function parseJson(response) {
|
|
897
|
+
const text = await response.text();
|
|
898
|
+
if (text === "") {
|
|
899
|
+
return null;
|
|
900
|
+
}
|
|
901
|
+
try {
|
|
902
|
+
return JSON.parse(text);
|
|
903
|
+
}
|
|
904
|
+
catch {
|
|
905
|
+
return text;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
function retryAfterSeconds(response) {
|
|
909
|
+
const header = response.headers.get("retry-after");
|
|
910
|
+
if (!header)
|
|
911
|
+
return null;
|
|
912
|
+
const seconds = Number(header);
|
|
913
|
+
return Number.isFinite(seconds) ? seconds : null;
|
|
914
|
+
}
|
|
915
|
+
function notModifiedResponse(response) {
|
|
916
|
+
const meta = { status: 304 };
|
|
917
|
+
const etag = response.headers.get("etag");
|
|
918
|
+
if (etag) {
|
|
919
|
+
meta.etag = etag;
|
|
920
|
+
}
|
|
921
|
+
const requestId = response.headers.get("x-request-id");
|
|
922
|
+
if (requestId) {
|
|
923
|
+
meta.request_id = requestId;
|
|
924
|
+
}
|
|
925
|
+
return { object: "not_modified", data: null, meta };
|
|
926
|
+
}
|
|
927
|
+
function isApiEnvelope(body) {
|
|
928
|
+
return (typeof body === "object" &&
|
|
929
|
+
body !== null &&
|
|
930
|
+
"object" in body &&
|
|
931
|
+
"data" in body);
|
|
932
|
+
}
|
|
933
|
+
export function isListEnvelope(body) {
|
|
934
|
+
return (typeof body === "object" &&
|
|
935
|
+
body !== null &&
|
|
936
|
+
"object" in body &&
|
|
937
|
+
body.object === "list" &&
|
|
938
|
+
"data" in body &&
|
|
939
|
+
Array.isArray(body.data) &&
|
|
940
|
+
"has_more" in body);
|
|
941
|
+
}
|
|
942
|
+
function sportsEdgeObservationsResponse(response) {
|
|
943
|
+
const candidate = response;
|
|
944
|
+
const funnel = candidate.funnel;
|
|
945
|
+
const meta = candidate.meta;
|
|
946
|
+
if (candidate.object !== "list" ||
|
|
947
|
+
!Array.isArray(candidate.data) ||
|
|
948
|
+
typeof candidate.has_more !== "boolean" ||
|
|
949
|
+
!(candidate.next_cursor === null ||
|
|
950
|
+
typeof candidate.next_cursor === "string") ||
|
|
951
|
+
typeof candidate.snapshot_as_of !== "string" ||
|
|
952
|
+
typeof candidate.degraded !== "boolean" ||
|
|
953
|
+
typeof funnel !== "object" ||
|
|
954
|
+
funnel === null ||
|
|
955
|
+
Array.isArray(funnel) ||
|
|
956
|
+
!Array.isArray(funnel.sports) ||
|
|
957
|
+
typeof meta !== "object" ||
|
|
958
|
+
meta === null ||
|
|
959
|
+
Array.isArray(meta) ||
|
|
960
|
+
typeof meta.request_id !== "string" ||
|
|
961
|
+
typeof meta.cached !== "boolean" ||
|
|
962
|
+
typeof meta.cost !== "number" ||
|
|
963
|
+
!Number.isInteger(meta.cost)) {
|
|
964
|
+
throw new OxinsiderApiError(200, {
|
|
965
|
+
object: "error",
|
|
966
|
+
error: {
|
|
967
|
+
code: "invalid_response",
|
|
968
|
+
message: "listSportsEdgeObservations returned an invalid required response envelope",
|
|
969
|
+
},
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
return response;
|
|
973
|
+
}
|
|
974
|
+
//# sourceMappingURL=client.js.map
|