puntersedge 0.1.0 → 1.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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +283 -37
  3. package/dist/cjs/client.js +487 -0
  4. package/dist/cjs/client.js.map +1 -0
  5. package/dist/cjs/credits.js +82 -0
  6. package/dist/cjs/credits.js.map +1 -0
  7. package/dist/cjs/errors.js +130 -0
  8. package/dist/cjs/errors.js.map +1 -0
  9. package/dist/cjs/http.js +218 -0
  10. package/dist/cjs/http.js.map +1 -0
  11. package/dist/cjs/index.js +58 -0
  12. package/dist/cjs/index.js.map +1 -0
  13. package/dist/cjs/package.json +3 -0
  14. package/dist/cjs/types.generated.js +13 -0
  15. package/dist/cjs/types.generated.js.map +1 -0
  16. package/dist/cjs/webhook.js +95 -0
  17. package/dist/cjs/webhook.js.map +1 -0
  18. package/dist/esm/client.d.ts +480 -0
  19. package/dist/esm/client.d.ts.map +1 -0
  20. package/dist/esm/client.js +483 -0
  21. package/dist/esm/client.js.map +1 -0
  22. package/dist/esm/credits.d.ts +78 -0
  23. package/dist/esm/credits.d.ts.map +1 -0
  24. package/dist/esm/credits.js +78 -0
  25. package/dist/esm/credits.js.map +1 -0
  26. package/dist/esm/errors.d.ts +132 -0
  27. package/dist/esm/errors.d.ts.map +1 -0
  28. package/dist/esm/errors.js +117 -0
  29. package/dist/esm/errors.js.map +1 -0
  30. package/dist/esm/http.d.ts +52 -0
  31. package/dist/esm/http.d.ts.map +1 -0
  32. package/dist/esm/http.js +214 -0
  33. package/dist/esm/http.js.map +1 -0
  34. package/dist/esm/index.d.ts +22 -0
  35. package/dist/esm/index.d.ts.map +1 -0
  36. package/dist/esm/index.js +23 -0
  37. package/dist/esm/index.js.map +1 -0
  38. package/dist/esm/types.generated.d.ts +1453 -0
  39. package/dist/esm/types.generated.d.ts.map +1 -0
  40. package/dist/esm/types.generated.js +12 -0
  41. package/dist/esm/types.generated.js.map +1 -0
  42. package/dist/esm/webhook.d.ts +56 -0
  43. package/dist/esm/webhook.d.ts.map +1 -0
  44. package/dist/esm/webhook.js +92 -0
  45. package/dist/esm/webhook.js.map +1 -0
  46. package/package.json +52 -19
  47. package/src/client.ts +799 -0
  48. package/src/credits.ts +131 -0
  49. package/src/errors.ts +166 -0
  50. package/src/http.ts +268 -0
  51. package/src/index.ts +68 -206
  52. package/src/types.generated.ts +1514 -0
  53. package/src/webhook.ts +113 -0
  54. package/dist/index.d.ts +0 -117
  55. package/dist/index.js +0 -99
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ /**
3
+ * Typed errors for the PuntersEdge client.
4
+ *
5
+ * WHY A CLASS PER STATUS. Odds code has to branch on *why* a call failed, and it has to do it
6
+ * without parsing an error string. A 402 means buy more credits; a 429 means wait and try the
7
+ * same call again; a 422 means the request itself is wrong and retrying will fail identically
8
+ * forever. Collapsing those into one `Error` pushes the branch into a regex over `.message`,
9
+ * which is how a "rate limited, back off" path silently starts swallowing "sport_key does not
10
+ * exist".
11
+ *
12
+ * Every error carries the RFC 7807 `Problem` body the API returned, when it returned one, so
13
+ * `err.problem.type` is a stable URI you can switch on and `err.problem.detail` is the
14
+ * explanation. `instanceof PuntersEdgeError` catches everything from this SDK.
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.APIError = exports.NetworkError = exports.ServerError = exports.NotFoundError = exports.ValidationError = exports.RateLimitError = exports.CreditsExhaustedError = exports.AuthError = exports.PuntersEdgeError = void 0;
18
+ exports.isProblem = isProblem;
19
+ /** Base class — `catch (e) { if (e instanceof PuntersEdgeError) ... }` catches every SDK error. */
20
+ class PuntersEdgeError extends Error {
21
+ /** HTTP status, or undefined when the request never got a response. */
22
+ status;
23
+ /** The RFC 7807 body, when the API supplied one. */
24
+ problem;
25
+ /** Raw response body (truncated to 2KB), for errors that are not a Problem document. */
26
+ body;
27
+ /** API path that produced this error. */
28
+ path;
29
+ /** Response headers, when there was a response. */
30
+ headers;
31
+ constructor(message, init = {}) {
32
+ super(message, init.cause === undefined ? undefined : { cause: init.cause });
33
+ this.name = new.target.name;
34
+ this.status = init.status;
35
+ this.problem = init.problem;
36
+ this.body = init.body;
37
+ this.path = init.path;
38
+ this.headers = init.headers;
39
+ // Restores `instanceof` when the package is compiled down to ES5 by a consumer's bundler.
40
+ Object.setPrototypeOf(this, new.target.prototype);
41
+ }
42
+ }
43
+ exports.PuntersEdgeError = PuntersEdgeError;
44
+ /**
45
+ * 401 or 403. The key is missing, wrong, inactive, IP-blocked, or on a plan that does not
46
+ * include this endpoint — the closing-line archive and webhook creation need Standard or above.
47
+ * Retrying will not help; read `problem.detail`.
48
+ */
49
+ class AuthError extends PuntersEdgeError {
50
+ }
51
+ exports.AuthError = AuthError;
52
+ /**
53
+ * 402. The monthly credit allowance is spent.
54
+ *
55
+ * This is NOT a rate limit, and backing off does not clear it — the balance resets on the
56
+ * billing date. `upgrade` carries the API's own recommendation: which plan, what it costs, the
57
+ * link to send a human to, and the exact request to POST to buy it without leaving the shell.
58
+ *
59
+ * Check `upgrade.covers`. `"partial"` means the recommended plan is the largest self-serve tier
60
+ * and is *still* below the measured need, so buying it will not stop the 402s.
61
+ */
62
+ class CreditsExhaustedError extends PuntersEdgeError {
63
+ /** The API's upgrade recommendation, from the `X-Upgrade-*` headers. */
64
+ upgrade;
65
+ constructor(message, init = {}) {
66
+ super(message, init);
67
+ this.upgrade = init.upgrade;
68
+ }
69
+ }
70
+ exports.CreditsExhaustedError = CreditsExhaustedError;
71
+ /**
72
+ * 429. Too many requests per minute for this key's plan. `retryAfter` is the server's own
73
+ * `Retry-After` in seconds — wait that long and repeat the identical call. This SDK never waits
74
+ * for you; see the note on retries in the README.
75
+ *
76
+ * A 429 is not billed, so nothing was spent.
77
+ */
78
+ class RateLimitError extends PuntersEdgeError {
79
+ /** Seconds to wait, from `Retry-After`. The API sends 60 on a per-minute limit. */
80
+ retryAfter;
81
+ /** Requests-per-minute ceiling for this plan, from `X-RateLimit-Limit`. */
82
+ limit;
83
+ constructor(message, init = {}) {
84
+ super(message, init);
85
+ this.retryAfter = init.retryAfter;
86
+ this.limit = init.limit;
87
+ }
88
+ }
89
+ exports.RateLimitError = RateLimitError;
90
+ /**
91
+ * 422. The request is malformed — an unknown `sport_key`, an unrecognised bookmaker key, a
92
+ * `direction` other than firming/drifting. These are refused **before billing**, so a 422 costs
93
+ * nothing; `fields` lists what the server objected to.
94
+ */
95
+ class ValidationError extends PuntersEdgeError {
96
+ /** Field-level complaints, when the 422 body carried them. */
97
+ fields;
98
+ constructor(message, init = {}) {
99
+ super(message, init);
100
+ this.fields = init.fields ?? [];
101
+ }
102
+ }
103
+ exports.ValidationError = ValidationError;
104
+ /** 404. No such race, horse, webhook or route. */
105
+ class NotFoundError extends PuntersEdgeError {
106
+ }
107
+ exports.NotFoundError = NotFoundError;
108
+ /** Any 5xx. The request may be fine; the API or an upstream is not. Safe to retry with backoff. */
109
+ class ServerError extends PuntersEdgeError {
110
+ }
111
+ exports.ServerError = ServerError;
112
+ /**
113
+ * The request never completed: DNS failure, connection reset, TLS error, or the timeout /
114
+ * `AbortSignal` fired. `cause` holds the underlying `fetch` rejection.
115
+ */
116
+ class NetworkError extends PuntersEdgeError {
117
+ }
118
+ exports.NetworkError = NetworkError;
119
+ /** A 4xx this SDK has no dedicated class for. */
120
+ class APIError extends PuntersEdgeError {
121
+ }
122
+ exports.APIError = APIError;
123
+ /** True when `value` looks like an RFC 7807 document rather than some other JSON body. */
124
+ function isProblem(value) {
125
+ return (typeof value === "object" && value !== null &&
126
+ typeof value.type === "string" &&
127
+ typeof value.title === "string" &&
128
+ typeof value.status === "number");
129
+ }
130
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,218 @@
1
+ "use strict";
2
+ /**
3
+ * The transport: one `fetch`, one place where a non-2xx becomes a typed error.
4
+ *
5
+ * Runs anywhere `fetch`, `AbortController` and `Headers` exist — Node 18+, Deno, Bun,
6
+ * Cloudflare Workers, Vercel Edge, and browsers. There are no dependencies and nothing is
7
+ * imported from `node:*`, so a bundler targeting the edge does not have to polyfill anything.
8
+ *
9
+ * NOTHING IS RETRIED HERE, on purpose. Odds are time-sensitive. A transparent retry inside the
10
+ * client can hand you a price recorded before a move you would have seen, and it can double a
11
+ * billed call without telling you. Retry deliberately, at the layer that knows whether a stale
12
+ * answer is acceptable — `RateLimitError.retryAfter` gives you the wait the server asked for.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.buildQuery = buildQuery;
16
+ exports.request = request;
17
+ const errors_js_1 = require("./errors.js");
18
+ const credits_js_1 = require("./credits.js");
19
+ /**
20
+ * Serialise query parameters.
21
+ *
22
+ * Booleans go over the wire as `true`/`false`. Arrays are joined with commas rather than
23
+ * repeated, because that is what this API's `categories`, `bookmakers`, `country` and `venue`
24
+ * parameters expect — `?categories=horse&categories=harness` is not the same request as
25
+ * `?categories=horse,harness` and only the second one works. `null` and `undefined` are dropped,
26
+ * so an unset option is an absent parameter rather than the string "undefined".
27
+ */
28
+ function buildQuery(params) {
29
+ if (!params)
30
+ return "";
31
+ const usp = new URLSearchParams();
32
+ for (const [key, value] of Object.entries(params)) {
33
+ if (value === null || value === undefined)
34
+ continue;
35
+ if (Array.isArray(value)) {
36
+ if (value.length === 0)
37
+ continue;
38
+ usp.set(key, value.join(","));
39
+ }
40
+ else if (typeof value === "boolean") {
41
+ usp.set(key, value ? "true" : "false");
42
+ }
43
+ else {
44
+ usp.set(key, String(value));
45
+ }
46
+ }
47
+ const qs = usp.toString();
48
+ return qs ? "?" + qs : "";
49
+ }
50
+ /** Read a body as JSON, falling back to text. Never throws. */
51
+ async function readBody(res) {
52
+ const raw = await res.text().catch(() => "");
53
+ if (!raw)
54
+ return {};
55
+ try {
56
+ return { json: JSON.parse(raw), text: raw };
57
+ }
58
+ catch {
59
+ return { text: raw };
60
+ }
61
+ }
62
+ function fieldErrors(problem) {
63
+ const detail = problem?.detail;
64
+ if (!Array.isArray(detail))
65
+ return [];
66
+ return detail.filter((d) => typeof d === "object" && d !== null && Array.isArray(d.loc));
67
+ }
68
+ /**
69
+ * Render `problem.detail` into one line.
70
+ *
71
+ * The API types `detail` three ways deliberately: a string for most errors, an ARRAY of field
72
+ * errors on a 422, and an OBJECT on a 429. Only handling the string case leaves the two most
73
+ * actionable errors reading `422 Validation Error` — a message that tells a developer nothing
74
+ * about which parameter they got wrong.
75
+ */
76
+ function renderDetail(detail) {
77
+ if (detail === undefined || detail === null)
78
+ return undefined;
79
+ if (typeof detail === "string")
80
+ return detail;
81
+ if (Array.isArray(detail)) {
82
+ const parts = detail.map((d) => {
83
+ if (typeof d === "object" && d !== null && Array.isArray(d.loc)) {
84
+ const fe = d;
85
+ // `["query", "direction"]` reads better as `direction` than as the full path.
86
+ const field = fe.loc.filter((p) => p !== "body" && p !== "query" && p !== "path").join(".");
87
+ return field ? `${field}: ${fe.msg}` : fe.msg;
88
+ }
89
+ return typeof d === "string" ? d : JSON.stringify(d);
90
+ });
91
+ return parts.length ? parts.join("; ") : undefined;
92
+ }
93
+ if (typeof detail === "object") {
94
+ try {
95
+ return JSON.stringify(detail);
96
+ }
97
+ catch {
98
+ return undefined;
99
+ }
100
+ }
101
+ return String(detail);
102
+ }
103
+ /** Best available human-readable message: the Problem's own words, else the raw body, else the status. */
104
+ function messageFor(res, problem, text) {
105
+ if (problem) {
106
+ const detail = renderDetail(problem.detail);
107
+ return detail && detail !== problem.title
108
+ ? `${res.status} ${problem.title}: ${detail}`
109
+ : `${res.status} ${problem.title}`;
110
+ }
111
+ const snippet = (text ?? "").trim().slice(0, 300);
112
+ return snippet ? `HTTP ${res.status}: ${snippet}` : `HTTP ${res.status} ${res.statusText}`;
113
+ }
114
+ function toError(res, path, body) {
115
+ const problem = (0, errors_js_1.isProblem)(body.json) ? body.json : undefined;
116
+ const init = {
117
+ status: res.status,
118
+ problem,
119
+ body: body.text?.slice(0, 2048),
120
+ path,
121
+ headers: res.headers,
122
+ };
123
+ const message = messageFor(res, problem, body.text);
124
+ switch (res.status) {
125
+ case 401:
126
+ case 403:
127
+ return new errors_js_1.AuthError(message, init);
128
+ case 402:
129
+ return new errors_js_1.CreditsExhaustedError(message, { ...init, upgrade: (0, credits_js_1.parseUpgrade)(res.headers) });
130
+ case 404:
131
+ return new errors_js_1.NotFoundError(message, init);
132
+ case 422:
133
+ return new errors_js_1.ValidationError(message, { ...init, fields: fieldErrors(problem) });
134
+ case 429: {
135
+ const retry = Number(res.headers.get("Retry-After"));
136
+ const limit = Number(res.headers.get("X-RateLimit-Limit"));
137
+ return new errors_js_1.RateLimitError(message, {
138
+ ...init,
139
+ retryAfter: Number.isFinite(retry) ? retry : undefined,
140
+ limit: Number.isFinite(limit) ? limit : undefined,
141
+ });
142
+ }
143
+ default:
144
+ return res.status >= 500 ? new errors_js_1.ServerError(message, init) : new errors_js_1.APIError(message, init);
145
+ }
146
+ }
147
+ /**
148
+ * Compose the caller's signal with a timeout.
149
+ *
150
+ * `AbortSignal.any` landed in Node 20 and is still missing in enough runtimes that relying on it
151
+ * would narrow where this package works, so the fallback wires the listener by hand.
152
+ */
153
+ function withTimeout(signal, ms) {
154
+ const controller = new AbortController();
155
+ const timer = setTimeout(() => controller.abort(new Error(`timed out after ${ms}ms`)), ms);
156
+ const onAbort = () => controller.abort(signal?.reason);
157
+ if (signal) {
158
+ if (signal.aborted)
159
+ onAbort();
160
+ else
161
+ signal.addEventListener("abort", onAbort, { once: true });
162
+ }
163
+ return {
164
+ signal: controller.signal,
165
+ done: () => {
166
+ clearTimeout(timer);
167
+ signal?.removeEventListener("abort", onAbort);
168
+ },
169
+ };
170
+ }
171
+ async function request(config, method, path, params, jsonBody, options = {}) {
172
+ const url = config.baseUrl + path + buildQuery(params);
173
+ const headers = {
174
+ Accept: "application/json",
175
+ "User-Agent": config.userAgent,
176
+ ...options.headers,
177
+ };
178
+ if (config.apiKey)
179
+ headers["X-API-Key"] = config.apiKey;
180
+ if (jsonBody !== undefined)
181
+ headers["Content-Type"] = "application/json";
182
+ const { signal, done } = withTimeout(options.signal, options.timeoutMs ?? config.timeoutMs);
183
+ let res;
184
+ try {
185
+ res = await config.fetch(url, {
186
+ method,
187
+ headers,
188
+ body: jsonBody === undefined ? undefined : JSON.stringify(jsonBody),
189
+ signal,
190
+ });
191
+ }
192
+ catch (cause) {
193
+ throw new errors_js_1.NetworkError(`${method} ${path} failed: ${cause instanceof Error ? cause.message : String(cause)}`, { path, cause });
194
+ }
195
+ finally {
196
+ done();
197
+ }
198
+ const credits = (0, credits_js_1.parseCredits)(res.headers);
199
+ // Reported even on an error: a 402 still tells you the balance, and that is the response
200
+ // where a caller most needs it.
201
+ if (credits && config.onCredits)
202
+ config.onCredits(credits);
203
+ if (!res.ok)
204
+ throw toError(res, path, await readBody(res));
205
+ // 204 and an empty 200 (DELETE /v1/webhooks/{id}) have no body to parse.
206
+ if (res.status === 204)
207
+ return { data: undefined, credits, headers: res.headers };
208
+ const body = await readBody(res);
209
+ if (body.text === undefined || body.text === "") {
210
+ return { data: undefined, credits, headers: res.headers };
211
+ }
212
+ if (!("json" in body)) {
213
+ // A 2xx that is not JSON: the CSV format on the archive endpoints comes back this way.
214
+ return { data: body.text, credits, headers: res.headers };
215
+ }
216
+ return { data: body.json, credits, headers: res.headers };
217
+ }
218
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/http.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ /**
3
+ * puntersedge — the official TypeScript/JavaScript client for the PuntersEdge Odds API.
4
+ *
5
+ * Australian and New Zealand racing (thoroughbred, greyhound, harness) and Australian sports
6
+ * odds from 14 Australian bookmakers. One REST/JSON API, an `X-API-Key` header, a free tier.
7
+ *
8
+ * npm install puntersedge
9
+ *
10
+ * import { PuntersEdge } from "puntersedge";
11
+ * const pe = new PuntersEdge({ apiKey: process.env.PUNTERSEDGE_API_KEY });
12
+ * const races = await pe.racing.nextToGo({ numRaces: 5 });
13
+ *
14
+ * Get a free key at https://puntersedge.online/api.
15
+ */
16
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ var desc = Object.getOwnPropertyDescriptor(m, k);
19
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
20
+ desc = { enumerable: true, get: function() { return m[k]; } };
21
+ }
22
+ Object.defineProperty(o, k2, desc);
23
+ }) : (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ o[k2] = m[k];
26
+ }));
27
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
28
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
29
+ };
30
+ Object.defineProperty(exports, "__esModule", { value: true });
31
+ exports.default = exports.verifyWebhookSignature = exports.buildQuery = exports.parseUpgrade = exports.parseCredits = exports.isProblem = exports.APIError = exports.NetworkError = exports.ServerError = exports.NotFoundError = exports.ValidationError = exports.RateLimitError = exports.CreditsExhaustedError = exports.AuthError = exports.PuntersEdgeError = exports.VERSION = exports.DEFAULT_BASE_URL = exports.PuntersEdge = void 0;
32
+ var client_js_1 = require("./client.js");
33
+ Object.defineProperty(exports, "PuntersEdge", { enumerable: true, get: function () { return client_js_1.PuntersEdge; } });
34
+ Object.defineProperty(exports, "DEFAULT_BASE_URL", { enumerable: true, get: function () { return client_js_1.DEFAULT_BASE_URL; } });
35
+ Object.defineProperty(exports, "VERSION", { enumerable: true, get: function () { return client_js_1.VERSION; } });
36
+ var errors_js_1 = require("./errors.js");
37
+ Object.defineProperty(exports, "PuntersEdgeError", { enumerable: true, get: function () { return errors_js_1.PuntersEdgeError; } });
38
+ Object.defineProperty(exports, "AuthError", { enumerable: true, get: function () { return errors_js_1.AuthError; } });
39
+ Object.defineProperty(exports, "CreditsExhaustedError", { enumerable: true, get: function () { return errors_js_1.CreditsExhaustedError; } });
40
+ Object.defineProperty(exports, "RateLimitError", { enumerable: true, get: function () { return errors_js_1.RateLimitError; } });
41
+ Object.defineProperty(exports, "ValidationError", { enumerable: true, get: function () { return errors_js_1.ValidationError; } });
42
+ Object.defineProperty(exports, "NotFoundError", { enumerable: true, get: function () { return errors_js_1.NotFoundError; } });
43
+ Object.defineProperty(exports, "ServerError", { enumerable: true, get: function () { return errors_js_1.ServerError; } });
44
+ Object.defineProperty(exports, "NetworkError", { enumerable: true, get: function () { return errors_js_1.NetworkError; } });
45
+ Object.defineProperty(exports, "APIError", { enumerable: true, get: function () { return errors_js_1.APIError; } });
46
+ Object.defineProperty(exports, "isProblem", { enumerable: true, get: function () { return errors_js_1.isProblem; } });
47
+ var credits_js_1 = require("./credits.js");
48
+ Object.defineProperty(exports, "parseCredits", { enumerable: true, get: function () { return credits_js_1.parseCredits; } });
49
+ Object.defineProperty(exports, "parseUpgrade", { enumerable: true, get: function () { return credits_js_1.parseUpgrade; } });
50
+ var http_js_1 = require("./http.js");
51
+ Object.defineProperty(exports, "buildQuery", { enumerable: true, get: function () { return http_js_1.buildQuery; } });
52
+ var webhook_js_1 = require("./webhook.js");
53
+ Object.defineProperty(exports, "verifyWebhookSignature", { enumerable: true, get: function () { return webhook_js_1.verifyWebhookSignature; } });
54
+ // Every response shape, generated from the live OpenAPI document.
55
+ __exportStar(require("./types.generated.js"), exports);
56
+ var client_js_2 = require("./client.js");
57
+ Object.defineProperty(exports, "default", { enumerable: true, get: function () { return client_js_2.PuntersEdge; } });
58
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;;;;;;;;;;;;;;;;AAEH,yCAwBqB;AAvBnB,wGAAA,WAAW,OAAA;AACX,6GAAA,gBAAgB,OAAA;AAChB,oGAAA,OAAO,OAAA;AAuBT,yCAaqB;AAZnB,6GAAA,gBAAgB,OAAA;AAChB,sGAAA,SAAS,OAAA;AACT,kHAAA,qBAAqB,OAAA;AACrB,2GAAA,cAAc,OAAA;AACd,4GAAA,eAAe,OAAA;AACf,0GAAA,aAAa,OAAA;AACb,wGAAA,WAAW,OAAA;AACX,yGAAA,YAAY,OAAA;AACZ,qGAAA,QAAQ,OAAA;AACR,sGAAA,SAAS,OAAA;AAKX,2CAKsB;AAJpB,0GAAA,YAAY,OAAA;AACZ,0GAAA,YAAY,OAAA;AAKd,qCAAsG;AAA7F,qGAAA,UAAU,OAAA;AAEnB,2CAA0E;AAAjE,oHAAA,sBAAsB,OAAA;AAE/B,kEAAkE;AAClE,uDAAqC;AAErC,yCAAqD;AAA5C,oGAAA,WAAW,OAAW"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ // GENERATED FILE — do not edit by hand.
3
+ //
4
+ // Source: openapi.json (API version 1.0.0)
5
+ // Rebuild: python3 scripts/generate-types.py
6
+ //
7
+ // Every interface below mirrors one `components.schemas` entry of the PuntersEdge
8
+ // OpenAPI document. A field marked optional here is one the API declares optional:
9
+ // racing data is scraped from bookmakers that each publish a different subset, so
10
+ // `barrier?: number` means some books genuinely do not report a barrier, not that
11
+ // the schema is sloppy. Check before you read.
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ //# sourceMappingURL=types.generated.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.generated.js","sourceRoot":"","sources":["../../src/types.generated.ts"],"names":[],"mappings":";AAAA,wCAAwC;AACxC,EAAE;AACF,4CAA4C;AAC5C,6CAA6C;AAC7C,EAAE;AACF,kFAAkF;AAClF,mFAAmF;AACnF,kFAAkF;AAClF,kFAAkF;AAClF,+CAA+C"}
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ /**
3
+ * Verify the signature on a delivered webhook.
4
+ *
5
+ * Every delivery carries `X-Webhook-Signature: sha256=<hex>`, an HMAC-SHA256 over the **exact
6
+ * bytes of the request body**, keyed with the secret the API returned when you created the
7
+ * subscription.
8
+ *
9
+ * THE MISTAKE THIS FUNCTION EXISTS TO PREVENT. Most web frameworks parse JSON for you, so the
10
+ * obvious implementation is `JSON.stringify(req.body)` and hash that. It will never match. The
11
+ * API signs a canonical serialisation — keys sorted, no spaces — and your framework's
12
+ * re-serialisation will differ in key order or whitespace. Hash the raw body you received,
13
+ * before anything parses it:
14
+ *
15
+ * ```ts
16
+ * // Express
17
+ * app.post("/hook", express.raw({ type: "application/json" }), async (req, res) => {
18
+ * const ok = await verifyWebhookSignature({
19
+ * body: req.body, // a Buffer, unparsed
20
+ * signature: req.header("X-Webhook-Signature"),
21
+ * secret: process.env.PUNTERSEDGE_WEBHOOK_SECRET!,
22
+ * });
23
+ * if (!ok) return res.sendStatus(401);
24
+ * const event = JSON.parse(req.body.toString("utf8"));
25
+ * res.sendStatus(200); // ack fast, work later
26
+ * });
27
+ * ```
28
+ *
29
+ * ```ts
30
+ * // Cloudflare Workers / Hono / any Fetch-API runtime
31
+ * const raw = await request.text();
32
+ * const ok = await verifyWebhookSignature({
33
+ * body: raw, signature: request.headers.get("X-Webhook-Signature"), secret: SECRET,
34
+ * });
35
+ * ```
36
+ *
37
+ * Uses WebCrypto, so it runs unchanged on Node 18+, Deno, Bun, Cloudflare Workers and in the
38
+ * browser, with no dependency on `node:crypto`.
39
+ */
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.verifyWebhookSignature = verifyWebhookSignature;
42
+ function toBytes(body) {
43
+ if (typeof body === "string")
44
+ return new TextEncoder().encode(body);
45
+ if (body instanceof Uint8Array)
46
+ return body;
47
+ return new Uint8Array(body);
48
+ }
49
+ function hex(buffer) {
50
+ const bytes = new Uint8Array(buffer);
51
+ let out = "";
52
+ for (let i = 0; i < bytes.length; i++)
53
+ out += bytes[i].toString(16).padStart(2, "0");
54
+ return out;
55
+ }
56
+ /**
57
+ * Constant-time string comparison.
58
+ *
59
+ * `a === b` on hex digests leaks, through timing, how many leading characters an attacker got
60
+ * right, which turns forging a signature into 64 cheap guesses instead of one impossible one.
61
+ * Both strings are lower-cased by the caller, and length is folded into the result rather than
62
+ * short-circuiting on it.
63
+ */
64
+ function timingSafeEqual(a, b) {
65
+ let diff = a.length ^ b.length;
66
+ const n = Math.max(a.length, b.length);
67
+ for (let i = 0; i < n; i++) {
68
+ diff |= (a.charCodeAt(i) || 0) ^ (b.charCodeAt(i) || 0);
69
+ }
70
+ return diff === 0;
71
+ }
72
+ /**
73
+ * True when `signature` is a valid HMAC-SHA256 of `body` under `secret`.
74
+ *
75
+ * Returns false — never throws — for a missing, malformed or wrong signature, so a bad delivery
76
+ * is a 401 rather than a 500 in your logs.
77
+ */
78
+ async function verifyWebhookSignature(options) {
79
+ const { body, signature, secret } = options;
80
+ if (!signature || !secret)
81
+ return false;
82
+ const subtle = (options.crypto ?? globalThis.crypto)?.subtle;
83
+ if (!subtle) {
84
+ throw new Error("No WebCrypto available. Use Node 18 or later, or pass one: " +
85
+ "verifyWebhookSignature({ crypto, ... }).");
86
+ }
87
+ const provided = (signature.startsWith("sha256=") ? signature.slice(7) : signature)
88
+ .trim().toLowerCase();
89
+ if (!/^[0-9a-f]{64}$/.test(provided))
90
+ return false;
91
+ const key = await subtle.importKey("raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
92
+ const mac = await subtle.sign("HMAC", key, toBytes(body));
93
+ return timingSafeEqual(hex(mac), provided);
94
+ }
95
+ //# sourceMappingURL=webhook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../src/webhook.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;;AAiDH,wDA0BC;AA9DD,SAAS,OAAO,CAAC,IAAuC;IACtD,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACpE,IAAI,IAAI,YAAY,UAAU;QAAE,OAAO,IAAI,CAAC;IAC5C,OAAO,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC;AAC9B,CAAC;AAED,SAAS,GAAG,CAAC,MAAmB;IAC9B,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;IACrC,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACrF,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,eAAe,CAAC,CAAS,EAAE,CAAS;IAC3C,IAAI,IAAI,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/B,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IACvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3B,IAAI,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,IAAI,KAAK,CAAC,CAAC;AACpB,CAAC;AAED;;;;;GAKG;AACI,KAAK,UAAU,sBAAsB,CAAC,OAAsB;IACjE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAC5C,IAAI,CAAC,SAAS,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAExC,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAC7D,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,6DAA6D;YAC7D,0CAA0C,CAC3C,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,CAAC,SAAS,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;SAChF,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACxB,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IAEnD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,SAAS,CAChC,KAAK,EACL,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,EAChC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,EACjC,KAAK,EACL,CAAC,MAAM,CAAC,CACT,CAAC;IACF,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,IAAI,CAAiB,CAAC,CAAC;IAE1E,OAAO,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAC;AAC7C,CAAC"}