@lumifyai/sdk 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 +177 -0
- package/dist/client.d.ts +67 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +205 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +91 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +119 -0
- package/dist/errors.js.map +1 -0
- package/dist/generated/models.d.ts +316 -0
- package/dist/generated/models.d.ts.map +1 -0
- package/dist/generated/models.js +9 -0
- package/dist/generated/models.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/lumify.d.ts +32 -0
- package/dist/lumify.d.ts.map +1 -0
- package/dist/lumify.js +42 -0
- package/dist/lumify.js.map +1 -0
- package/dist/meta.d.ts +27 -0
- package/dist/meta.d.ts.map +1 -0
- package/dist/meta.js +46 -0
- package/dist/meta.js.map +1 -0
- package/dist/pagination.d.ts +41 -0
- package/dist/pagination.d.ts.map +1 -0
- package/dist/pagination.js +53 -0
- package/dist/pagination.js.map +1 -0
- package/dist/resources/agent.d.ts +39 -0
- package/dist/resources/agent.d.ts.map +1 -0
- package/dist/resources/agent.js +52 -0
- package/dist/resources/agent.js.map +1 -0
- package/dist/resources/events.d.ts +89 -0
- package/dist/resources/events.d.ts.map +1 -0
- package/dist/resources/events.js +92 -0
- package/dist/resources/events.js.map +1 -0
- package/dist/resources/players.d.ts +39 -0
- package/dist/resources/players.d.ts.map +1 -0
- package/dist/resources/players.js +51 -0
- package/dist/resources/players.js.map +1 -0
- package/dist/resources/sports.d.ts +25 -0
- package/dist/resources/sports.d.ts.map +1 -0
- package/dist/resources/sports.js +25 -0
- package/dist/resources/sports.js.map +1 -0
- package/dist/resources/teams.d.ts +29 -0
- package/dist/resources/teams.d.ts.map +1 -0
- package/dist/resources/teams.js +37 -0
- package/dist/resources/teams.js.map +1 -0
- package/dist/resources/webhooks.d.ts +35 -0
- package/dist/resources/webhooks.d.ts.map +1 -0
- package/dist/resources/webhooks.js +38 -0
- package/dist/resources/webhooks.js.map +1 -0
- package/dist/sse.d.ts +57 -0
- package/dist/sse.d.ts.map +1 -0
- package/dist/sse.js +165 -0
- package/dist/sse.js.map +1 -0
- package/dist/webhook-signature.d.ts +30 -0
- package/dist/webhook-signature.d.ts.map +1 -0
- package/dist/webhook-signature.js +82 -0
- package/dist/webhook-signature.js.map +1 -0
- package/package.json +56 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { LumifyClient } from "../client.js";
|
|
2
|
+
import type { Team, TeamsListResponse } from "../generated/models.js";
|
|
3
|
+
import { type PaginateOptions } from "../pagination.js";
|
|
4
|
+
export interface ListTeamsParams {
|
|
5
|
+
sport?: string;
|
|
6
|
+
league?: string;
|
|
7
|
+
conference?: string;
|
|
8
|
+
division?: string;
|
|
9
|
+
country?: string;
|
|
10
|
+
/** Free-text search on team name/abbreviation. */
|
|
11
|
+
q?: string;
|
|
12
|
+
active?: boolean;
|
|
13
|
+
afterId?: number;
|
|
14
|
+
/** Max 100, default 25. */
|
|
15
|
+
limit?: number;
|
|
16
|
+
}
|
|
17
|
+
export declare class TeamsResource {
|
|
18
|
+
private readonly client;
|
|
19
|
+
constructor(client: LumifyClient);
|
|
20
|
+
/** GET /v1/teams — cursor-paginated. */
|
|
21
|
+
list(params?: ListTeamsParams): Promise<TeamsListResponse>;
|
|
22
|
+
/** Async-iterate every page of `list()` for the given filters. */
|
|
23
|
+
paginate(params?: Omit<ListTeamsParams, "afterId">, options?: PaginateOptions): AsyncGenerator<TeamsListResponse, void, void>;
|
|
24
|
+
/** Async-iterate every team matching the filters, across all pages. */
|
|
25
|
+
iterate(params?: Omit<ListTeamsParams, "afterId">, options?: PaginateOptions): AsyncGenerator<unknown, void, void>;
|
|
26
|
+
/** GET /v1/teams/{id} */
|
|
27
|
+
get(teamId: number): Promise<Team>;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=teams.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"teams.d.ts","sourceRoot":"","sources":["../../src/resources/teams.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,IAAI,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AACtE,OAAO,EAA0B,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEhF,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAgBD,qBAAa,aAAa;IACZ,OAAO,CAAC,QAAQ,CAAC,MAAM;IAAnC,YAA6B,MAAM,EAAE,YAAY,EAAI;IAErD,wCAAwC;IACxC,IAAI,CAAC,MAAM,GAAE,eAAoB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAE7D;IAED,kEAAkE;IAClE,QAAQ,CAAC,MAAM,GAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAM,EAAE,OAAO,GAAE,eAAoB,iDAKpF;IAED,uEAAuE;IACvE,OAAO,CAAC,MAAM,GAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAM,EAAE,OAAO,GAAE,eAAoB,uCAKnF;IAED,yBAAyB;IACzB,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEjC;CACF"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { paginate, iterateItems } from "../pagination.js";
|
|
2
|
+
function toQuery(p) {
|
|
3
|
+
return {
|
|
4
|
+
sport: p.sport,
|
|
5
|
+
league: p.league,
|
|
6
|
+
conference: p.conference,
|
|
7
|
+
division: p.division,
|
|
8
|
+
country: p.country,
|
|
9
|
+
q: p.q,
|
|
10
|
+
active: p.active,
|
|
11
|
+
after_id: p.afterId,
|
|
12
|
+
limit: p.limit,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export class TeamsResource {
|
|
16
|
+
client;
|
|
17
|
+
constructor(client) {
|
|
18
|
+
this.client = client;
|
|
19
|
+
}
|
|
20
|
+
/** GET /v1/teams — cursor-paginated. */
|
|
21
|
+
list(params = {}) {
|
|
22
|
+
return this.client.get("/v1/teams", { query: toQuery(params) });
|
|
23
|
+
}
|
|
24
|
+
/** Async-iterate every page of `list()` for the given filters. */
|
|
25
|
+
paginate(params = {}, options = {}) {
|
|
26
|
+
return paginate((afterId, limit) => this.list({ ...params, afterId, limit }), { limit: params.limit, ...options });
|
|
27
|
+
}
|
|
28
|
+
/** Async-iterate every team matching the filters, across all pages. */
|
|
29
|
+
iterate(params = {}, options = {}) {
|
|
30
|
+
return iterateItems((afterId, limit) => this.list({ ...params, afterId, limit }), { limit: params.limit, ...options });
|
|
31
|
+
}
|
|
32
|
+
/** GET /v1/teams/{id} */
|
|
33
|
+
get(teamId) {
|
|
34
|
+
return this.client.get(`/v1/teams/${teamId}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=teams.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"teams.js","sourceRoot":"","sources":["../../src/resources/teams.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAwB,MAAM,kBAAkB,CAAC;AAgBhF,SAAS,OAAO,CAAC,CAAkB;IACjC,OAAO;QACL,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,CAAC,EAAE,CAAC,CAAC,CAAC;QACN,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,QAAQ,EAAE,CAAC,CAAC,OAAO;QACnB,KAAK,EAAE,CAAC,CAAC,KAAK;KACf,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,aAAa;IACK,MAAM;IAAnC,YAA6B,MAAoB;sBAApB,MAAM;IAAiB,CAAC;IAErD,wCAAwC;IACxC,IAAI,CAAC,MAAM,GAAoB,EAAE;QAC/B,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAoB,WAAW,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACrF,CAAC;IAED,kEAAkE;IAClE,QAAQ,CAAC,MAAM,GAAqC,EAAE,EAAE,OAAO,GAAoB,EAAE;QACnF,OAAO,QAAQ,CACb,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAC5D,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,GAAG,OAAO,EAAE,CACpC,CAAC;IACJ,CAAC;IAED,uEAAuE;IACvE,OAAO,CAAC,MAAM,GAAqC,EAAE,EAAE,OAAO,GAAoB,EAAE;QAClF,OAAO,YAAY,CACjB,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAC5D,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,GAAG,OAAO,EAAE,CACpC,CAAC;IACJ,CAAC;IAED,yBAAyB;IACzB,GAAG,CAAC,MAAc;QAChB,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAO,aAAa,MAAM,EAAE,CAAC,CAAC;IACtD,CAAC;CACF"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { LumifyClient } from "../client.js";
|
|
2
|
+
import type { WebhookCreateResponse, WebhookDeleteResponse, WebhookListResponse } from "../generated/models.js";
|
|
3
|
+
import { type VerifyWebhookOptions } from "../webhook-signature.js";
|
|
4
|
+
/** Event types a webhook subscription can fire on (see core/webhooks/delivery.py). */
|
|
5
|
+
export type WebhookEventType = "score" | "status" | "line_move" | "intelligence";
|
|
6
|
+
export interface CreateWebhookParams {
|
|
7
|
+
/** HTTPS delivery URL. Rejected if it resolves to a private/loopback address (SSRF guard). */
|
|
8
|
+
url: string;
|
|
9
|
+
/** Defaults to ["score", "status"] server-side if omitted. */
|
|
10
|
+
eventTypes?: WebhookEventType[];
|
|
11
|
+
/** Scope the subscription to one sport. */
|
|
12
|
+
sport?: string;
|
|
13
|
+
/** Scope the subscription to one event. */
|
|
14
|
+
eventId?: number;
|
|
15
|
+
}
|
|
16
|
+
export declare class WebhooksResource {
|
|
17
|
+
private readonly client;
|
|
18
|
+
constructor(client: LumifyClient);
|
|
19
|
+
/**
|
|
20
|
+
* POST /v1/webhooks. The response's `signing_secret` (`whsec_...`) is
|
|
21
|
+
* returned once — store it to verify deliveries with {@link WebhooksResource.verify}.
|
|
22
|
+
*/
|
|
23
|
+
create(params: CreateWebhookParams): Promise<WebhookCreateResponse>;
|
|
24
|
+
/** GET /v1/webhooks — the caller's subscriptions (signing secrets are not re-returned). */
|
|
25
|
+
list(): Promise<WebhookListResponse>;
|
|
26
|
+
/** DELETE /v1/webhooks/{id} */
|
|
27
|
+
delete(subscriptionId: number): Promise<WebhookDeleteResponse>;
|
|
28
|
+
/**
|
|
29
|
+
* Verify a delivery's `Lumify-Signature` header against the raw request
|
|
30
|
+
* body and this subscription's `signing_secret`. Throws
|
|
31
|
+
* `WebhookSignatureError` (imported from `@lumifyai/sdk`) if invalid.
|
|
32
|
+
*/
|
|
33
|
+
verify(signingSecret: string, signatureHeader: string, rawBody: string, options?: VerifyWebhookOptions): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=webhooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../../src/resources/webhooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EACV,qBAAqB,EACrB,qBAAqB,EACrB,mBAAmB,EACpB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAiB,KAAK,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAEnF,sFAAsF;AACtF,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,cAAc,CAAC;AAEjF,MAAM,WAAW,mBAAmB;IAClC,8FAA8F;IAC9F,GAAG,EAAE,MAAM,CAAC;IACZ,8DAA8D;IAC9D,UAAU,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAChC,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,gBAAgB;IACf,OAAO,CAAC,QAAQ,CAAC,MAAM;IAAnC,YAA6B,MAAM,EAAE,YAAY,EAAI;IAErD;;;OAGG;IACH,MAAM,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CASlE;IAED,2FAA2F;IAC3F,IAAI,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAEnC;IAED,+BAA+B;IAC/B,MAAM,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAE7D;IAED;;;;OAIG;IACH,MAAM,CACJ,aAAa,EAAE,MAAM,EACrB,eAAe,EAAE,MAAM,EACvB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAEf;CACF"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { verifyWebhook } from "../webhook-signature.js";
|
|
2
|
+
export class WebhooksResource {
|
|
3
|
+
client;
|
|
4
|
+
constructor(client) {
|
|
5
|
+
this.client = client;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* POST /v1/webhooks. The response's `signing_secret` (`whsec_...`) is
|
|
9
|
+
* returned once — store it to verify deliveries with {@link WebhooksResource.verify}.
|
|
10
|
+
*/
|
|
11
|
+
create(params) {
|
|
12
|
+
return this.client.post("/v1/webhooks", {
|
|
13
|
+
body: {
|
|
14
|
+
url: params.url,
|
|
15
|
+
event_types: params.eventTypes,
|
|
16
|
+
sport: params.sport,
|
|
17
|
+
event_id: params.eventId,
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/** GET /v1/webhooks — the caller's subscriptions (signing secrets are not re-returned). */
|
|
22
|
+
list() {
|
|
23
|
+
return this.client.get("/v1/webhooks");
|
|
24
|
+
}
|
|
25
|
+
/** DELETE /v1/webhooks/{id} */
|
|
26
|
+
delete(subscriptionId) {
|
|
27
|
+
return this.client.delete(`/v1/webhooks/${subscriptionId}`);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Verify a delivery's `Lumify-Signature` header against the raw request
|
|
31
|
+
* body and this subscription's `signing_secret`. Throws
|
|
32
|
+
* `WebhookSignatureError` (imported from `@lumifyai/sdk`) if invalid.
|
|
33
|
+
*/
|
|
34
|
+
verify(signingSecret, signatureHeader, rawBody, options) {
|
|
35
|
+
return verifyWebhook(signingSecret, signatureHeader, rawBody, options);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=webhooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhooks.js","sourceRoot":"","sources":["../../src/resources/webhooks.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,aAAa,EAA6B,MAAM,yBAAyB,CAAC;AAgBnF,MAAM,OAAO,gBAAgB;IACE,MAAM;IAAnC,YAA6B,MAAoB;sBAApB,MAAM;IAAiB,CAAC;IAErD;;;OAGG;IACH,MAAM,CAAC,MAA2B;QAChC,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAwB,cAAc,EAAE;YAC7D,IAAI,EAAE;gBACJ,GAAG,EAAE,MAAM,CAAC,GAAG;gBACf,WAAW,EAAE,MAAM,CAAC,UAAU;gBAC9B,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,QAAQ,EAAE,MAAM,CAAC,OAAO;aACzB;SACF,CAAC,CAAC;IACL,CAAC;IAED,2FAA2F;IAC3F,IAAI;QACF,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAsB,cAAc,CAAC,CAAC;IAC9D,CAAC;IAED,+BAA+B;IAC/B,MAAM,CAAC,cAAsB;QAC3B,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAwB,gBAAgB,cAAc,EAAE,CAAC,CAAC;IACrF,CAAC;IAED;;;;OAIG;IACH,MAAM,CACJ,aAAqB,EACrB,eAAuB,EACvB,OAAe,EACf,OAA8B;QAE9B,OAAO,aAAa,CAAC,aAAa,EAAE,eAAe,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IACzE,CAAC;CACF"}
|
package/dist/sse.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { LumifyClient } from "./client.js";
|
|
2
|
+
import type { ScoreResponse } from "./generated/models.js";
|
|
3
|
+
export interface SSEEvent {
|
|
4
|
+
/** SSE `event:` field. Defaults to "message" per the SSE spec if the server omits it. */
|
|
5
|
+
event: string;
|
|
6
|
+
/** Raw (still-a-string) `data:` field, joined across multiline data. */
|
|
7
|
+
data: string;
|
|
8
|
+
id?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Parse a `text/event-stream` response body into individual SSE frames.
|
|
12
|
+
* Exposed for advanced use; most callers want {@link streamScores}.
|
|
13
|
+
*
|
|
14
|
+
* Frames are separated by a blank line (`\n\n` or `\r\n\r\n`). The reader is
|
|
15
|
+
* cancelled when the consumer breaks out of the async iterator so the upstream
|
|
16
|
+
* fetch connection is released promptly.
|
|
17
|
+
*/
|
|
18
|
+
export declare function parseSSEStream(body: ReadableStream<Uint8Array>): AsyncGenerator<SSEEvent, void, void>;
|
|
19
|
+
export interface StreamScoresOptions {
|
|
20
|
+
/** Abort the stream from the caller's side (e.g. on a UI unmount). */
|
|
21
|
+
signal?: AbortSignal;
|
|
22
|
+
/**
|
|
23
|
+
* Timeout for the *initial* HTTP connect/headers only (ms). The stream itself
|
|
24
|
+
* is long-lived and is not subject to this timeout. Defaults to the client's
|
|
25
|
+
* `timeoutMs` (30s). Pass `0` to disable.
|
|
26
|
+
*/
|
|
27
|
+
connectTimeoutMs?: number;
|
|
28
|
+
}
|
|
29
|
+
export type ScoreStreamEvent = {
|
|
30
|
+
event: "score";
|
|
31
|
+
data: ScoreResponse;
|
|
32
|
+
} | {
|
|
33
|
+
event: "done";
|
|
34
|
+
data: {
|
|
35
|
+
event_id: number;
|
|
36
|
+
};
|
|
37
|
+
} | {
|
|
38
|
+
event: "error";
|
|
39
|
+
data: {
|
|
40
|
+
error: {
|
|
41
|
+
code: string;
|
|
42
|
+
message: string;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Async-iterate live score updates for an event over SSE. Emits only on
|
|
48
|
+
* change (plus a final `done`), so it's far cheaper than polling `get_live_score`.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* for await (const evt of streamScores(client, eventId)) {
|
|
52
|
+
* if (evt.event === "score") console.log(evt.data.status, evt.data.clock);
|
|
53
|
+
* if (evt.event === "done") break;
|
|
54
|
+
* }
|
|
55
|
+
*/
|
|
56
|
+
export declare function streamScores(client: LumifyClient, eventId: number, options?: StreamScoresOptions): AsyncGenerator<ScoreStreamEvent, void, void>;
|
|
57
|
+
//# sourceMappingURL=sse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../src/sse.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,MAAM,WAAW,QAAQ;IACvB,yFAAyF;IACzF,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;GAOG;AACH,wBAAuB,cAAc,CAAC,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,cAAc,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,CAoC5G;AAoBD,MAAM,WAAW,mBAAmB;IAClC,sEAAsE;IACtE,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,MAAM,gBAAgB,GACxB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,aAAa,CAAA;CAAE,GACvC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAC7C;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE;QAAE,KAAK,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAA;CAAE,CAAC;AAE3E;;;;;;;;;GASG;AACH,wBAAuB,YAAY,CACjC,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,mBAAwB,GAChC,cAAc,CAAC,gBAAgB,EAAE,IAAI,EAAE,IAAI,CAAC,CA8D9C"}
|
package/dist/sse.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// Server-Sent Events reader for GET /v1/events/{id}/stream (see api/routes/v1/stream.py).
|
|
2
|
+
// The endpoint emits named events — `score` on change, `done` when the event
|
|
3
|
+
// finishes, `error` if the event id doesn't exist — plus unnamed `: keep-alive`
|
|
4
|
+
// comment pings every ~15s. This module has no dependency on the DOM
|
|
5
|
+
// `EventSource` API (which can't set an Authorization header); it parses the
|
|
6
|
+
// wire format directly off the fetch `Response` body stream, which works
|
|
7
|
+
// identically in Node 18+ and browsers.
|
|
8
|
+
import { ConnectionError, errorFromResponse } from "./errors.js";
|
|
9
|
+
/**
|
|
10
|
+
* Parse a `text/event-stream` response body into individual SSE frames.
|
|
11
|
+
* Exposed for advanced use; most callers want {@link streamScores}.
|
|
12
|
+
*
|
|
13
|
+
* Frames are separated by a blank line (`\n\n` or `\r\n\r\n`). The reader is
|
|
14
|
+
* cancelled when the consumer breaks out of the async iterator so the upstream
|
|
15
|
+
* fetch connection is released promptly.
|
|
16
|
+
*/
|
|
17
|
+
export async function* parseSSEStream(body) {
|
|
18
|
+
const reader = body.getReader();
|
|
19
|
+
const decoder = new TextDecoder();
|
|
20
|
+
let buffer = "";
|
|
21
|
+
try {
|
|
22
|
+
while (true) {
|
|
23
|
+
const { done, value } = await reader.read();
|
|
24
|
+
if (done)
|
|
25
|
+
break;
|
|
26
|
+
buffer += decoder.decode(value, { stream: true });
|
|
27
|
+
// Normalize CRLF → LF so a single splitter handles both wire forms.
|
|
28
|
+
buffer = buffer.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
29
|
+
let sepIndex;
|
|
30
|
+
while ((sepIndex = buffer.indexOf("\n\n")) !== -1) {
|
|
31
|
+
const rawFrame = buffer.slice(0, sepIndex);
|
|
32
|
+
buffer = buffer.slice(sepIndex + 2);
|
|
33
|
+
const frame = parseFrame(rawFrame);
|
|
34
|
+
if (frame)
|
|
35
|
+
yield frame;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
// Cancel the underlying stream so breaking out of `for await` tears down
|
|
41
|
+
// the HTTP connection instead of waiting for the server's 5-minute cap.
|
|
42
|
+
try {
|
|
43
|
+
await reader.cancel();
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// already closed / cancelled
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
reader.releaseLock();
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// already released
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function parseFrame(raw) {
|
|
57
|
+
const dataLines = [];
|
|
58
|
+
let event = "message";
|
|
59
|
+
let id;
|
|
60
|
+
let isCommentOnly = true;
|
|
61
|
+
for (const line of raw.split("\n")) {
|
|
62
|
+
if (line.startsWith(":"))
|
|
63
|
+
continue; // comment / keep-alive
|
|
64
|
+
isCommentOnly = false;
|
|
65
|
+
if (line.startsWith("event:"))
|
|
66
|
+
event = line.slice("event:".length).trim();
|
|
67
|
+
else if (line.startsWith("data:"))
|
|
68
|
+
dataLines.push(line.slice("data:".length).trimStart());
|
|
69
|
+
else if (line.startsWith("id:"))
|
|
70
|
+
id = line.slice("id:".length).trim();
|
|
71
|
+
}
|
|
72
|
+
if (isCommentOnly && dataLines.length === 0)
|
|
73
|
+
return null; // pure keep-alive frame
|
|
74
|
+
return { event, data: dataLines.join("\n"), id };
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Async-iterate live score updates for an event over SSE. Emits only on
|
|
78
|
+
* change (plus a final `done`), so it's far cheaper than polling `get_live_score`.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* for await (const evt of streamScores(client, eventId)) {
|
|
82
|
+
* if (evt.event === "score") console.log(evt.data.status, evt.data.clock);
|
|
83
|
+
* if (evt.event === "done") break;
|
|
84
|
+
* }
|
|
85
|
+
*/
|
|
86
|
+
export async function* streamScores(client, eventId, options = {}) {
|
|
87
|
+
const url = client.url(`/v1/events/${eventId}/stream`);
|
|
88
|
+
// Connect-phase timeout only — once headers arrive we clear it so the
|
|
89
|
+
// long-lived SSE body isn't aborted by the client default timeout.
|
|
90
|
+
const connectTimeoutMs = options.connectTimeoutMs !== undefined ? options.connectTimeoutMs : client.requestTimeoutMs;
|
|
91
|
+
const connectController = new AbortController();
|
|
92
|
+
let connectTimer;
|
|
93
|
+
if (connectTimeoutMs > 0) {
|
|
94
|
+
connectTimer = setTimeout(() => connectController.abort(new Error("connect timeout")), connectTimeoutMs);
|
|
95
|
+
}
|
|
96
|
+
const signals = [connectController.signal];
|
|
97
|
+
if (options.signal)
|
|
98
|
+
signals.push(options.signal);
|
|
99
|
+
const signal = anySignal(signals);
|
|
100
|
+
let res;
|
|
101
|
+
try {
|
|
102
|
+
res = await client.fetchFn(url, {
|
|
103
|
+
headers: client.authHeaders("text/event-stream"),
|
|
104
|
+
signal,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
throw new ConnectionError(`SSE connect to /v1/events/${eventId}/stream failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
if (connectTimer)
|
|
112
|
+
clearTimeout(connectTimer);
|
|
113
|
+
}
|
|
114
|
+
if (!res.ok) {
|
|
115
|
+
let payload;
|
|
116
|
+
try {
|
|
117
|
+
payload = await res.json();
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// non-JSON error body — fall through with an undefined payload
|
|
121
|
+
}
|
|
122
|
+
const retryAfterHeader = parseRetryAfter(res.headers.get("retry-after"));
|
|
123
|
+
throw errorFromResponse(res.status, payload?.error, res.headers.get("x-request-id"), {
|
|
124
|
+
retryAfterHeader,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (!res.body) {
|
|
128
|
+
throw new ConnectionError(`SSE response for event ${eventId} had no body.`);
|
|
129
|
+
}
|
|
130
|
+
for await (const frame of parseSSEStream(res.body)) {
|
|
131
|
+
if (frame.event === "message" && !frame.data)
|
|
132
|
+
continue; // keep-alive with no named event
|
|
133
|
+
let parsed;
|
|
134
|
+
try {
|
|
135
|
+
parsed = frame.data ? JSON.parse(frame.data) : undefined;
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
continue; // malformed frame — skip rather than throw mid-stream
|
|
139
|
+
}
|
|
140
|
+
yield { event: frame.event, data: parsed };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function parseRetryAfter(header) {
|
|
144
|
+
if (header == null || header === "")
|
|
145
|
+
return undefined;
|
|
146
|
+
const seconds = Number(header);
|
|
147
|
+
if (!Number.isFinite(seconds) || seconds < 0)
|
|
148
|
+
return undefined;
|
|
149
|
+
return seconds;
|
|
150
|
+
}
|
|
151
|
+
function anySignal(signals) {
|
|
152
|
+
if (typeof AbortSignal !== "undefined" && "any" in AbortSignal) {
|
|
153
|
+
return AbortSignal.any(signals);
|
|
154
|
+
}
|
|
155
|
+
const controller = new AbortController();
|
|
156
|
+
for (const s of signals) {
|
|
157
|
+
if (s.aborted) {
|
|
158
|
+
controller.abort(s.reason);
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
s.addEventListener("abort", () => controller.abort(s.reason), { once: true });
|
|
162
|
+
}
|
|
163
|
+
return controller.signal;
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=sse.js.map
|
package/dist/sse.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse.js","sourceRoot":"","sources":["../src/sse.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface VerifyWebhookOptions {
|
|
2
|
+
/** Max age (seconds) a signature is accepted, to reject replayed deliveries. Default 300 (5 min). */
|
|
3
|
+
toleranceSeconds?: number;
|
|
4
|
+
/** Injectable clock for tests. */
|
|
5
|
+
now?: () => number;
|
|
6
|
+
}
|
|
7
|
+
export declare class WebhookSignatureError extends Error {
|
|
8
|
+
constructor(message: string);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Verify a webhook delivery's `Lumify-Signature` header against the raw
|
|
12
|
+
* request body and your subscription's `signing_secret`. Throws
|
|
13
|
+
* {@link WebhookSignatureError} on any failure (bad format, mismatch, stale
|
|
14
|
+
* timestamp) — treat that as "reject the delivery with a 4xx", not a crash.
|
|
15
|
+
*
|
|
16
|
+
* `body` must be the *raw, unparsed* request bytes/string — re-serializing
|
|
17
|
+
* parsed JSON can reorder keys or change whitespace and break the signature.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* app.post("/webhooks/lumify", express.raw({ type: "*\/*" }), async (req, res) => {
|
|
21
|
+
* try {
|
|
22
|
+
* await verifyWebhook(process.env.LUMIFY_WEBHOOK_SECRET!, req.header("Lumify-Signature")!, req.body.toString("utf8"));
|
|
23
|
+
* } catch {
|
|
24
|
+
* return res.status(400).end();
|
|
25
|
+
* }
|
|
26
|
+
* res.status(200).end();
|
|
27
|
+
* });
|
|
28
|
+
*/
|
|
29
|
+
export declare function verifyWebhook(signingSecret: string, signatureHeader: string, rawBody: string, options?: VerifyWebhookOptions): Promise<void>;
|
|
30
|
+
//# sourceMappingURL=webhook-signature.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-signature.d.ts","sourceRoot":"","sources":["../src/webhook-signature.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,oBAAoB;IACnC,qGAAqG;IACrG,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kCAAkC;IAClC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AA+CD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,aAAa,CACjC,aAAa,EAAE,MAAM,EACrB,eAAe,EAAE,MAAM,EACvB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,IAAI,CAAC,CAef"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Verifies the `Lumify-Signature` header webhook deliveries carry (see
|
|
2
|
+
// core/webhooks/delivery.py's `_sign`):
|
|
3
|
+
//
|
|
4
|
+
// Lumify-Signature: t=<unix_ts>,v1=<hex hmac_sha256(signing_secret, `${ts}.${body}`)>
|
|
5
|
+
//
|
|
6
|
+
// Uses Web Crypto (`crypto.subtle`), available unflagged in Node 18+ and every
|
|
7
|
+
// modern browser/edge runtime — no `node:crypto` import, so this stays portable
|
|
8
|
+
// and dependency-free.
|
|
9
|
+
export class WebhookSignatureError extends Error {
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "WebhookSignatureError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function parseSignatureHeader(header) {
|
|
16
|
+
const parts = new Map();
|
|
17
|
+
for (const segment of header.split(",")) {
|
|
18
|
+
const [key, value] = segment.split("=", 2);
|
|
19
|
+
if (key && value !== undefined)
|
|
20
|
+
parts.set(key.trim(), value.trim());
|
|
21
|
+
}
|
|
22
|
+
const t = parts.get("t");
|
|
23
|
+
const v1 = parts.get("v1");
|
|
24
|
+
if (!t || !v1) {
|
|
25
|
+
throw new WebhookSignatureError("Malformed Lumify-Signature header — expected 't=<unix_ts>,v1=<hex_hmac>'.");
|
|
26
|
+
}
|
|
27
|
+
const timestamp = Number(t);
|
|
28
|
+
if (!Number.isFinite(timestamp)) {
|
|
29
|
+
throw new WebhookSignatureError("Malformed Lumify-Signature header — 't' is not a number.");
|
|
30
|
+
}
|
|
31
|
+
return { timestamp, signature: v1 };
|
|
32
|
+
}
|
|
33
|
+
async function hmacSha256Hex(secret, payload) {
|
|
34
|
+
const enc = new TextEncoder();
|
|
35
|
+
const key = await crypto.subtle.importKey("raw", enc.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
36
|
+
const sig = await crypto.subtle.sign("HMAC", key, enc.encode(payload));
|
|
37
|
+
return Array.from(new Uint8Array(sig))
|
|
38
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
39
|
+
.join("");
|
|
40
|
+
}
|
|
41
|
+
/** Constant-time string comparison (no early exit on the first mismatched byte). */
|
|
42
|
+
function timingSafeEqual(a, b) {
|
|
43
|
+
if (a.length !== b.length)
|
|
44
|
+
return false;
|
|
45
|
+
let diff = 0;
|
|
46
|
+
for (let i = 0; i < a.length; i++) {
|
|
47
|
+
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
48
|
+
}
|
|
49
|
+
return diff === 0;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Verify a webhook delivery's `Lumify-Signature` header against the raw
|
|
53
|
+
* request body and your subscription's `signing_secret`. Throws
|
|
54
|
+
* {@link WebhookSignatureError} on any failure (bad format, mismatch, stale
|
|
55
|
+
* timestamp) — treat that as "reject the delivery with a 4xx", not a crash.
|
|
56
|
+
*
|
|
57
|
+
* `body` must be the *raw, unparsed* request bytes/string — re-serializing
|
|
58
|
+
* parsed JSON can reorder keys or change whitespace and break the signature.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* app.post("/webhooks/lumify", express.raw({ type: "*\/*" }), async (req, res) => {
|
|
62
|
+
* try {
|
|
63
|
+
* await verifyWebhook(process.env.LUMIFY_WEBHOOK_SECRET!, req.header("Lumify-Signature")!, req.body.toString("utf8"));
|
|
64
|
+
* } catch {
|
|
65
|
+
* return res.status(400).end();
|
|
66
|
+
* }
|
|
67
|
+
* res.status(200).end();
|
|
68
|
+
* });
|
|
69
|
+
*/
|
|
70
|
+
export async function verifyWebhook(signingSecret, signatureHeader, rawBody, options = {}) {
|
|
71
|
+
const { timestamp, signature } = parseSignatureHeader(signatureHeader);
|
|
72
|
+
const tolerance = options.toleranceSeconds ?? 300;
|
|
73
|
+
const now = (options.now ?? (() => Date.now() / 1000))();
|
|
74
|
+
if (Math.abs(now - timestamp) > tolerance) {
|
|
75
|
+
throw new WebhookSignatureError(`Signature timestamp is ${Math.round(Math.abs(now - timestamp))}s old, outside the ${tolerance}s tolerance (possible replay).`);
|
|
76
|
+
}
|
|
77
|
+
const expected = await hmacSha256Hex(signingSecret, `${timestamp}.${rawBody}`);
|
|
78
|
+
if (!timingSafeEqual(expected, signature)) {
|
|
79
|
+
throw new WebhookSignatureError("Signature mismatch — payload may have been tampered with, or the signing secret is wrong.");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=webhook-signature.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-signature.js","sourceRoot":"","sources":["../src/webhook-signature.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lumifyai/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Official TypeScript/JavaScript SDK for the Lumify agent-ready sports intelligence API (schedules, live scores, odds, betting splits, and AI bet intelligence).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc -p tsconfig.json",
|
|
24
|
+
"prepublishOnly": "npm run build",
|
|
25
|
+
"gen:models": "node scripts/gen-models.mjs",
|
|
26
|
+
"gen:models:check": "node scripts/gen-models.mjs --check",
|
|
27
|
+
"pretest": "npm run build",
|
|
28
|
+
"test": "node --test"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"typescript": "^7.0.0"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"lumify",
|
|
38
|
+
"sports",
|
|
39
|
+
"sports-data",
|
|
40
|
+
"odds",
|
|
41
|
+
"betting",
|
|
42
|
+
"sdk",
|
|
43
|
+
"ai-agents",
|
|
44
|
+
"sports-api"
|
|
45
|
+
],
|
|
46
|
+
"homepage": "https://lumify.ai/docs",
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/lumifyai/lumify/issues"
|
|
49
|
+
},
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "git+https://github.com/lumifyai/lumify.git",
|
|
53
|
+
"directory": "clients/lumify-sdk"
|
|
54
|
+
},
|
|
55
|
+
"license": "MIT"
|
|
56
|
+
}
|