vairified 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +314 -250
- package/dist/index.cjs +950 -802
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1016 -767
- package/dist/index.d.ts +1016 -767
- package/dist/index.js +938 -796
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
|
-
<h1 align="center">Vairified
|
|
1
|
+
<h1 align="center">Vairified TypeScript SDK</h1>
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<strong>Official TypeScript/JavaScript SDK for the Vairified Partner API</strong><br>
|
|
5
|
-
|
|
5
|
+
Multi-sport player ratings, search, and bulk match submission
|
|
6
6
|
</p>
|
|
7
7
|
|
|
8
8
|
<p align="center">
|
|
9
9
|
<a href="https://github.com/Vairified/vairified.js/actions/workflows/ci.yml"><img src="https://github.com/Vairified/vairified.js/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI"></a>
|
|
10
10
|
<a href="https://www.npmjs.com/package/vairified"><img src="https://img.shields.io/npm/v/vairified.svg" alt="npm"></a>
|
|
11
|
+
<img src="https://img.shields.io/badge/node-%3E%3D24-blue.svg" alt="Node.js 24+">
|
|
12
|
+
<img src="https://img.shields.io/badge/deps-0-green.svg" alt="Zero dependencies">
|
|
11
13
|
</p>
|
|
12
14
|
|
|
13
15
|
---
|
|
14
16
|
|
|
15
|
-
TypeScript
|
|
17
|
+
Async-first TypeScript SDK for the [Vairified](https://vairified.com) Partner API. Built on
|
|
18
|
+
native `fetch` with **zero runtime dependencies**. Sub-resource layout, auto-paginating search,
|
|
19
|
+
n-team × n-game match submission, and `await using` lifecycle support on Node 24+.
|
|
16
20
|
|
|
17
21
|
## Installation
|
|
18
22
|
|
|
@@ -20,7 +24,7 @@ TypeScript/JavaScript SDK for integrating with the [Vairified](https://vairified
|
|
|
20
24
|
npm install vairified
|
|
21
25
|
```
|
|
22
26
|
|
|
23
|
-
Or with other package
|
|
27
|
+
Or with any other package manager:
|
|
24
28
|
|
|
25
29
|
```bash
|
|
26
30
|
yarn add vairified
|
|
@@ -30,353 +34,412 @@ bun add vairified
|
|
|
30
34
|
|
|
31
35
|
## Quick Start
|
|
32
36
|
|
|
33
|
-
```
|
|
37
|
+
```ts
|
|
34
38
|
import { Vairified } from 'vairified';
|
|
35
39
|
|
|
36
|
-
|
|
40
|
+
await using client = new Vairified({ apiKey: 'vair_pk_xxx' });
|
|
37
41
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
console.log(`${member.name}: ${member.rating}`);
|
|
41
|
-
console.log(`Verified: ${member.isVairified}`);
|
|
42
|
-
console.log(`Best rating: ${member.ratingSplits.best}`);
|
|
42
|
+
const member = await client.members.get('vair_mem_xxx');
|
|
43
|
+
console.log(member.name, 'rated', member.ratingFor('pickleball'));
|
|
43
44
|
```
|
|
44
45
|
|
|
45
|
-
|
|
46
|
+
`await using` (TypeScript 5.2+, Node 20+) closes the client deterministically when the block
|
|
47
|
+
exits. If you can't use it, call `await client.close()` manually.
|
|
46
48
|
|
|
47
|
-
|
|
49
|
+
## Sub-resources
|
|
48
50
|
|
|
49
|
-
|
|
50
|
-
const client = new Vairified({ apiKey: 'vair_pk_xxx' });
|
|
51
|
+
Every operation lives on a sub-resource that mirrors the REST path:
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
| Sub-resource | Operations |
|
|
54
|
+
|-------------------------|------------------------------------------------------------------|
|
|
55
|
+
| `client.members` | `get`, `getBulk`, `search`, `find`, `ratingUpdates` |
|
|
56
|
+
| `client.matches` | `submit`, `tournamentImport`, `testWebhook` |
|
|
57
|
+
| `client.oauth` | `authorize`, `exchangeToken`, `refresh`, `revoke` |
|
|
58
|
+
| `client.webhooks` | `deliveries` |
|
|
59
|
+
| `client.leaderboard` | `list`, `rank`, `categories` |
|
|
60
|
+
| `client.usage()` | Rate-limit + request-count stats |
|
|
61
|
+
|
|
62
|
+
## Members
|
|
63
|
+
|
|
64
|
+
### Get a connected member
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
const member = await client.members.get('vair_mem_xxx');
|
|
68
|
+
|
|
69
|
+
console.log(member.name); // Full name
|
|
70
|
+
console.log(member.displayName); // "Mike B."
|
|
71
|
+
console.log(member.ratingFor('pickleball')); // 3.915
|
|
72
|
+
console.log(member.status.isVairified); // true
|
|
73
|
+
|
|
74
|
+
// Dict-like access to rating splits for a specific sport
|
|
75
|
+
const pb = member.sport.get('pickleball');
|
|
76
|
+
if (pb) {
|
|
77
|
+
console.log(pb.rating, pb.abbr); // 3.915 VO
|
|
78
|
+
console.log(pb.get('overall-open')?.rating); // 3.915
|
|
79
|
+
console.log(pb.has('singles-open')); // true
|
|
80
|
+
for (const [key, split] of pb) {
|
|
81
|
+
console.log(key, split.rating);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Bulk member lookup
|
|
87
|
+
|
|
88
|
+
Fetch up to 100 members in one call by their integer member IDs:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
const members = await client.members.getBulk([4873327, 4873328, 4873329]);
|
|
92
|
+
for (const m of members) {
|
|
93
|
+
console.log(m.name, m.ratingFor('pickleball'));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Filter to a specific sport
|
|
97
|
+
const pb = await client.members.getBulk([4873327], { sport: 'pickleball' });
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Unknown IDs are silently omitted — the returned array may be shorter than the input.
|
|
101
|
+
|
|
102
|
+
### Filter ratings to specific sports
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
// Just pickleball
|
|
106
|
+
const member = await client.members.get('vair_mem_xxx', { sport: 'pickleball' });
|
|
107
|
+
|
|
108
|
+
// Multiple sports
|
|
109
|
+
const member2 = await client.members.get('vair_mem_xxx', {
|
|
110
|
+
sport: ['pickleball', 'padel'],
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Auto-paginating search
|
|
115
|
+
|
|
116
|
+
`search()` is an async generator — iterate directly with `for await`. Pages are fetched
|
|
117
|
+
lazily, so memory usage stays bounded regardless of result count.
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
for await (const member of client.members.search({
|
|
54
121
|
city: 'Austin',
|
|
55
122
|
state: 'TX',
|
|
56
123
|
ratingMin: 3.5,
|
|
57
124
|
ratingMax: 4.5,
|
|
58
125
|
vairifiedOnly: true,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
// Iterate over results
|
|
63
|
-
for (const player of results) {
|
|
64
|
-
console.log(`${player.name}: ${player.rating}`);
|
|
126
|
+
})) {
|
|
127
|
+
console.log(member.name, member.ratingFor('pickleball'));
|
|
65
128
|
}
|
|
66
129
|
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
130
|
+
// Cap with maxResults, or break out early
|
|
131
|
+
const top20: Member[] = [];
|
|
132
|
+
for await (const m of client.members.search({ name: 'Smith', maxResults: 20 })) {
|
|
133
|
+
top20.push(m);
|
|
71
134
|
}
|
|
72
135
|
```
|
|
73
136
|
|
|
74
|
-
### Find
|
|
137
|
+
### Find by name (first hit only)
|
|
75
138
|
|
|
76
|
-
```
|
|
77
|
-
const
|
|
78
|
-
if (
|
|
79
|
-
console.log(
|
|
139
|
+
```ts
|
|
140
|
+
const mike = await client.members.find('Mike Barker');
|
|
141
|
+
if (mike) {
|
|
142
|
+
console.log(mike.ratingFor('pickleball'));
|
|
80
143
|
}
|
|
81
144
|
```
|
|
82
145
|
|
|
83
|
-
###
|
|
146
|
+
### Rating change notifications
|
|
84
147
|
|
|
85
|
-
```
|
|
86
|
-
|
|
148
|
+
```ts
|
|
149
|
+
const updates = await client.members.ratingUpdates();
|
|
150
|
+
for (const update of updates) {
|
|
151
|
+
const arrow = update.improved ? '↑' : '↓';
|
|
152
|
+
console.log(`${update.displayName} ${arrow} delta=${update.delta?.toFixed(3)}`);
|
|
153
|
+
}
|
|
154
|
+
```
|
|
87
155
|
|
|
88
|
-
|
|
156
|
+
## Match Submission
|
|
89
157
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
158
|
+
Matches are submitted as a `MatchBatch` — defaults at the batch level apply to every
|
|
159
|
+
match unless overridden. The shape is n-team × n-game, so singles, doubles, and
|
|
160
|
+
round-robin all go through the same path.
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
const result = await client.matches.submit({
|
|
164
|
+
sport: 'pickleball',
|
|
165
|
+
winScore: 11,
|
|
166
|
+
winBy: 2,
|
|
93
167
|
bracket: '4.0 Doubles',
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
168
|
+
event: 'Weekly League',
|
|
169
|
+
matchDate: '2026-04-11T14:00:00Z',
|
|
170
|
+
matches: [
|
|
171
|
+
{
|
|
172
|
+
identifier: 'm1',
|
|
173
|
+
teams: [
|
|
174
|
+
['vair_mem_aaa', 'vair_mem_bbb'],
|
|
175
|
+
['vair_mem_ccc', 'vair_mem_ddd'],
|
|
176
|
+
],
|
|
177
|
+
games: [{ scores: [11, 8] }, { scores: [11, 5] }],
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
identifier: 'm2',
|
|
181
|
+
teams: [['vair_mem_eee'], ['vair_mem_fff']], // singles
|
|
182
|
+
games: [{ scores: [11, 9] }, { scores: [11, 7] }],
|
|
183
|
+
},
|
|
184
|
+
],
|
|
98
185
|
});
|
|
99
186
|
|
|
100
|
-
const result = await client.submitMatch(match);
|
|
101
187
|
if (result.ok) {
|
|
102
|
-
console.log(`Submitted ${result.numGames} games`);
|
|
188
|
+
console.log(`Submitted ${result.numGames} games in ${result.numMatches} matches`);
|
|
103
189
|
}
|
|
190
|
+
```
|
|
104
191
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
192
|
+
Set `dryRun: true` on the batch to validate without persisting — your API key must have
|
|
193
|
+
the `key:dry-run` scope.
|
|
194
|
+
|
|
195
|
+
### Tournament import
|
|
196
|
+
|
|
197
|
+
Import historical tournament results with automatic player matching. Unmatched players
|
|
198
|
+
become ghost accounts that can be claimed later.
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
const result = await client.matches.tournamentImport({
|
|
202
|
+
tournamentName: 'Austin Open 2026',
|
|
203
|
+
sport: 'pickleball',
|
|
204
|
+
winScore: 11,
|
|
205
|
+
winBy: 2,
|
|
206
|
+
matches: [
|
|
207
|
+
{
|
|
208
|
+
identifier: 'USAP-R1-M1',
|
|
209
|
+
event: 'Austin Open',
|
|
210
|
+
bracket: "Men's Pro Doubles",
|
|
211
|
+
format: 'DOUBLES',
|
|
212
|
+
matchDate: '2026-04-11T10:00:00Z',
|
|
213
|
+
teamA: {
|
|
214
|
+
player1: { firstName: 'Ben', lastName: 'Johns' },
|
|
215
|
+
player2: { firstName: 'Matt', lastName: 'Wright' },
|
|
216
|
+
game1: 11, game2: 11,
|
|
217
|
+
},
|
|
218
|
+
teamB: {
|
|
219
|
+
player1: { firstName: 'JW', lastName: 'Johnson' },
|
|
220
|
+
player2: { firstName: 'Dylan', lastName: 'Frazier' },
|
|
221
|
+
game1: 7, game2: 9,
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
],
|
|
113
225
|
});
|
|
114
|
-
|
|
226
|
+
console.log(`Imported ${result.matchesImported} matches, ${result.ghostPlayersCreated} ghosts`);
|
|
115
227
|
```
|
|
116
228
|
|
|
117
|
-
|
|
229
|
+
## Webhook Deliveries
|
|
118
230
|
|
|
119
|
-
|
|
120
|
-
// First, look up members to subscribe to their updates
|
|
121
|
-
await client.getMember('user_1');
|
|
122
|
-
await client.getMember('user_2');
|
|
231
|
+
Inspect recent webhook delivery attempts for your app:
|
|
123
232
|
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
for (const
|
|
127
|
-
|
|
128
|
-
console.log(`${update.memberId} ${direction}: ${update.previousRating} -> ${update.newRating}`);
|
|
129
|
-
|
|
130
|
-
// Get the full member profile
|
|
131
|
-
const member = await update.getMember();
|
|
233
|
+
```ts
|
|
234
|
+
const result = await client.webhooks.deliveries({ status: 'failed', limit: 10 });
|
|
235
|
+
for (const d of result.deliveries) {
|
|
236
|
+
console.log(d.event, d.statusCode, d.errorMessage);
|
|
132
237
|
}
|
|
133
|
-
```
|
|
134
238
|
|
|
135
|
-
|
|
239
|
+
// Filter by event type
|
|
240
|
+
const ratingEvents = await client.webhooks.deliveries({ event: 'rating.updated' });
|
|
241
|
+
console.log(`${ratingEvents.total} total rating.updated deliveries`);
|
|
242
|
+
```
|
|
136
243
|
|
|
137
|
-
|
|
244
|
+
## OAuth Connect Flow
|
|
138
245
|
|
|
139
|
-
```
|
|
140
|
-
import { Vairified,
|
|
246
|
+
```ts
|
|
247
|
+
import { Vairified, OAuthError, generateState } from 'vairified';
|
|
141
248
|
|
|
142
|
-
|
|
249
|
+
await using client = new Vairified({ apiKey: 'vair_pk_xxx' });
|
|
143
250
|
|
|
144
|
-
// Step 1
|
|
145
|
-
const state = generateState();
|
|
146
|
-
const auth = await client.
|
|
147
|
-
'https://myapp.com/oauth/callback',
|
|
148
|
-
['profile:read', 'rating:read', 'match:submit'],
|
|
251
|
+
// Step 1 — start authorization
|
|
252
|
+
const state = generateState();
|
|
253
|
+
const auth = await client.oauth.authorize({
|
|
254
|
+
redirectUri: 'https://myapp.com/oauth/callback',
|
|
255
|
+
scopes: ['user:profile:read', 'user:rating:read', 'user:match:submit'],
|
|
149
256
|
state,
|
|
150
|
-
);
|
|
151
|
-
|
|
257
|
+
});
|
|
152
258
|
// Redirect user to auth.authorizationUrl
|
|
153
|
-
window.location.href = auth.authorizationUrl;
|
|
154
|
-
```
|
|
155
259
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
const tokens = await client.exchangeToken(code, 'https://myapp.com/oauth/callback');
|
|
163
|
-
|
|
164
|
-
// Store tokens securely
|
|
165
|
-
const { playerId, accessToken, refreshToken } = tokens;
|
|
166
|
-
|
|
167
|
-
// Now you can access the player's data
|
|
168
|
-
const member = await client.getMember(playerId);
|
|
169
|
-
console.log(`Connected: ${member.name} (${member.rating})`);
|
|
170
|
-
```
|
|
260
|
+
// Step 2 — exchange the callback code
|
|
261
|
+
const tokens = await client.oauth.exchangeToken({
|
|
262
|
+
code: 'code-from-callback',
|
|
263
|
+
redirectUri: 'https://myapp.com/oauth/callback',
|
|
264
|
+
});
|
|
265
|
+
const { accessToken, refreshToken, playerId } = tokens;
|
|
171
266
|
|
|
172
|
-
|
|
173
|
-
// Step 3: Refresh expired tokens
|
|
267
|
+
// Step 3 — refresh when the access token expires
|
|
174
268
|
try {
|
|
175
|
-
const newTokens = await client.
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
// Token revoked, user needs to re-authorize
|
|
269
|
+
const newTokens = await client.oauth.refresh(refreshToken!);
|
|
270
|
+
} catch (err) {
|
|
271
|
+
if (err instanceof OAuthError && err.errorCode === 'invalid_grant') {
|
|
272
|
+
// User must re-authorize
|
|
180
273
|
}
|
|
181
274
|
}
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
### Available OAuth Scopes
|
|
185
|
-
|
|
186
|
-
| Scope | Description |
|
|
187
|
-
|-------|-------------|
|
|
188
|
-
| `profile:read` | Name, location, verification status |
|
|
189
|
-
| `profile:email` | Email address |
|
|
190
|
-
| `rating:read` | Current rating and rating splits |
|
|
191
|
-
| `rating:history` | Complete rating history |
|
|
192
|
-
| `match:submit` | Submit matches on behalf of user |
|
|
193
|
-
| `webhook:subscribe` | Rating change notifications |
|
|
194
275
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
```typescript
|
|
198
|
-
await client.revokeConnection('vair_mem_xxx');
|
|
276
|
+
// Step 4 — revoke the connection
|
|
277
|
+
await client.oauth.revoke(playerId);
|
|
199
278
|
```
|
|
200
279
|
|
|
201
|
-
|
|
280
|
+
`OAuthScope` is a string literal union, so your editor will catch typos:
|
|
202
281
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
```typescript
|
|
206
|
-
player.id // UUID or member ID
|
|
207
|
-
player.memberId // Legacy member ID
|
|
208
|
-
player.name // "John Smith"
|
|
209
|
-
player.firstName // "John"
|
|
210
|
-
player.lastName // "Smith"
|
|
211
|
-
player.rating // 4.25
|
|
212
|
-
player.isVairified // true/false
|
|
213
|
-
player.ratingSplits // RatingSplits object
|
|
214
|
-
player.city // "Austin"
|
|
215
|
-
player.state // "TX"
|
|
216
|
-
player.verifiedRating // Best verified rating
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
### Member (extends Player)
|
|
282
|
+
```ts
|
|
283
|
+
import type { OAuthScope } from 'vairified';
|
|
220
284
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
await member.refresh() // Refresh data from API
|
|
285
|
+
const scopes: OAuthScope[] = ['user:profile:read', 'user:rating:read']; // ok
|
|
286
|
+
const bad: OAuthScope[] = ['user:profile:read', 'rating']; // type error
|
|
224
287
|
```
|
|
225
288
|
|
|
226
|
-
###
|
|
289
|
+
### Available scopes
|
|
227
290
|
|
|
228
|
-
|
|
291
|
+
| Scope | Description |
|
|
292
|
+
|---------------------------|------------------------------------------------|
|
|
293
|
+
| `user:profile:read` | Name, location, verification status |
|
|
294
|
+
| `user:profile:email` | Email address |
|
|
295
|
+
| `user:rating:read` | Current rating and rating splits |
|
|
296
|
+
| `user:rating:history` | Complete rating history |
|
|
297
|
+
| `user:match:submit` | Submit matches on behalf of user |
|
|
298
|
+
| `user:webhook:subscribe` | Rating change notifications |
|
|
229
299
|
|
|
230
|
-
|
|
231
|
-
const splits = member.ratingSplits;
|
|
232
|
-
splits.open // Open division rating
|
|
233
|
-
splits.gender // Same-gender doubles rating
|
|
234
|
-
splits.mixed // Mixed doubles rating
|
|
235
|
-
splits.recreational // Recreational rating
|
|
236
|
-
splits.singles // Singles rating
|
|
237
|
-
splits.best // Best available rating
|
|
238
|
-
splits.get('50_and_up') // Age bracket rating
|
|
239
|
-
```
|
|
300
|
+
## Leaderboards
|
|
240
301
|
|
|
241
|
-
|
|
302
|
+
```ts
|
|
303
|
+
// Global leaderboard
|
|
304
|
+
const lb = await client.leaderboard.list();
|
|
242
305
|
|
|
243
|
-
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
scores: [[11, 9], [11, 7]], // Game scores
|
|
251
|
-
location: 'Austin Club', // Optional
|
|
252
|
-
matchType: 'SIDEOUT', // Default: "SIDEOUT"
|
|
253
|
-
source: 'PARTNER', // Default: "PARTNER"
|
|
306
|
+
// Texas singles, verified only
|
|
307
|
+
const tx = await client.leaderboard.list({
|
|
308
|
+
category: 'singles',
|
|
309
|
+
scope: 'state',
|
|
310
|
+
state: 'TX',
|
|
311
|
+
verifiedOnly: true,
|
|
312
|
+
limit: 50,
|
|
254
313
|
});
|
|
255
314
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
### MatchResult
|
|
263
|
-
|
|
264
|
-
```typescript
|
|
265
|
-
const result = await client.submitMatches([match1, match2]);
|
|
266
|
-
result.success // true/false
|
|
267
|
-
result.numMatches // Number processed
|
|
268
|
-
result.numGames // Games recorded
|
|
269
|
-
result.dryRun // true if validation only
|
|
270
|
-
result.message // Human-readable message
|
|
271
|
-
result.errors // Array of errors
|
|
272
|
-
result.ok // true if successful
|
|
273
|
-
```
|
|
315
|
+
// A specific player's rank with 5 players on either side
|
|
316
|
+
const rank = await client.leaderboard.rank('vair_mem_xxx', {
|
|
317
|
+
category: 'doubles',
|
|
318
|
+
contextSize: 5,
|
|
319
|
+
});
|
|
274
320
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
```typescript
|
|
278
|
-
results.players // Array of Player objects
|
|
279
|
-
results.total // Total matching players
|
|
280
|
-
results.page // Current page
|
|
281
|
-
results.pages // Total pages
|
|
282
|
-
results.hasMore // More pages available
|
|
283
|
-
await results.nextPage() // Get next page
|
|
284
|
-
results.length // Players on current page
|
|
285
|
-
results.at(0) // Get by index
|
|
286
|
-
for (const p of results) { } // Iterable
|
|
321
|
+
// Available categories, brackets, scopes
|
|
322
|
+
const categories = await client.leaderboard.categories();
|
|
287
323
|
```
|
|
288
324
|
|
|
289
325
|
## Configuration
|
|
290
326
|
|
|
291
|
-
```
|
|
327
|
+
```ts
|
|
292
328
|
import { Vairified } from 'vairified';
|
|
293
329
|
|
|
294
|
-
//
|
|
295
|
-
const client = new Vairified({ apiKey: 'vair_pk_xxx' });
|
|
330
|
+
// Environment preset
|
|
331
|
+
const client = new Vairified({ apiKey: 'vair_pk_xxx', env: 'production' }); // default
|
|
332
|
+
const staging = new Vairified({ apiKey: 'vair_pk_xxx', env: 'staging' });
|
|
333
|
+
const local = new Vairified({ apiKey: 'vair_pk_xxx', env: 'local' });
|
|
296
334
|
|
|
297
|
-
//
|
|
298
|
-
const
|
|
335
|
+
// Custom base URL (overrides env)
|
|
336
|
+
const custom = new Vairified({
|
|
337
|
+
apiKey: 'vair_pk_xxx',
|
|
338
|
+
baseUrl: 'http://localhost:3001/api/v1',
|
|
339
|
+
timeoutMs: 30_000,
|
|
340
|
+
});
|
|
299
341
|
|
|
300
|
-
//
|
|
301
|
-
const
|
|
342
|
+
// Inject a custom fetch (test shims, non-Node environments)
|
|
343
|
+
const withFetch = new Vairified({
|
|
302
344
|
apiKey: 'vair_pk_xxx',
|
|
303
|
-
|
|
345
|
+
fetch: customFetchImpl,
|
|
304
346
|
});
|
|
305
347
|
```
|
|
306
348
|
|
|
307
|
-
### Environment
|
|
349
|
+
### Environment variables
|
|
308
350
|
|
|
309
351
|
```bash
|
|
310
352
|
export VAIRIFIED_API_KEY="vair_pk_xxx"
|
|
353
|
+
export VAIRIFIED_ENV="staging" # optional; default: production
|
|
311
354
|
```
|
|
312
355
|
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
const client = new Vairified();
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
## Dry-Run Mode (Dev Keys)
|
|
319
|
-
|
|
320
|
-
If your API key has the `dry-run` scope, match submissions are **validated but not persisted**. This is useful for testing integrations without affecting production data.
|
|
321
|
-
|
|
322
|
-
```typescript
|
|
323
|
-
// With a dry-run API key
|
|
324
|
-
const client = new Vairified({ apiKey: 'vair_pk_dev_xxx' });
|
|
325
|
-
const result = await client.submitMatches([match1, match2]);
|
|
326
|
-
|
|
327
|
-
if (result.dryRun) {
|
|
328
|
-
console.log(`Validation passed: ${result.numGames} games would be created`);
|
|
329
|
-
console.log(result.message);
|
|
330
|
-
}
|
|
356
|
+
```ts
|
|
357
|
+
const client = new Vairified(); // reads both env vars
|
|
331
358
|
```
|
|
332
359
|
|
|
333
|
-
Request a dry-run API key from your Vairified partner contact for integration testing.
|
|
334
|
-
|
|
335
360
|
## Error Handling
|
|
336
361
|
|
|
337
|
-
|
|
362
|
+
The SDK maps HTTP status codes to typed exceptions. All typed exceptions inherit from
|
|
363
|
+
`VairifiedError`, so a single `catch` can handle everything.
|
|
364
|
+
|
|
365
|
+
```ts
|
|
338
366
|
import {
|
|
339
367
|
Vairified,
|
|
340
368
|
VairifiedError,
|
|
341
369
|
RateLimitError,
|
|
342
370
|
AuthenticationError,
|
|
343
371
|
NotFoundError,
|
|
372
|
+
ValidationError,
|
|
344
373
|
OAuthError,
|
|
345
374
|
} from 'vairified';
|
|
346
375
|
|
|
347
|
-
const client = new Vairified({ apiKey: 'vair_pk_xxx' });
|
|
348
|
-
|
|
349
376
|
try {
|
|
350
|
-
const member = await client.
|
|
351
|
-
} catch (
|
|
352
|
-
if (
|
|
353
|
-
console.log(`Rate limited
|
|
354
|
-
} else if (
|
|
377
|
+
const member = await client.members.get('vair_mem_xxx');
|
|
378
|
+
} catch (err) {
|
|
379
|
+
if (err instanceof RateLimitError) {
|
|
380
|
+
console.log(`Rate limited; retry after ${err.retryAfter}s`);
|
|
381
|
+
} else if (err instanceof AuthenticationError) {
|
|
355
382
|
console.log('Invalid API key');
|
|
356
|
-
} else if (
|
|
383
|
+
} else if (err instanceof NotFoundError) {
|
|
357
384
|
console.log('Member not found');
|
|
358
|
-
} else if (
|
|
359
|
-
console.log(`
|
|
360
|
-
} else if (
|
|
361
|
-
console.log(`
|
|
385
|
+
} else if (err instanceof ValidationError) {
|
|
386
|
+
console.log(`Bad request: ${err.message}`);
|
|
387
|
+
} else if (err instanceof OAuthError) {
|
|
388
|
+
console.log(`OAuth error: ${err.message} (code: ${err.errorCode})`);
|
|
389
|
+
} else if (err instanceof VairifiedError) {
|
|
390
|
+
console.log(`API error: ${err.message} (status: ${err.statusCode})`);
|
|
391
|
+
} else {
|
|
392
|
+
throw err;
|
|
362
393
|
}
|
|
363
394
|
}
|
|
364
395
|
```
|
|
365
396
|
|
|
366
|
-
##
|
|
397
|
+
## Models
|
|
367
398
|
|
|
368
|
-
|
|
399
|
+
Response models are immutable classes wrapping the wire payload. They expose computed
|
|
400
|
+
getters (`member.name`, `update.delta`) and support iteration where it makes sense
|
|
401
|
+
(`for (const [key, split] of sportRating)`).
|
|
402
|
+
|
|
403
|
+
### `Member`
|
|
404
|
+
|
|
405
|
+
```ts
|
|
406
|
+
member.memberId // Numeric member ID (public)
|
|
407
|
+
member.id // UUID | null
|
|
408
|
+
member.name // Full name (getter)
|
|
409
|
+
member.displayName // "Mike B."
|
|
410
|
+
member.firstName / lastName
|
|
411
|
+
member.gender // 'MALE' | 'FEMALE' | 'OTHER' | 'UNKNOWN' | null
|
|
412
|
+
member.age / city / state / zip / country
|
|
413
|
+
member.status.isVairified // grouped status flags
|
|
414
|
+
member.status.isConnected
|
|
415
|
+
member.sport // MemberSportMap
|
|
416
|
+
member.sports // readonly string[] of sport codes
|
|
417
|
+
member.ratingFor('pickleball') // number | null
|
|
418
|
+
member.split('overall-open') // RatingSplitWire | null
|
|
419
|
+
```
|
|
369
420
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
421
|
+
### `SportRating` (dict-like)
|
|
422
|
+
|
|
423
|
+
```ts
|
|
424
|
+
const pb = member.sport.get('pickleball');
|
|
425
|
+
pb?.rating // Primary rating for this sport
|
|
426
|
+
pb?.abbr // "VO", "VG", etc.
|
|
427
|
+
pb?.get('overall-open') // Any split key
|
|
428
|
+
pb?.size // Number of splits
|
|
429
|
+
pb?.has('singles-40+') // Membership check
|
|
430
|
+
for (const [key, split] of pb ?? []) { /* iterate */ }
|
|
378
431
|
```
|
|
379
432
|
|
|
433
|
+
## Migrating
|
|
434
|
+
|
|
435
|
+
**From 0.2.x → 0.3.0:** All OAuth scope strings gained a `user:` prefix
|
|
436
|
+
(`profile:read` → `user:profile:read`). Update any hardcoded scope arrays.
|
|
437
|
+
New: `members.getBulk()`, `matches.tournamentImport()`, `webhooks.deliveries()`.
|
|
438
|
+
|
|
439
|
+
**From 0.1.x → 0.2.0:** Full rewrite. See the
|
|
440
|
+
[migration guide](https://vairified.github.io/vairified.js/documents/Migrating_from_0.1.x.html)
|
|
441
|
+
for the full diff and [CHANGELOG.md](CHANGELOG.md) for the release notes.
|
|
442
|
+
|
|
380
443
|
## Development
|
|
381
444
|
|
|
382
445
|
```bash
|
|
@@ -384,16 +447,17 @@ git clone https://github.com/Vairified/vairified.js.git
|
|
|
384
447
|
cd vairified.js
|
|
385
448
|
npm install
|
|
386
449
|
npm test
|
|
450
|
+
npm run build
|
|
387
451
|
```
|
|
388
452
|
|
|
389
453
|
## License
|
|
390
454
|
|
|
391
|
-
MIT
|
|
455
|
+
MIT — see [LICENSE](LICENSE) for details.
|
|
392
456
|
|
|
393
457
|
---
|
|
394
458
|
|
|
395
459
|
<p align="center">
|
|
396
|
-
<a href="https://vairified.com">vairified.com</a> ·
|
|
397
|
-
<a href="https://vairified.github.io/vairified.js">Documentation</a> ·
|
|
460
|
+
<a href="https://vairified.com">vairified.com</a> ·
|
|
461
|
+
<a href="https://vairified.github.io/vairified.js">Documentation</a> ·
|
|
398
462
|
<a href="mailto:support@vairified.com">Support</a>
|
|
399
463
|
</p>
|