@dofek/xert 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 Asher Cohen
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,135 @@
1
+ # @dofek/xert
2
+
3
+ TypeScript client for Xert authentication, token refresh, observed activity
4
+ paging, runtime response validation, and conversion to provider-agnostic
5
+ activity records.
6
+
7
+ Xert publishes an API reference that documents its password and refresh-token
8
+ grants, including the `xert_public` client credentials. The paginated activity
9
+ response consumed here is an observed Xert web API shape rather than the
10
+ activity-list representation in that reference. See the
11
+ [Xert API documentation](https://www.xertonline.com/API.html).
12
+
13
+ This package is not affiliated with, endorsed by, or supported by Xert or Baron
14
+ Biosystems. Undocumented endpoints and response shapes may change without
15
+ notice.
16
+
17
+ ## Requirements and installation
18
+
19
+ Requires Node.js 22.14 or newer and its built-in
20
+ [`fetch`](https://nodejs.org/api/globals.html#fetch) implementation.
21
+
22
+ ```sh
23
+ npm install @dofek/xert
24
+ ```
25
+
26
+ ## Quick start
27
+
28
+ ```ts
29
+ import { XertClient } from "@dofek/xert";
30
+ import { parseXertActivity } from "@dofek/xert/parsing";
31
+
32
+ const email = process.env.XERT_EMAIL;
33
+ const password = process.env.XERT_PASSWORD;
34
+ if (!email || !password) {
35
+ throw new Error("Set XERT_EMAIL and XERT_PASSWORD");
36
+ }
37
+
38
+ const token = await XertClient.signIn(email, password);
39
+ const client = new XertClient(token.accessToken);
40
+ const activities = await client.listActivities({
41
+ from: Math.floor(Date.parse("2026-07-01T00:00:00Z") / 1000),
42
+ });
43
+
44
+ console.log(activities.map(parseXertActivity));
45
+ ```
46
+
47
+ `from` is a Unix timestamp in seconds. `page` defaults to `0`, and `limit`
48
+ defaults to `50`.
49
+
50
+ ## Authentication and token refresh
51
+
52
+ `XertClient.signIn(email, password, fetch?, credentials?)` uses the resource
53
+ owner password grant documented by Xert. It returns:
54
+
55
+ - `accessToken`
56
+ - `refreshToken`
57
+ - `expiresAt`
58
+ - `scopes`
59
+
60
+ The library defaults to the documented `xert_public` client ID and secret. To
61
+ use different credentials, pass them explicitly:
62
+
63
+ ```ts
64
+ const token = await XertClient.signIn(
65
+ email,
66
+ password,
67
+ fetch,
68
+ { clientId: "client-id", clientSecret: "client-secret" },
69
+ );
70
+ ```
71
+
72
+ The library never reads environment variables. Applications decide how to load
73
+ configuration and must store account passwords, access tokens, and refresh
74
+ tokens as password-equivalent secrets.
75
+
76
+ Refresh before `expiresAt`:
77
+
78
+ ```ts
79
+ const refreshed = await XertClient.refreshToken(token.refreshToken!);
80
+ ```
81
+
82
+ The standalone `signInToXert` and `refreshXertToken` functions expose the same
83
+ operations. If Xert does not return a replacement refresh token,
84
+ `refreshXertToken` retains the supplied token.
85
+
86
+ ## Public API
87
+
88
+ - `new XertClient(accessToken, fetch?)`
89
+ - `XertClient.signIn(email, password, fetch?, credentials?)`
90
+ - `XertClient.refreshToken(refreshToken, fetch?, credentials?)`
91
+ - `client.listActivities({ from, page?, limit? })`
92
+ - `signInToXert(...)`
93
+ - `refreshXertToken(...)`
94
+ - `DEFAULT_XERT_CLIENT_CREDENTIALS`
95
+ - `XertAuthenticationError`
96
+ - `XertApiError`
97
+
98
+ Supported deep imports:
99
+
100
+ - `@dofek/xert/client` — authentication, refresh, and API client.
101
+ - `@dofek/xert/parsing` — sport mapping and normalized activity parsing.
102
+ - `@dofek/xert/types` — Zod schemas and TypeScript types.
103
+
104
+ All token and activity responses cross a
105
+ [Zod](https://zod.dev/) runtime-validation boundary. A response-shape change
106
+ therefore throws `ZodError` instead of returning unchecked data.
107
+
108
+ ## Errors and rate limits
109
+
110
+ Rejected authentication and refresh requests throw
111
+ `XertAuthenticationError`. Other unsuccessful activity requests throw
112
+ `XertApiError`. Both expose `statusCode` and `responseBody`.
113
+
114
+ The shared HTTP wrapper throws `ProviderRateLimitError` for `429` and
115
+ `ProviderServiceUnavailableError` for `502`, `503`, and `504`, retaining a
116
+ parsed
117
+ [`Retry-After`](https://www.rfc-editor.org/rfc/rfc9110.html#name-retry-after)
118
+ value when supplied.
119
+
120
+ If your application handles these classes directly, declare
121
+ `@dofek/provider-http` as a direct dependency:
122
+
123
+ ```ts
124
+ import {
125
+ ProviderRateLimitError,
126
+ ProviderServiceUnavailableError,
127
+ } from "@dofek/provider-http/rate-limit";
128
+ ```
129
+
130
+ ## Project
131
+
132
+ - [Source](https://github.com/Asherlc/dofek/tree/main/packages/xert-client)
133
+ - [Report an issue](https://github.com/Asherlc/dofek/issues)
134
+ - [Contribute a pull request](https://github.com/Asherlc/dofek/pulls)
135
+ - License: [MIT](./LICENSE)
@@ -0,0 +1,36 @@
1
+ import { type XertActivity, type XertActivityListOptions, type XertClientCredentials, type XertTokenSet } from "./types.ts";
2
+ export declare const XERT_API_BASE_URL = "https://www.xertonline.com";
3
+ export declare const DEFAULT_XERT_CLIENT_CREDENTIALS: Readonly<XertClientCredentials>;
4
+ export declare class XertAuthenticationError extends Error {
5
+ readonly statusCode: number;
6
+ readonly responseBody: string;
7
+ readonly operation: "sign-in" | "token refresh";
8
+ constructor(operation: "sign-in" | "token refresh", statusCode: number, responseBody: string, options?: ErrorOptions);
9
+ }
10
+ export declare class XertApiError extends Error {
11
+ readonly statusCode: number;
12
+ readonly responseBody: string;
13
+ constructor(statusCode: number, responseBody: string, options?: ErrorOptions);
14
+ }
15
+ /**
16
+ * Signs in with Xert's documented resource-owner password grant.
17
+ *
18
+ * Credentials are explicit so the library never reads process environment.
19
+ */
20
+ export declare function signInToXert(email: string, password: string, fetchFn?: typeof globalThis.fetch, credentials?: XertClientCredentials): Promise<XertTokenSet>;
21
+ /**
22
+ * Exchanges a refresh token for a new token set.
23
+ *
24
+ * Xert normally rotates refresh tokens. If it omits one, this retains the
25
+ * supplied refresh token so callers do not accidentally discard it.
26
+ */
27
+ export declare function refreshXertToken(refreshToken: string, fetchFn?: typeof globalThis.fetch, credentials?: XertClientCredentials): Promise<XertTokenSet>;
28
+ export declare class XertClient {
29
+ #private;
30
+ constructor(accessToken: string, fetchFn?: typeof globalThis.fetch);
31
+ static signIn(email: string, password: string, fetchFn?: typeof globalThis.fetch, credentials?: XertClientCredentials): Promise<XertTokenSet>;
32
+ static refreshToken(refreshToken: string, fetchFn?: typeof globalThis.fetch, credentials?: XertClientCredentials): Promise<XertTokenSet>;
33
+ listActivities(options: XertActivityListOptions): Promise<XertActivity[]>;
34
+ }
35
+ export type { XertActivity, XertActivityListOptions, XertClientCredentials, XertTokenSet, } from "./types.ts";
36
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,uBAAuB,EAE5B,KAAK,qBAAqB,EAC1B,KAAK,YAAY,EAClB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,iBAAiB,+BAA+B,CAAC;AAE9D,eAAO,MAAM,+BAA+B,EAAE,QAAQ,CAAC,qBAAqB,CAG1E,CAAC;AAYH,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,eAAe,CAAC;gBAG9C,SAAS,EAAE,SAAS,GAAG,eAAe,EACtC,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,YAAY;CAQzB;AAED,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;gBAElB,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;CAM7E;AA6CD;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,OAAO,UAAU,CAAC,KAAwB,EACnD,WAAW,GAAE,qBAAuD,GACnE,OAAO,CAAC,YAAY,CAAC,CAWvB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,OAAO,UAAU,CAAC,KAAwB,EACnD,WAAW,GAAE,qBAAuD,GACnE,OAAO,CAAC,YAAY,CAAC,CAWvB;AAED,qBAAa,UAAU;;gBAIT,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,OAAO,UAAU,CAAC,KAAwB;IAKpF,MAAM,CAAC,MAAM,CACX,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,OAAO,UAAU,CAAC,KAAwB,EACnD,WAAW,GAAE,qBAAuD,GACnE,OAAO,CAAC,YAAY,CAAC;IAIxB,MAAM,CAAC,YAAY,CACjB,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,OAAO,UAAU,CAAC,KAAwB,EACnD,WAAW,GAAE,qBAAuD,GACnE,OAAO,CAAC,YAAY,CAAC;IAIlB,cAAc,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;CAwBhF;AAED,YAAY,EACV,YAAY,EACZ,uBAAuB,EACvB,qBAAqB,EACrB,YAAY,GACb,MAAM,YAAY,CAAC"}
package/dist/client.js ADDED
@@ -0,0 +1,125 @@
1
+ import { createRateLimitAwareFetch } from "@dofek/provider-http/rate-limit";
2
+ import { z } from "zod";
3
+ import { XertActivityListSchema, } from "./types.js";
4
+ export const XERT_API_BASE_URL = "https://www.xertonline.com";
5
+ export const DEFAULT_XERT_CLIENT_CREDENTIALS = Object.freeze({
6
+ clientId: "xert_public",
7
+ clientSecret: "xert_public",
8
+ });
9
+ const DEFAULT_EXPIRES_IN_SECONDS = 365 * 24 * 60 * 60;
10
+ const XertTokenResponseSchema = z.object({
11
+ access_token: z.string(),
12
+ refresh_token: z.string().optional(),
13
+ expires_in: z.number().optional(),
14
+ token_type: z.string().optional(),
15
+ scope: z.string().optional(),
16
+ });
17
+ export class XertAuthenticationError extends Error {
18
+ statusCode;
19
+ responseBody;
20
+ operation;
21
+ constructor(operation, statusCode, responseBody, options) {
22
+ super(`Xert ${operation} failed (${statusCode}): ${responseBody}`, options);
23
+ this.name = new.target.name;
24
+ this.operation = operation;
25
+ this.statusCode = statusCode;
26
+ this.responseBody = responseBody;
27
+ }
28
+ }
29
+ export class XertApiError extends Error {
30
+ statusCode;
31
+ responseBody;
32
+ constructor(statusCode, responseBody, options) {
33
+ super(`Xert API error (${statusCode}): ${responseBody}`, options);
34
+ this.name = new.target.name;
35
+ this.statusCode = statusCode;
36
+ this.responseBody = responseBody;
37
+ }
38
+ }
39
+ function authorizationHeader(credentials) {
40
+ return `Basic ${btoa(`${credentials.clientId}:${credentials.clientSecret}`)}`;
41
+ }
42
+ function parseTokenResponse(data, fallbackRefreshToken = null) {
43
+ const parsed = XertTokenResponseSchema.parse(data);
44
+ return {
45
+ accessToken: parsed.access_token,
46
+ refreshToken: parsed.refresh_token ?? fallbackRefreshToken,
47
+ expiresAt: new Date(Date.now() + (parsed.expires_in ?? DEFAULT_EXPIRES_IN_SECONDS) * 1000),
48
+ scopes: parsed.scope ?? null,
49
+ };
50
+ }
51
+ async function requestToken(operation, body, fetchFn, credentials, fallbackRefreshToken = null) {
52
+ const rateLimitFetchFn = createRateLimitAwareFetch(fetchFn, { providerId: "xert" });
53
+ const response = await rateLimitFetchFn(`${XERT_API_BASE_URL}/oauth/token`, {
54
+ method: "POST",
55
+ headers: {
56
+ Authorization: authorizationHeader(credentials),
57
+ "Content-Type": "application/x-www-form-urlencoded",
58
+ },
59
+ body: body.toString(),
60
+ });
61
+ if (!response.ok) {
62
+ const responseBody = await response.text();
63
+ throw new XertAuthenticationError(operation, response.status, responseBody);
64
+ }
65
+ const data = await response.json();
66
+ return parseTokenResponse(data, fallbackRefreshToken);
67
+ }
68
+ /**
69
+ * Signs in with Xert's documented resource-owner password grant.
70
+ *
71
+ * Credentials are explicit so the library never reads process environment.
72
+ */
73
+ export function signInToXert(email, password, fetchFn = globalThis.fetch, credentials = DEFAULT_XERT_CLIENT_CREDENTIALS) {
74
+ return requestToken("sign-in", new URLSearchParams({
75
+ grant_type: "password",
76
+ username: email,
77
+ password,
78
+ }), fetchFn, credentials);
79
+ }
80
+ /**
81
+ * Exchanges a refresh token for a new token set.
82
+ *
83
+ * Xert normally rotates refresh tokens. If it omits one, this retains the
84
+ * supplied refresh token so callers do not accidentally discard it.
85
+ */
86
+ export function refreshXertToken(refreshToken, fetchFn = globalThis.fetch, credentials = DEFAULT_XERT_CLIENT_CREDENTIALS) {
87
+ return requestToken("token refresh", new URLSearchParams({
88
+ grant_type: "refresh_token",
89
+ refresh_token: refreshToken,
90
+ }), fetchFn, credentials, refreshToken);
91
+ }
92
+ export class XertClient {
93
+ #accessToken;
94
+ #fetchFn;
95
+ constructor(accessToken, fetchFn = globalThis.fetch) {
96
+ this.#accessToken = accessToken;
97
+ this.#fetchFn = createRateLimitAwareFetch(fetchFn, { providerId: "xert" });
98
+ }
99
+ static signIn(email, password, fetchFn = globalThis.fetch, credentials = DEFAULT_XERT_CLIENT_CREDENTIALS) {
100
+ return signInToXert(email, password, fetchFn, credentials);
101
+ }
102
+ static refreshToken(refreshToken, fetchFn = globalThis.fetch, credentials = DEFAULT_XERT_CLIENT_CREDENTIALS) {
103
+ return refreshXertToken(refreshToken, fetchFn, credentials);
104
+ }
105
+ async listActivities(options) {
106
+ const query = new URLSearchParams({
107
+ from: String(options.from),
108
+ page: String(options.page ?? 0),
109
+ limit: String(options.limit ?? 50),
110
+ });
111
+ const response = await this.#fetchFn(`${XERT_API_BASE_URL}/oauth/activity/?${query.toString()}`, {
112
+ headers: {
113
+ Authorization: `Bearer ${this.#accessToken}`,
114
+ Accept: "application/json",
115
+ },
116
+ });
117
+ if (!response.ok) {
118
+ const responseBody = await response.text();
119
+ throw new XertApiError(response.status, responseBody);
120
+ }
121
+ const data = await response.json();
122
+ return XertActivityListSchema.parse(data);
123
+ }
124
+ }
125
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { CanonicalActivityType } from "@dofek/training/training";
2
+ import type { XertActivity } from "./types.ts";
3
+ export interface ParsedXertActivity {
4
+ externalId: string;
5
+ activityType: CanonicalActivityType;
6
+ name: string;
7
+ startedAt: Date;
8
+ endedAt: Date;
9
+ raw: Record<string, unknown>;
10
+ }
11
+ export declare const XERT_SPORT_MAP: Readonly<Record<string, CanonicalActivityType>>;
12
+ export declare function mapXertSport(sport: string): CanonicalActivityType;
13
+ export declare function parseXertActivity(activity: XertActivity): ParsedXertActivity;
14
+ //# sourceMappingURL=parsing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parsing.d.ts","sourceRoot":"","sources":["../src/parsing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,qBAAqB,CAAC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,IAAI,CAAC;IAChB,OAAO,EAAE,IAAI,CAAC;IACd,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC9B;AAED,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAY1E,CAAC;AAEF,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,qBAAqB,CAEjE;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,YAAY,GAAG,kBAAkB,CAyB5E"}
@@ -0,0 +1,43 @@
1
+ export const XERT_SPORT_MAP = {
2
+ Cycling: "cycling",
3
+ Running: "running",
4
+ Swimming: "swimming",
5
+ Walking: "walking",
6
+ Hiking: "hiking",
7
+ Rowing: "rowing",
8
+ Skiing: "skiing",
9
+ "Virtual Cycling": "virtual_cycling",
10
+ "Mountain Biking": "mountain_biking",
11
+ "Trail Running": "trail_running",
12
+ "Cross Country Skiing": "cross_country_skiing",
13
+ };
14
+ export function mapXertSport(sport) {
15
+ return XERT_SPORT_MAP[sport] ?? "other";
16
+ }
17
+ export function parseXertActivity(activity) {
18
+ return {
19
+ externalId: String(activity.id),
20
+ activityType: mapXertSport(activity.sport),
21
+ name: activity.name,
22
+ startedAt: new Date(activity.startTimestamp * 1000),
23
+ endedAt: new Date(activity.endTimestamp * 1000),
24
+ raw: {
25
+ sport: activity.sport,
26
+ duration: activity.duration,
27
+ distance: activity.distance,
28
+ powerAvg: activity.power_avg,
29
+ powerMax: activity.power_max,
30
+ powerNormalized: activity.power_normalized,
31
+ heartrateAvg: activity.heartrate_avg,
32
+ heartrateMax: activity.heartrate_max,
33
+ cadenceAvg: activity.cadence_avg,
34
+ cadenceMax: activity.cadence_max,
35
+ elevationGain: activity.elevation_gain,
36
+ elevationLoss: activity.elevation_loss,
37
+ xss: activity.xss,
38
+ focus: activity.focus,
39
+ difficulty: activity.difficulty,
40
+ },
41
+ };
42
+ }
43
+ //# sourceMappingURL=parsing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parsing.js","sourceRoot":"","sources":["../src/parsing.ts"],"names":[],"mappings":"AAYA,MAAM,CAAC,MAAM,cAAc,GAAoD;IAC7E,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;IAClB,QAAQ,EAAE,UAAU;IACpB,OAAO,EAAE,SAAS;IAClB,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,QAAQ;IAChB,iBAAiB,EAAE,iBAAiB;IACpC,iBAAiB,EAAE,iBAAiB;IACpC,eAAe,EAAE,eAAe;IAChC,sBAAsB,EAAE,sBAAsB;CAC/C,CAAC;AAEF,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAO,cAAc,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,QAAsB;IACtD,OAAO;QACL,UAAU,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC/B,YAAY,EAAE,YAAY,CAAC,QAAQ,CAAC,KAAK,CAAC;QAC1C,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,SAAS,EAAE,IAAI,IAAI,CAAC,QAAQ,CAAC,cAAc,GAAG,IAAI,CAAC;QACnD,OAAO,EAAE,IAAI,IAAI,CAAC,QAAQ,CAAC,YAAY,GAAG,IAAI,CAAC;QAC/C,GAAG,EAAE;YACH,KAAK,EAAE,QAAQ,CAAC,KAAK;YACrB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;YAC3B,QAAQ,EAAE,QAAQ,CAAC,QAAQ;YAC3B,QAAQ,EAAE,QAAQ,CAAC,SAAS;YAC5B,QAAQ,EAAE,QAAQ,CAAC,SAAS;YAC5B,eAAe,EAAE,QAAQ,CAAC,gBAAgB;YAC1C,YAAY,EAAE,QAAQ,CAAC,aAAa;YACpC,YAAY,EAAE,QAAQ,CAAC,aAAa;YACpC,UAAU,EAAE,QAAQ,CAAC,WAAW;YAChC,UAAU,EAAE,QAAQ,CAAC,WAAW;YAChC,aAAa,EAAE,QAAQ,CAAC,cAAc;YACtC,aAAa,EAAE,QAAQ,CAAC,cAAc;YACtC,GAAG,EAAE,QAAQ,CAAC,GAAG;YACjB,KAAK,EAAE,QAAQ,CAAC,KAAK;YACrB,UAAU,EAAE,QAAQ,CAAC,UAAU;SAChC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,63 @@
1
+ import { z } from "zod";
2
+ export declare const XertActivitySchema: z.ZodObject<{
3
+ id: z.ZodNumber;
4
+ name: z.ZodString;
5
+ sport: z.ZodString;
6
+ startTimestamp: z.ZodNumber;
7
+ endTimestamp: z.ZodNumber;
8
+ duration: z.ZodNumber;
9
+ distance: z.ZodNumber;
10
+ power_avg: z.ZodNumber;
11
+ power_max: z.ZodNumber;
12
+ power_normalized: z.ZodNumber;
13
+ heartrate_avg: z.ZodNumber;
14
+ heartrate_max: z.ZodNumber;
15
+ cadence_avg: z.ZodNumber;
16
+ cadence_max: z.ZodNumber;
17
+ elevation_gain: z.ZodNumber;
18
+ elevation_loss: z.ZodNumber;
19
+ xss: z.ZodNumber;
20
+ focus: z.ZodNumber;
21
+ difficulty: z.ZodNumber;
22
+ }, z.core.$strip>;
23
+ export declare const XertActivityListSchema: z.ZodArray<z.ZodObject<{
24
+ id: z.ZodNumber;
25
+ name: z.ZodString;
26
+ sport: z.ZodString;
27
+ startTimestamp: z.ZodNumber;
28
+ endTimestamp: z.ZodNumber;
29
+ duration: z.ZodNumber;
30
+ distance: z.ZodNumber;
31
+ power_avg: z.ZodNumber;
32
+ power_max: z.ZodNumber;
33
+ power_normalized: z.ZodNumber;
34
+ heartrate_avg: z.ZodNumber;
35
+ heartrate_max: z.ZodNumber;
36
+ cadence_avg: z.ZodNumber;
37
+ cadence_max: z.ZodNumber;
38
+ elevation_gain: z.ZodNumber;
39
+ elevation_loss: z.ZodNumber;
40
+ xss: z.ZodNumber;
41
+ focus: z.ZodNumber;
42
+ difficulty: z.ZodNumber;
43
+ }, z.core.$strip>>;
44
+ export type XertActivity = z.infer<typeof XertActivitySchema>;
45
+ export interface XertClientCredentials {
46
+ clientId: string;
47
+ clientSecret: string;
48
+ }
49
+ export interface XertTokenSet {
50
+ accessToken: string;
51
+ refreshToken: string | null;
52
+ expiresAt: Date;
53
+ scopes: string | null;
54
+ }
55
+ export interface XertActivityListOptions {
56
+ /** Earliest activity start time, expressed as Unix seconds. */
57
+ from: number;
58
+ /** Zero-based page number. */
59
+ page?: number;
60
+ /** Number of activities requested per page. */
61
+ limit?: number;
62
+ }
63
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;iBAoB7B,CAAC;AAEH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;kBAA8B,CAAC;AAElE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,IAAI,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,uBAAuB;IACtC,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB"}
package/dist/types.js ADDED
@@ -0,0 +1,24 @@
1
+ import { z } from "zod";
2
+ export const XertActivitySchema = z.object({
3
+ id: z.number(),
4
+ name: z.string(),
5
+ sport: z.string(),
6
+ startTimestamp: z.number(),
7
+ endTimestamp: z.number(),
8
+ duration: z.number(),
9
+ distance: z.number(),
10
+ power_avg: z.number(),
11
+ power_max: z.number(),
12
+ power_normalized: z.number(),
13
+ heartrate_avg: z.number(),
14
+ heartrate_max: z.number(),
15
+ cadence_avg: z.number(),
16
+ cadence_max: z.number(),
17
+ elevation_gain: z.number(),
18
+ elevation_loss: z.number(),
19
+ xss: z.number(),
20
+ focus: z.number(),
21
+ difficulty: z.number(),
22
+ });
23
+ export const XertActivityListSchema = z.array(XertActivitySchema);
24
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IACd,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE;IAC1B,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE;IACxB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;IACrB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;IACrB,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE;IAC5B,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE;IACzB,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE;IACzB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE;IAC1B,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE;IAC1B,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;IACf,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;CACvB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@dofek/xert",
3
+ "version": "0.1.0",
4
+ "description": "Unofficial Xert API client with password authentication, token refresh, and activity parsing",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/Asherlc/dofek.git",
10
+ "directory": "packages/xert-client"
11
+ },
12
+ "homepage": "https://github.com/Asherlc/dofek/tree/main/packages/xert-client#readme",
13
+ "bugs": "https://github.com/Asherlc/dofek/issues",
14
+ "keywords": [
15
+ "xert",
16
+ "cycling",
17
+ "api-client",
18
+ "oauth",
19
+ "unofficial",
20
+ "typescript"
21
+ ],
22
+ "engines": {
23
+ "node": ">=22.14.0"
24
+ },
25
+ "sideEffects": false,
26
+ "files": [
27
+ "dist",
28
+ "README.md",
29
+ "LICENSE"
30
+ ],
31
+ "exports": {
32
+ ".": {
33
+ "types": "./dist/client.d.ts",
34
+ "import": "./dist/client.js"
35
+ },
36
+ "./*": {
37
+ "types": "./dist/*.d.ts",
38
+ "import": "./dist/*.js"
39
+ }
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "scripts": {
45
+ "test": "vitest run",
46
+ "test:watch": "vitest --watch",
47
+ "build": "tsc",
48
+ "prepack": "pnpm run build",
49
+ "lint": "biome check .",
50
+ "typecheck": "tsc --noEmit"
51
+ },
52
+ "dependencies": {
53
+ "@dofek/provider-http": "0.1.0",
54
+ "@dofek/training": "0.1.0",
55
+ "zod": "4.4.3"
56
+ },
57
+ "devDependencies": {
58
+ "typescript": "6.0.3",
59
+ "vitest": "3.2.6"
60
+ },
61
+ "gitHead": "ac95258c753c7e75b57da7ae47426d22b78e8c01"
62
+ }