dino-markets 0.1.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/LICENSE +21 -0
- package/README.md +120 -0
- package/dist/index.cjs +312 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +298 -0
- package/dist/index.d.ts +298 -0
- package/dist/index.js +278 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nusantara Ventures LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# dino-markets
|
|
2
|
+
|
|
3
|
+
Official TypeScript SDK for [dino.markets](https://dino.markets), a matched cross-venue feed for sports prediction markets on Kalshi and Polymarket. It also surfaces confirmed arbitrage signals when both venues price the same outcome apart.
|
|
4
|
+
|
|
5
|
+
Zero runtime dependencies. Built on the native `fetch` API, so it runs in Node 18+, Deno, Bun, and the browser without a bundler polyfill.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install dino-markets
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Auth
|
|
14
|
+
|
|
15
|
+
Get a free API key from the dashboard at https://dino.markets. Sign in and open the keys page, then mint a key with the `sk_live_` prefix.
|
|
16
|
+
|
|
17
|
+
Pass the key directly, or set it as an environment variable and let the client pick it up:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
export DINO_API_KEY=sk_live_...
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { Dino } from "dino-markets";
|
|
25
|
+
|
|
26
|
+
const dino = new Dino(); // reads DINO_API_KEY
|
|
27
|
+
// or: new Dino({ apiKey: "sk_live_..." });
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quickstart
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { Dino } from "dino-markets";
|
|
34
|
+
|
|
35
|
+
const dino = new Dino({ apiKey: "sk_live_..." });
|
|
36
|
+
|
|
37
|
+
const { markets } = await dino.markets({ sport: "baseball" });
|
|
38
|
+
console.log(markets.length, "markets");
|
|
39
|
+
|
|
40
|
+
const arbs = await dino.findArbitrage({ sport: "soccer" });
|
|
41
|
+
for (const m of arbs.markets) {
|
|
42
|
+
console.log(m.title, m.potential_arb_pct);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const market = await dino.market(markets[0].id);
|
|
46
|
+
const history = await dino.history(markets[0].id);
|
|
47
|
+
const { sports } = await dino.leagues();
|
|
48
|
+
|
|
49
|
+
await dino.reportBadArb({ opp_id: market.id, reason: "stale price on one leg" });
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Every response is typed against the same market object the REST API and the WebSocket stream both serve, including the family fields that appear only on spread/total/team-total markets and the ladder fields specific to weather markets.
|
|
53
|
+
|
|
54
|
+
## Streaming
|
|
55
|
+
|
|
56
|
+
Basic and Pro plans get a real-time WebSocket feed over [Centrifugo](https://centrifugal.dev). Streaming needs the optional `centrifuge` peer dependency:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install centrifuge
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { Dino, watch } from "dino-markets";
|
|
64
|
+
|
|
65
|
+
const dino = new Dino({ apiKey: "sk_live_..." });
|
|
66
|
+
|
|
67
|
+
const handle = await watch(dino, {
|
|
68
|
+
onFrame: (frame, channel) => console.log(channel, frame),
|
|
69
|
+
onError: (err) => console.error(err),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// later
|
|
73
|
+
handle.close();
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`watch` mints a connect ticket via `streamToken()` and opens the socket, forwarding every publication on your plan's channels. A Free key's `streamToken()` call throws a `PlanError` rather than a silent empty stream.
|
|
77
|
+
|
|
78
|
+
## Error handling
|
|
79
|
+
|
|
80
|
+
Every non-2xx response is thrown as a typed error, all extending `DinoError`:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { AuthenticationError, PlanError, RateLimitError, ServerError } from "dino-markets";
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
await dino.markets();
|
|
87
|
+
} catch (err) {
|
|
88
|
+
if (err instanceof RateLimitError) {
|
|
89
|
+
console.log("retry after", err.retryAfter, "seconds");
|
|
90
|
+
} else if (err instanceof AuthenticationError) {
|
|
91
|
+
console.log("check your API key");
|
|
92
|
+
} else if (err instanceof PlanError) {
|
|
93
|
+
console.log("upgrade required:", err.body);
|
|
94
|
+
} else if (err instanceof ServerError) {
|
|
95
|
+
console.log("dino.markets had a server error, try again shortly");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The client retries a 429 or 5xx automatically (`maxRetries`, default 2), honoring the server's `Retry-After` when present and backing off otherwise. A 4xx other than 429 is never retried.
|
|
101
|
+
|
|
102
|
+
## Rate limits
|
|
103
|
+
|
|
104
|
+
REST requests are capped per API key, per 60-second window:
|
|
105
|
+
|
|
106
|
+
| Plan | Requests / minute |
|
|
107
|
+
|-------|--------------------|
|
|
108
|
+
| Free | 60 |
|
|
109
|
+
| Basic | 300 |
|
|
110
|
+
| Pro | 1200 |
|
|
111
|
+
|
|
112
|
+
Streaming is available on Basic and Pro only.
|
|
113
|
+
|
|
114
|
+
## Disclaimer
|
|
115
|
+
|
|
116
|
+
Informational data. Not investment advice. You trade on your own venue accounts at your own risk.
|
|
117
|
+
|
|
118
|
+
## Support
|
|
119
|
+
|
|
120
|
+
Reach us at support@dino.markets with questions, or report a bad signal directly from your code with `dino.reportBadArb(...)`.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AuthenticationError: () => AuthenticationError,
|
|
24
|
+
Dino: () => Dino,
|
|
25
|
+
DinoError: () => DinoError,
|
|
26
|
+
PlanError: () => PlanError,
|
|
27
|
+
RateLimitError: () => RateLimitError,
|
|
28
|
+
ServerError: () => ServerError,
|
|
29
|
+
mapError: () => mapError,
|
|
30
|
+
watch: () => watch
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(index_exports);
|
|
33
|
+
|
|
34
|
+
// src/errors.ts
|
|
35
|
+
var DinoError = class extends Error {
|
|
36
|
+
/** HTTP status code of the failed response. */
|
|
37
|
+
status;
|
|
38
|
+
/** Parsed JSON response body, or null if unavailable/unparseable. */
|
|
39
|
+
body;
|
|
40
|
+
constructor(message, status, body = null) {
|
|
41
|
+
super(message);
|
|
42
|
+
this.name = "DinoError";
|
|
43
|
+
this.status = status;
|
|
44
|
+
this.body = body;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
var AuthenticationError = class extends DinoError {
|
|
48
|
+
constructor(message, status, body = null) {
|
|
49
|
+
super(message, status, body);
|
|
50
|
+
this.name = "AuthenticationError";
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var PlanError = class extends DinoError {
|
|
54
|
+
constructor(message, status, body = null) {
|
|
55
|
+
super(message, status, body);
|
|
56
|
+
this.name = "PlanError";
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
var RateLimitError = class extends DinoError {
|
|
60
|
+
retryAfter;
|
|
61
|
+
constructor(message, status, body = null, retryAfter) {
|
|
62
|
+
super(message, status, body);
|
|
63
|
+
this.name = "RateLimitError";
|
|
64
|
+
this.retryAfter = retryAfter;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
var ServerError = class extends DinoError {
|
|
68
|
+
constructor(message, status, body = null) {
|
|
69
|
+
super(message, status, body);
|
|
70
|
+
this.name = "ServerError";
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
function extractRetryAfter(headers, body) {
|
|
74
|
+
const header = headers?.get?.("retry-after");
|
|
75
|
+
if (header) {
|
|
76
|
+
const n = Number(header);
|
|
77
|
+
if (Number.isFinite(n)) return n;
|
|
78
|
+
}
|
|
79
|
+
if (body && typeof body === "object" && "retry_after" in body) {
|
|
80
|
+
const n = Number(body.retry_after);
|
|
81
|
+
if (Number.isFinite(n)) return n;
|
|
82
|
+
}
|
|
83
|
+
return void 0;
|
|
84
|
+
}
|
|
85
|
+
function errorMessage(status, body, fallback) {
|
|
86
|
+
if (body && typeof body === "object" && "error" in body) {
|
|
87
|
+
const msg = body.error;
|
|
88
|
+
if (typeof msg === "string" && msg) return msg;
|
|
89
|
+
}
|
|
90
|
+
return fallback;
|
|
91
|
+
}
|
|
92
|
+
function mapError(status, body, headers) {
|
|
93
|
+
if (status === 401) {
|
|
94
|
+
return new AuthenticationError(errorMessage(status, body, "authentication failed"), status, body);
|
|
95
|
+
}
|
|
96
|
+
if (status === 402) {
|
|
97
|
+
return new PlanError(errorMessage(status, body, "plan does not allow this request"), status, body);
|
|
98
|
+
}
|
|
99
|
+
if (status === 429) {
|
|
100
|
+
const retryAfter = extractRetryAfter(headers, body);
|
|
101
|
+
return new RateLimitError(errorMessage(status, body, "rate limit exceeded"), status, body, retryAfter);
|
|
102
|
+
}
|
|
103
|
+
if (status >= 500) {
|
|
104
|
+
return new ServerError(errorMessage(status, body, "server error"), status, body);
|
|
105
|
+
}
|
|
106
|
+
return new DinoError(errorMessage(status, body, `request failed with status ${status}`), status, body);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/client.ts
|
|
110
|
+
var SDK_VERSION = "0.1.0";
|
|
111
|
+
var DEFAULT_BASE_URL = "https://api.dino.markets";
|
|
112
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
113
|
+
var DEFAULT_MAX_RETRIES = 2;
|
|
114
|
+
var RETRY_BACKOFF_MS = [500, 1e3, 2e3];
|
|
115
|
+
function resolveApiKey(apiKey) {
|
|
116
|
+
if (apiKey) return apiKey;
|
|
117
|
+
if (typeof process !== "undefined" && process.env && process.env.DINO_API_KEY) {
|
|
118
|
+
return process.env.DINO_API_KEY;
|
|
119
|
+
}
|
|
120
|
+
throw new Error(
|
|
121
|
+
"dino-markets: no API key provided. Pass { apiKey } or set the DINO_API_KEY environment variable. Get a free key at https://dino.markets"
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
function validateLimit(limit) {
|
|
125
|
+
if (limit === void 0) return;
|
|
126
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 1e3) {
|
|
127
|
+
throw new Error(`dino-markets: limit must be an integer between 1 and 1000, got ${limit}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function buildQuery(params) {
|
|
131
|
+
const usp = new URLSearchParams();
|
|
132
|
+
for (const [key, value] of Object.entries(params)) {
|
|
133
|
+
if (value === void 0 || value === null) continue;
|
|
134
|
+
usp.set(key, String(value));
|
|
135
|
+
}
|
|
136
|
+
const qs = usp.toString();
|
|
137
|
+
return qs ? `?${qs}` : "";
|
|
138
|
+
}
|
|
139
|
+
function sleep(ms) {
|
|
140
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
141
|
+
}
|
|
142
|
+
var Dino = class {
|
|
143
|
+
apiKey;
|
|
144
|
+
baseUrl;
|
|
145
|
+
timeoutMs;
|
|
146
|
+
maxRetries;
|
|
147
|
+
fetchImpl;
|
|
148
|
+
constructor(opts = {}) {
|
|
149
|
+
this.apiKey = resolveApiKey(opts.apiKey);
|
|
150
|
+
this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
151
|
+
this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
152
|
+
this.maxRetries = opts.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
153
|
+
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
154
|
+
}
|
|
155
|
+
/** Matched cross-venue catalog. See MarketsOptions for filters. */
|
|
156
|
+
async markets(opts = {}) {
|
|
157
|
+
validateLimit(opts.limit);
|
|
158
|
+
const qs = buildQuery({
|
|
159
|
+
sport: opts.sport,
|
|
160
|
+
league: opts.league,
|
|
161
|
+
status: opts.status,
|
|
162
|
+
signal: opts.signal,
|
|
163
|
+
sort: opts.sort,
|
|
164
|
+
include: opts.include,
|
|
165
|
+
market_type: opts.market_type,
|
|
166
|
+
game_id: opts.game_id,
|
|
167
|
+
limit: opts.limit
|
|
168
|
+
});
|
|
169
|
+
return this.request("GET", `/v2/markets${qs}`);
|
|
170
|
+
}
|
|
171
|
+
/** One canonical market object. Accepts a "dino_<uuid>" id or a bare uuid. */
|
|
172
|
+
async market(marketId) {
|
|
173
|
+
if (!marketId) throw new Error("dino-markets: market(marketId) requires a non-empty marketId");
|
|
174
|
+
return this.request("GET", `/v2/pairs/${encodeURIComponent(marketId)}`);
|
|
175
|
+
}
|
|
176
|
+
/** Per-outcome Kalshi/Polymarket price history for one market. */
|
|
177
|
+
async history(marketId) {
|
|
178
|
+
if (!marketId) throw new Error("dino-markets: history(marketId) requires a non-empty marketId");
|
|
179
|
+
return this.request("GET", `/v2/pairs/${encodeURIComponent(marketId)}/history`);
|
|
180
|
+
}
|
|
181
|
+
/** Sports and leagues currently in season, with lifecycle counts. */
|
|
182
|
+
async leagues() {
|
|
183
|
+
return this.request("GET", "/v2/leagues");
|
|
184
|
+
}
|
|
185
|
+
/** Sugar for markets({ ...opts, signal: "arb" }). */
|
|
186
|
+
async findArbitrage(opts = {}) {
|
|
187
|
+
return this.markets({ ...opts, signal: "arb" });
|
|
188
|
+
}
|
|
189
|
+
/** Flag a bad or incorrect arbitrage signal. At least one field of body is required. */
|
|
190
|
+
async reportBadArb(body) {
|
|
191
|
+
const hasField = body && Object.values(body).some((v) => v !== void 0 && v !== null);
|
|
192
|
+
if (!hasField) {
|
|
193
|
+
throw new Error(
|
|
194
|
+
"dino-markets: reportBadArb(body) requires at least one of opp_id/reason/sport/market/detail"
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
return this.request("POST", "/v2/report-bad-arb", body);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Mint a short-lived WebSocket connect ticket (Basic/Pro only; 402 on Free).
|
|
201
|
+
* See stream.ts's `watch()` helper to consume it directly.
|
|
202
|
+
*/
|
|
203
|
+
async streamToken() {
|
|
204
|
+
return this.request("POST", "/v1/stream/token");
|
|
205
|
+
}
|
|
206
|
+
async request(method, path, body) {
|
|
207
|
+
const url = `${this.baseUrl}${path}`;
|
|
208
|
+
const init = {
|
|
209
|
+
method,
|
|
210
|
+
headers: {
|
|
211
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
212
|
+
Accept: "application/json",
|
|
213
|
+
"User-Agent": `dino-markets-js/${SDK_VERSION}`,
|
|
214
|
+
...body !== void 0 ? { "Content-Type": "application/json" } : {}
|
|
215
|
+
},
|
|
216
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
217
|
+
};
|
|
218
|
+
let attempt = 0;
|
|
219
|
+
for (; ; ) {
|
|
220
|
+
const controller = new AbortController();
|
|
221
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
222
|
+
let response;
|
|
223
|
+
try {
|
|
224
|
+
response = await this.fetchImpl(url, { ...init, signal: controller.signal });
|
|
225
|
+
} catch (err) {
|
|
226
|
+
clearTimeout(timer);
|
|
227
|
+
if (attempt < this.maxRetries) {
|
|
228
|
+
const delayMs = RETRY_BACKOFF_MS[attempt] ?? 2e3;
|
|
229
|
+
attempt += 1;
|
|
230
|
+
await sleep(delayMs);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
234
|
+
throw new Error(`dino-markets: request to ${path} timed out after ${this.timeoutMs}ms`);
|
|
235
|
+
}
|
|
236
|
+
throw err;
|
|
237
|
+
}
|
|
238
|
+
clearTimeout(timer);
|
|
239
|
+
if (response.ok) {
|
|
240
|
+
if (response.status === 202 || response.status === 204) {
|
|
241
|
+
return await safeJson(response);
|
|
242
|
+
}
|
|
243
|
+
return await response.json();
|
|
244
|
+
}
|
|
245
|
+
const retryable = response.status === 429 || response.status >= 500;
|
|
246
|
+
if (retryable && attempt < this.maxRetries) {
|
|
247
|
+
const body3 = await safeJson(response);
|
|
248
|
+
const headerRetry = Number(response.headers.get("retry-after"));
|
|
249
|
+
const bodyRetry = body3 && typeof body3 === "object" && "retry_after" in body3 ? Number(body3.retry_after) : NaN;
|
|
250
|
+
const retryAfterS = Number.isFinite(headerRetry) ? headerRetry : Number.isFinite(bodyRetry) ? bodyRetry : void 0;
|
|
251
|
+
const delayMs = retryAfterS !== void 0 ? retryAfterS * 1e3 : RETRY_BACKOFF_MS[attempt] ?? 2e3;
|
|
252
|
+
attempt += 1;
|
|
253
|
+
await sleep(delayMs);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
const body2 = await safeJson(response);
|
|
257
|
+
throw mapError(response.status, body2, response.headers);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
async function safeJson(response) {
|
|
262
|
+
try {
|
|
263
|
+
const text = await response.text();
|
|
264
|
+
if (!text) return null;
|
|
265
|
+
return JSON.parse(text);
|
|
266
|
+
} catch {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// src/stream.ts
|
|
272
|
+
async function watch(client, opts) {
|
|
273
|
+
const centrifugeModuleName = "centrifuge";
|
|
274
|
+
let CentrifugeCtor;
|
|
275
|
+
try {
|
|
276
|
+
const mod = await import(
|
|
277
|
+
/* @vite-ignore */
|
|
278
|
+
centrifugeModuleName
|
|
279
|
+
);
|
|
280
|
+
CentrifugeCtor = mod.Centrifuge ?? mod.default?.Centrifuge ?? mod.default;
|
|
281
|
+
} catch {
|
|
282
|
+
throw new Error(
|
|
283
|
+
'dino-markets: streaming requires the "centrifuge" package. Install it with `npm install centrifuge`.'
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
const { ticket, ws_url } = await client.streamToken();
|
|
287
|
+
const centrifuge = new CentrifugeCtor(ws_url, {
|
|
288
|
+
data: { t: ticket }
|
|
289
|
+
});
|
|
290
|
+
centrifuge.on("publication", (ctx) => {
|
|
291
|
+
opts.onFrame(ctx.data, ctx.channel);
|
|
292
|
+
});
|
|
293
|
+
if (opts.onError) {
|
|
294
|
+
centrifuge.on("error", opts.onError);
|
|
295
|
+
}
|
|
296
|
+
centrifuge.connect();
|
|
297
|
+
return {
|
|
298
|
+
close: () => centrifuge.disconnect()
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
302
|
+
0 && (module.exports = {
|
|
303
|
+
AuthenticationError,
|
|
304
|
+
Dino,
|
|
305
|
+
DinoError,
|
|
306
|
+
PlanError,
|
|
307
|
+
RateLimitError,
|
|
308
|
+
ServerError,
|
|
309
|
+
mapError,
|
|
310
|
+
watch
|
|
311
|
+
});
|
|
312
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/client.ts","../src/stream.ts"],"sourcesContent":["export { Dino } from \"./client.js\";\nexport type { DinoOptions, FetchLike } from \"./client.js\";\nexport { watch } from \"./stream.js\";\nexport type { WatchOptions, WatchHandle, StreamFrame } from \"./stream.js\";\nexport {\n DinoError,\n AuthenticationError,\n PlanError,\n RateLimitError,\n ServerError,\n mapError,\n} from \"./errors.js\";\nexport type {\n KalshiBook,\n PolymarketBook,\n VenueIds,\n Confidence,\n Settlement,\n Outcome,\n MarketResult,\n MarketLinks,\n KalshiLadderBucket,\n MarketStatus,\n MarketSignal,\n Market,\n MarketsResponse,\n HistoryPoint,\n HistorySeries,\n HistoryResponse,\n LeagueSummary,\n LeaguesResponse,\n StreamTokenResponse,\n ReportBadArbBody,\n ReportBadArbResponse,\n MarketsOptions,\n FindArbitrageOptions,\n} from \"./types.js\";\n","/**\n * Error types raised by the dino-markets client.\n *\n * Every non-2xx HTTP response from the API is mapped to one of these via\n * `mapError`. `body` is the parsed JSON response body when available (the\n * API returns `{ error: string, ... }` on failures), or `null` if the body\n * could not be parsed as JSON.\n */\n\nexport class DinoError extends Error {\n /** HTTP status code of the failed response. */\n status: number;\n /** Parsed JSON response body, or null if unavailable/unparseable. */\n body: unknown;\n\n constructor(message: string, status: number, body: unknown = null) {\n super(message);\n this.name = \"DinoError\";\n this.status = status;\n this.body = body;\n }\n}\n\n/** 401 - missing, malformed, or revoked API key. */\nexport class AuthenticationError extends DinoError {\n constructor(message: string, status: number, body: unknown = null) {\n super(message, status, body);\n this.name = \"AuthenticationError\";\n }\n}\n\n/** 402 - subscription inactive, or the endpoint needs a paid plan (e.g. streaming on Free). */\nexport class PlanError extends DinoError {\n constructor(message: string, status: number, body: unknown = null) {\n super(message, status, body);\n this.name = \"PlanError\";\n }\n}\n\n/** 429 - rate limit exceeded. `retryAfter` is in seconds when the server reports one. */\nexport class RateLimitError extends DinoError {\n retryAfter?: number;\n\n constructor(message: string, status: number, body: unknown = null, retryAfter?: number) {\n super(message, status, body);\n this.name = \"RateLimitError\";\n this.retryAfter = retryAfter;\n }\n}\n\n/** 5xx - server-side failure. */\nexport class ServerError extends DinoError {\n constructor(message: string, status: number, body: unknown = null) {\n super(message, status, body);\n this.name = \"ServerError\";\n }\n}\n\nfunction extractRetryAfter(headers: Headers | undefined, body: unknown): number | undefined {\n const header = headers?.get?.(\"retry-after\");\n if (header) {\n const n = Number(header);\n if (Number.isFinite(n)) return n;\n }\n if (body && typeof body === \"object\" && \"retry_after\" in (body as Record<string, unknown>)) {\n const n = Number((body as Record<string, unknown>).retry_after);\n if (Number.isFinite(n)) return n;\n }\n return undefined;\n}\n\nfunction errorMessage(status: number, body: unknown, fallback: string): string {\n if (body && typeof body === \"object\" && \"error\" in (body as Record<string, unknown>)) {\n const msg = (body as Record<string, unknown>).error;\n if (typeof msg === \"string\" && msg) return msg;\n }\n return fallback;\n}\n\n/**\n * Map an HTTP response's status/body to a DinoError subclass. status must be\n * a non-2xx code; headers is optional and only used to read Retry-After.\n */\nexport function mapError(status: number, body: unknown, headers?: Headers): DinoError {\n if (status === 401) {\n return new AuthenticationError(errorMessage(status, body, \"authentication failed\"), status, body);\n }\n if (status === 402) {\n return new PlanError(errorMessage(status, body, \"plan does not allow this request\"), status, body);\n }\n if (status === 429) {\n const retryAfter = extractRetryAfter(headers, body);\n return new RateLimitError(errorMessage(status, body, \"rate limit exceeded\"), status, body, retryAfter);\n }\n if (status >= 500) {\n return new ServerError(errorMessage(status, body, \"server error\"), status, body);\n }\n return new DinoError(errorMessage(status, body, `request failed with status ${status}`), status, body);\n}\n","import { mapError } from \"./errors.js\";\nimport type {\n FindArbitrageOptions,\n HistoryResponse,\n LeaguesResponse,\n Market,\n MarketsOptions,\n MarketsResponse,\n ReportBadArbBody,\n ReportBadArbResponse,\n StreamTokenResponse,\n} from \"./types.js\";\n\nconst SDK_VERSION = \"0.1.0\";\nconst DEFAULT_BASE_URL = \"https://api.dino.markets\";\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_MAX_RETRIES = 2;\nconst RETRY_BACKOFF_MS = [500, 1000, 2000];\n\nexport type FetchLike = typeof fetch;\n\nexport interface DinoOptions {\n /** API key. Falls back to process.env.DINO_API_KEY when process is available. */\n apiKey?: string;\n baseUrl?: string;\n timeoutMs?: number;\n maxRetries?: number;\n /** Injectable fetch implementation, mainly for tests. Defaults to the global fetch. */\n fetchImpl?: FetchLike;\n}\n\nfunction resolveApiKey(apiKey?: string): string {\n if (apiKey) return apiKey;\n if (typeof process !== \"undefined\" && process.env && process.env.DINO_API_KEY) {\n return process.env.DINO_API_KEY;\n }\n throw new Error(\n \"dino-markets: no API key provided. Pass { apiKey } or set the DINO_API_KEY environment variable. \" +\n \"Get a free key at https://dino.markets\",\n );\n}\n\nfunction validateLimit(limit: number | undefined): void {\n if (limit === undefined) return;\n if (!Number.isInteger(limit) || limit < 1 || limit > 1000) {\n throw new Error(`dino-markets: limit must be an integer between 1 and 1000, got ${limit}`);\n }\n}\n\nfunction buildQuery(params: Record<string, string | number | undefined>): string {\n const usp = new URLSearchParams();\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined || value === null) continue;\n usp.set(key, String(value));\n }\n const qs = usp.toString();\n return qs ? `?${qs}` : \"\";\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport class Dino {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly timeoutMs: number;\n private readonly maxRetries: number;\n private readonly fetchImpl: FetchLike;\n\n constructor(opts: DinoOptions = {}) {\n this.apiKey = resolveApiKey(opts.apiKey);\n this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.maxRetries = opts.maxRetries ?? DEFAULT_MAX_RETRIES;\n this.fetchImpl = opts.fetchImpl ?? fetch;\n }\n\n /** Matched cross-venue catalog. See MarketsOptions for filters. */\n async markets(opts: MarketsOptions = {}): Promise<MarketsResponse> {\n validateLimit(opts.limit);\n const qs = buildQuery({\n sport: opts.sport,\n league: opts.league,\n status: opts.status,\n signal: opts.signal,\n sort: opts.sort,\n include: opts.include,\n market_type: opts.market_type,\n game_id: opts.game_id,\n limit: opts.limit,\n });\n return this.request<MarketsResponse>(\"GET\", `/v2/markets${qs}`);\n }\n\n /** One canonical market object. Accepts a \"dino_<uuid>\" id or a bare uuid. */\n async market(marketId: string): Promise<Market> {\n if (!marketId) throw new Error(\"dino-markets: market(marketId) requires a non-empty marketId\");\n return this.request<Market>(\"GET\", `/v2/pairs/${encodeURIComponent(marketId)}`);\n }\n\n /** Per-outcome Kalshi/Polymarket price history for one market. */\n async history(marketId: string): Promise<HistoryResponse> {\n if (!marketId) throw new Error(\"dino-markets: history(marketId) requires a non-empty marketId\");\n return this.request<HistoryResponse>(\"GET\", `/v2/pairs/${encodeURIComponent(marketId)}/history`);\n }\n\n /** Sports and leagues currently in season, with lifecycle counts. */\n async leagues(): Promise<LeaguesResponse> {\n return this.request<LeaguesResponse>(\"GET\", \"/v2/leagues\");\n }\n\n /** Sugar for markets({ ...opts, signal: \"arb\" }). */\n async findArbitrage(opts: FindArbitrageOptions = {}): Promise<MarketsResponse> {\n return this.markets({ ...opts, signal: \"arb\" });\n }\n\n /** Flag a bad or incorrect arbitrage signal. At least one field of body is required. */\n async reportBadArb(body: ReportBadArbBody): Promise<ReportBadArbResponse> {\n // Mirrors the server's own gate (and the Python SDK): any non-null value counts,\n // empty strings included.\n const hasField = body && Object.values(body).some((v) => v !== undefined && v !== null);\n if (!hasField) {\n throw new Error(\n \"dino-markets: reportBadArb(body) requires at least one of opp_id/reason/sport/market/detail\",\n );\n }\n return this.request<ReportBadArbResponse>(\"POST\", \"/v2/report-bad-arb\", body);\n }\n\n /**\n * Mint a short-lived WebSocket connect ticket (Basic/Pro only; 402 on Free).\n * See stream.ts's `watch()` helper to consume it directly.\n */\n async streamToken(): Promise<StreamTokenResponse> {\n return this.request<StreamTokenResponse>(\"POST\", \"/v1/stream/token\");\n }\n\n private async request<T>(method: string, path: string, body?: unknown): Promise<T> {\n const url = `${this.baseUrl}${path}`;\n const init: RequestInit = {\n method,\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n Accept: \"application/json\",\n \"User-Agent\": `dino-markets-js/${SDK_VERSION}`,\n ...(body !== undefined ? { \"Content-Type\": \"application/json\" } : {}),\n },\n ...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n };\n\n let attempt = 0;\n for (;;) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n let response: Response;\n try {\n response = await this.fetchImpl(url, { ...init, signal: controller.signal });\n } catch (err) {\n clearTimeout(timer);\n // Transient transport failure (network error or timeout): retry on the same\n // backoff schedule as 429/5xx, matching the Python SDK's URLError handling.\n if (attempt < this.maxRetries) {\n const delayMs = RETRY_BACKOFF_MS[attempt] ?? 2000;\n attempt += 1;\n await sleep(delayMs);\n continue;\n }\n if (err instanceof Error && err.name === \"AbortError\") {\n throw new Error(`dino-markets: request to ${path} timed out after ${this.timeoutMs}ms`);\n }\n throw err;\n }\n clearTimeout(timer);\n\n if (response.ok) {\n if (response.status === 202 || response.status === 204) {\n return (await safeJson(response)) as T;\n }\n return (await response.json()) as T;\n }\n\n const retryable = response.status === 429 || response.status >= 500;\n if (retryable && attempt < this.maxRetries) {\n const body = await safeJson(response);\n const headerRetry = Number(response.headers.get(\"retry-after\"));\n const bodyRetry =\n body && typeof body === \"object\" && \"retry_after\" in (body as Record<string, unknown>)\n ? Number((body as Record<string, unknown>).retry_after)\n : NaN;\n const retryAfterS = Number.isFinite(headerRetry)\n ? headerRetry\n : Number.isFinite(bodyRetry)\n ? bodyRetry\n : undefined;\n const delayMs = retryAfterS !== undefined ? retryAfterS * 1000 : RETRY_BACKOFF_MS[attempt] ?? 2000;\n attempt += 1;\n await sleep(delayMs);\n continue;\n }\n\n const body = await safeJson(response);\n throw mapError(response.status, body, response.headers);\n }\n }\n}\n\nasync function safeJson(response: Response): Promise<unknown> {\n try {\n const text = await response.text();\n if (!text) return null;\n return JSON.parse(text);\n } catch {\n return null;\n }\n}\n","import type { Dino } from \"./client.js\";\n\n/**\n * A market/price frame published on the markets:* channels. Frames are flat\n * and self-describing (type discriminator, no wrapper) - see the WebSocket\n * docs at https://dino.markets/docs/websocket. This SDK does not parse the\n * frame further; it is passed through as-is.\n */\nexport type StreamFrame = Record<string, unknown>;\n\nexport interface WatchOptions {\n /** Called for every publication received on the subscribed channels. */\n onFrame: (frame: StreamFrame, channel: string) => void;\n /** Called on a connection error. Optional. */\n onError?: (error: unknown) => void;\n}\n\nexport interface WatchHandle {\n /** Disconnect the stream. */\n close: () => void;\n}\n\n/**\n * Connect to the dino.markets real-time stream and forward publications to\n * onFrame. Requires a Basic or Pro plan; a Free key's streamToken() call\n * throws a PlanError (402).\n *\n * Requires the optional \"centrifuge\" peer dependency: `npm install centrifuge`.\n */\nexport async function watch(client: Dino, opts: WatchOptions): Promise<WatchHandle> {\n // Dynamic + untyped on purpose: \"centrifuge\" is an optional peer dependency\n // (streaming only), so this module must build and type-check without it\n // installed. The import specifier is not statically analyzable by tsup's\n // bundler, which is what keeps it out of the required dependency graph.\n const centrifugeModuleName = \"centrifuge\";\n let CentrifugeCtor: new (url: string, opts: unknown) => CentrifugeClient;\n try {\n const mod: any = await import(/* @vite-ignore */ centrifugeModuleName);\n CentrifugeCtor = mod.Centrifuge ?? mod.default?.Centrifuge ?? mod.default;\n } catch {\n throw new Error(\n 'dino-markets: streaming requires the \"centrifuge\" package. Install it with `npm install centrifuge`.',\n );\n }\n\n const { ticket, ws_url } = await client.streamToken();\n\n const centrifuge = new CentrifugeCtor(ws_url, {\n data: { t: ticket },\n });\n\n centrifuge.on(\"publication\", (ctx: { channel: string; data: StreamFrame }) => {\n opts.onFrame(ctx.data, ctx.channel);\n });\n\n if (opts.onError) {\n centrifuge.on(\"error\", opts.onError);\n }\n\n centrifuge.connect();\n\n return {\n close: () => centrifuge.disconnect(),\n };\n}\n\n/** Minimal shape of the centrifuge Client this SDK relies on, kept local so no type dependency is needed. */\ninterface CentrifugeClient {\n on(event: \"publication\", cb: (ctx: { channel: string; data: StreamFrame }) => void): void;\n on(event: \"error\", cb: (error: unknown) => void): void;\n connect(): void;\n disconnect(): void;\n}\n"],"mappings":"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","names":["body"]}
|