vairified 0.1.1 → 0.2.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 +250 -256
- package/dist/index.cjs +766 -819
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +821 -780
- package/dist/index.d.ts +821 -780
- package/dist/index.js +758 -813
- 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,342 @@ 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`, `search`, `find`, `ratingUpdates` |
|
|
56
|
+
| `client.matches` | `submit`, `testWebhook` |
|
|
57
|
+
| `client.oauth` | `authorize`, `exchangeToken`, `refresh`, `revoke` |
|
|
58
|
+
| `client.leaderboard` | `list`, `rank`, `categories` |
|
|
59
|
+
| `client.usage()` | Rate-limit + request-count stats |
|
|
60
|
+
|
|
61
|
+
## Members
|
|
62
|
+
|
|
63
|
+
### Get a connected member
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
const member = await client.members.get('vair_mem_xxx');
|
|
67
|
+
|
|
68
|
+
console.log(member.name); // Full name
|
|
69
|
+
console.log(member.displayName); // "Mike B."
|
|
70
|
+
console.log(member.ratingFor('pickleball')); // 3.915
|
|
71
|
+
console.log(member.status.isVairified); // true
|
|
72
|
+
|
|
73
|
+
// Dict-like access to rating splits for a specific sport
|
|
74
|
+
const pb = member.sport.get('pickleball');
|
|
75
|
+
if (pb) {
|
|
76
|
+
console.log(pb.rating, pb.abbr); // 3.915 VO
|
|
77
|
+
console.log(pb.get('overall-open')?.rating); // 3.915
|
|
78
|
+
console.log(pb.has('singles-open')); // true
|
|
79
|
+
for (const [key, split] of pb) {
|
|
80
|
+
console.log(key, split.rating);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Filter ratings to specific sports
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
// Just pickleball
|
|
89
|
+
const member = await client.members.get('vair_mem_xxx', { sport: 'pickleball' });
|
|
90
|
+
|
|
91
|
+
// Multiple sports
|
|
92
|
+
const member2 = await client.members.get('vair_mem_xxx', {
|
|
93
|
+
sport: ['pickleball', 'padel'],
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Auto-paginating search
|
|
98
|
+
|
|
99
|
+
`search()` is an async generator — iterate directly with `for await`. Pages are fetched
|
|
100
|
+
lazily, so memory usage stays bounded regardless of result count.
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
for await (const member of client.members.search({
|
|
54
104
|
city: 'Austin',
|
|
55
105
|
state: 'TX',
|
|
56
106
|
ratingMin: 3.5,
|
|
57
107
|
ratingMax: 4.5,
|
|
58
108
|
vairifiedOnly: true,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
// Iterate over results
|
|
63
|
-
for (const player of results) {
|
|
64
|
-
console.log(`${player.name}: ${player.rating}`);
|
|
109
|
+
})) {
|
|
110
|
+
console.log(member.name, member.ratingFor('pickleball'));
|
|
65
111
|
}
|
|
66
112
|
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
113
|
+
// Cap with maxResults, or break out early
|
|
114
|
+
const top20: Member[] = [];
|
|
115
|
+
for await (const m of client.members.search({ name: 'Smith', maxResults: 20 })) {
|
|
116
|
+
top20.push(m);
|
|
71
117
|
}
|
|
72
118
|
```
|
|
73
119
|
|
|
74
|
-
### Find
|
|
120
|
+
### Find by name (first hit only)
|
|
75
121
|
|
|
76
|
-
```
|
|
77
|
-
const
|
|
78
|
-
if (
|
|
79
|
-
console.log(
|
|
122
|
+
```ts
|
|
123
|
+
const mike = await client.members.find('Mike Barker');
|
|
124
|
+
if (mike) {
|
|
125
|
+
console.log(mike.ratingFor('pickleball'));
|
|
80
126
|
}
|
|
81
127
|
```
|
|
82
128
|
|
|
83
|
-
###
|
|
84
|
-
|
|
85
|
-
```typescript
|
|
86
|
-
import { Vairified, Match } from 'vairified';
|
|
129
|
+
### Rating change notifications
|
|
87
130
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
bracket: '4.0 Doubles',
|
|
94
|
-
date: new Date(),
|
|
95
|
-
team1: ['player1_id', 'player2_id'],
|
|
96
|
-
team2: ['player3_id', 'player4_id'],
|
|
97
|
-
scores: [[11, 9], [11, 7]],
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
const result = await client.submitMatch(match);
|
|
101
|
-
if (result.ok) {
|
|
102
|
-
console.log(`Submitted ${result.numGames} games`);
|
|
131
|
+
```ts
|
|
132
|
+
const updates = await client.members.ratingUpdates();
|
|
133
|
+
for (const update of updates) {
|
|
134
|
+
const arrow = update.improved ? '↑' : '↓';
|
|
135
|
+
console.log(`${update.displayName} ${arrow} delta=${update.delta?.toFixed(3)}`);
|
|
103
136
|
}
|
|
104
|
-
|
|
105
|
-
// Singles match
|
|
106
|
-
const singles = new Match({
|
|
107
|
-
event: 'Club Singles',
|
|
108
|
-
bracket: 'Open Singles',
|
|
109
|
-
date: new Date(),
|
|
110
|
-
team1: ['player1_id'],
|
|
111
|
-
team2: ['player2_id'],
|
|
112
|
-
scores: [[11, 8], [9, 11], [11, 6]],
|
|
113
|
-
});
|
|
114
|
-
await client.submitMatch(singles);
|
|
115
137
|
```
|
|
116
138
|
|
|
117
|
-
|
|
139
|
+
## Match Submission
|
|
118
140
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
await client.getMember('user_2');
|
|
141
|
+
Matches are submitted as a `MatchBatch` — defaults at the batch level apply to every
|
|
142
|
+
match unless overridden. The shape is n-team × n-game, so singles, doubles, and
|
|
143
|
+
round-robin all go through the same path.
|
|
123
144
|
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
145
|
+
```ts
|
|
146
|
+
const result = await client.matches.submit({
|
|
147
|
+
sport: 'pickleball',
|
|
148
|
+
winScore: 11,
|
|
149
|
+
winBy: 2,
|
|
150
|
+
bracket: '4.0 Doubles',
|
|
151
|
+
event: 'Weekly League',
|
|
152
|
+
matchDate: '2026-04-11T14:00:00Z',
|
|
153
|
+
matches: [
|
|
154
|
+
{
|
|
155
|
+
identifier: 'm1',
|
|
156
|
+
teams: [
|
|
157
|
+
['vair_mem_aaa', 'vair_mem_bbb'],
|
|
158
|
+
['vair_mem_ccc', 'vair_mem_ddd'],
|
|
159
|
+
],
|
|
160
|
+
games: [{ scores: [11, 8] }, { scores: [11, 5] }],
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
identifier: 'm2',
|
|
164
|
+
teams: [['vair_mem_eee'], ['vair_mem_fff']], // singles
|
|
165
|
+
games: [{ scores: [11, 9] }, { scores: [11, 7] }],
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
});
|
|
129
169
|
|
|
130
|
-
|
|
131
|
-
|
|
170
|
+
if (result.ok) {
|
|
171
|
+
console.log(`Submitted ${result.numGames} games in ${result.numMatches} matches`);
|
|
132
172
|
}
|
|
133
173
|
```
|
|
134
174
|
|
|
135
|
-
|
|
175
|
+
Set `dryRun: true` on the batch to validate without persisting — your API key must have
|
|
176
|
+
the `dry-run` scope.
|
|
136
177
|
|
|
137
|
-
|
|
178
|
+
## OAuth Connect Flow
|
|
138
179
|
|
|
139
|
-
```
|
|
140
|
-
import { Vairified,
|
|
180
|
+
```ts
|
|
181
|
+
import { Vairified, OAuthError, generateState } from 'vairified';
|
|
141
182
|
|
|
142
|
-
|
|
183
|
+
await using client = new Vairified({ apiKey: 'vair_pk_xxx' });
|
|
143
184
|
|
|
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'],
|
|
185
|
+
// Step 1 — start authorization
|
|
186
|
+
const state = generateState();
|
|
187
|
+
const auth = await client.oauth.authorize({
|
|
188
|
+
redirectUri: 'https://myapp.com/oauth/callback',
|
|
189
|
+
scopes: ['profile:read', 'rating:read', 'match:submit'],
|
|
149
190
|
state,
|
|
150
|
-
);
|
|
151
|
-
|
|
191
|
+
});
|
|
152
192
|
// Redirect user to auth.authorizationUrl
|
|
153
|
-
window.location.href = auth.authorizationUrl;
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
```typescript
|
|
157
|
-
// Step 2: Handle callback (in your /oauth/callback route)
|
|
158
|
-
const client = new Vairified({ apiKey: 'vair_pk_xxx' });
|
|
159
193
|
|
|
160
|
-
//
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const {
|
|
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
|
-
```
|
|
194
|
+
// Step 2 — exchange the callback code
|
|
195
|
+
const tokens = await client.oauth.exchangeToken({
|
|
196
|
+
code: 'code-from-callback',
|
|
197
|
+
redirectUri: 'https://myapp.com/oauth/callback',
|
|
198
|
+
});
|
|
199
|
+
const { accessToken, refreshToken, playerId } = tokens;
|
|
171
200
|
|
|
172
|
-
|
|
173
|
-
// Step 3: Refresh expired tokens
|
|
201
|
+
// Step 3 — refresh when the access token expires
|
|
174
202
|
try {
|
|
175
|
-
const newTokens = await client.
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
// Token revoked, user needs to re-authorize
|
|
203
|
+
const newTokens = await client.oauth.refresh(refreshToken!);
|
|
204
|
+
} catch (err) {
|
|
205
|
+
if (err instanceof OAuthError && err.errorCode === 'invalid_grant') {
|
|
206
|
+
// User must re-authorize
|
|
180
207
|
}
|
|
181
208
|
}
|
|
182
|
-
```
|
|
183
209
|
|
|
184
|
-
|
|
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
|
-
|
|
195
|
-
### Revoke Connection
|
|
196
|
-
|
|
197
|
-
```typescript
|
|
198
|
-
await client.revokeConnection('vair_mem_xxx');
|
|
210
|
+
// Step 4 — revoke the connection
|
|
211
|
+
await client.oauth.revoke(playerId);
|
|
199
212
|
```
|
|
200
213
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
### Player
|
|
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
|
-
```
|
|
214
|
+
`OAuthScope` is a string literal union, so your editor will catch typos:
|
|
218
215
|
|
|
219
|
-
|
|
216
|
+
```ts
|
|
217
|
+
import type { OAuthScope } from 'vairified';
|
|
220
218
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
await member.refresh() // Refresh data from API
|
|
219
|
+
const scopes: OAuthScope[] = ['profile:read', 'rating:read']; // ok
|
|
220
|
+
const bad: OAuthScope[] = ['profile:read', 'rating']; // type error
|
|
224
221
|
```
|
|
225
222
|
|
|
226
|
-
###
|
|
223
|
+
### Available scopes
|
|
227
224
|
|
|
228
|
-
|
|
225
|
+
| Scope | Description |
|
|
226
|
+
|----------------------|------------------------------------------------|
|
|
227
|
+
| `profile:read` | Name, location, verification status |
|
|
228
|
+
| `profile:email` | Email address |
|
|
229
|
+
| `rating:read` | Current rating and rating splits |
|
|
230
|
+
| `rating:history` | Complete rating history |
|
|
231
|
+
| `match:submit` | Submit matches on behalf of user |
|
|
232
|
+
| `webhook:subscribe` | Rating change notifications |
|
|
229
233
|
|
|
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
|
-
```
|
|
234
|
+
## Leaderboards
|
|
240
235
|
|
|
241
|
-
|
|
236
|
+
```ts
|
|
237
|
+
// Global leaderboard
|
|
238
|
+
const lb = await client.leaderboard.list();
|
|
242
239
|
|
|
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"
|
|
240
|
+
// Texas singles, verified only
|
|
241
|
+
const tx = await client.leaderboard.list({
|
|
242
|
+
category: 'singles',
|
|
243
|
+
scope: 'state',
|
|
244
|
+
state: 'TX',
|
|
245
|
+
verifiedOnly: true,
|
|
246
|
+
limit: 50,
|
|
254
247
|
});
|
|
255
248
|
|
|
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
|
-
```
|
|
249
|
+
// A specific player's rank with 5 players on either side
|
|
250
|
+
const rank = await client.leaderboard.rank('vair_mem_xxx', {
|
|
251
|
+
category: 'doubles',
|
|
252
|
+
contextSize: 5,
|
|
253
|
+
});
|
|
274
254
|
|
|
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
|
|
255
|
+
// Available categories, brackets, scopes
|
|
256
|
+
const categories = await client.leaderboard.categories();
|
|
287
257
|
```
|
|
288
258
|
|
|
289
259
|
## Configuration
|
|
290
260
|
|
|
291
|
-
```
|
|
261
|
+
```ts
|
|
292
262
|
import { Vairified } from 'vairified';
|
|
293
263
|
|
|
294
|
-
//
|
|
295
|
-
const client = new Vairified({ apiKey: 'vair_pk_xxx' });
|
|
264
|
+
// Environment preset
|
|
265
|
+
const client = new Vairified({ apiKey: 'vair_pk_xxx', env: 'production' }); // default
|
|
266
|
+
const staging = new Vairified({ apiKey: 'vair_pk_xxx', env: 'staging' });
|
|
267
|
+
const local = new Vairified({ apiKey: 'vair_pk_xxx', env: 'local' });
|
|
296
268
|
|
|
297
|
-
//
|
|
298
|
-
const
|
|
269
|
+
// Custom base URL (overrides env)
|
|
270
|
+
const custom = new Vairified({
|
|
271
|
+
apiKey: 'vair_pk_xxx',
|
|
272
|
+
baseUrl: 'http://localhost:3001/api/v1',
|
|
273
|
+
timeoutMs: 30_000,
|
|
274
|
+
});
|
|
299
275
|
|
|
300
|
-
//
|
|
301
|
-
const
|
|
276
|
+
// Inject a custom fetch (test shims, non-Node environments)
|
|
277
|
+
const withFetch = new Vairified({
|
|
302
278
|
apiKey: 'vair_pk_xxx',
|
|
303
|
-
|
|
279
|
+
fetch: customFetchImpl,
|
|
304
280
|
});
|
|
305
281
|
```
|
|
306
282
|
|
|
307
|
-
### Environment
|
|
283
|
+
### Environment variables
|
|
308
284
|
|
|
309
285
|
```bash
|
|
310
286
|
export VAIRIFIED_API_KEY="vair_pk_xxx"
|
|
287
|
+
export VAIRIFIED_ENV="staging" # optional; default: production
|
|
311
288
|
```
|
|
312
289
|
|
|
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
|
-
}
|
|
290
|
+
```ts
|
|
291
|
+
const client = new Vairified(); // reads both env vars
|
|
331
292
|
```
|
|
332
293
|
|
|
333
|
-
Request a dry-run API key from your Vairified partner contact for integration testing.
|
|
334
|
-
|
|
335
294
|
## Error Handling
|
|
336
295
|
|
|
337
|
-
|
|
296
|
+
The SDK maps HTTP status codes to typed exceptions. All typed exceptions inherit from
|
|
297
|
+
`VairifiedError`, so a single `catch` can handle everything.
|
|
298
|
+
|
|
299
|
+
```ts
|
|
338
300
|
import {
|
|
339
301
|
Vairified,
|
|
340
302
|
VairifiedError,
|
|
341
303
|
RateLimitError,
|
|
342
304
|
AuthenticationError,
|
|
343
305
|
NotFoundError,
|
|
306
|
+
ValidationError,
|
|
344
307
|
OAuthError,
|
|
345
308
|
} from 'vairified';
|
|
346
309
|
|
|
347
|
-
const client = new Vairified({ apiKey: 'vair_pk_xxx' });
|
|
348
|
-
|
|
349
310
|
try {
|
|
350
|
-
const member = await client.
|
|
351
|
-
} catch (
|
|
352
|
-
if (
|
|
353
|
-
console.log(`Rate limited
|
|
354
|
-
} else if (
|
|
311
|
+
const member = await client.members.get('vair_mem_xxx');
|
|
312
|
+
} catch (err) {
|
|
313
|
+
if (err instanceof RateLimitError) {
|
|
314
|
+
console.log(`Rate limited; retry after ${err.retryAfter}s`);
|
|
315
|
+
} else if (err instanceof AuthenticationError) {
|
|
355
316
|
console.log('Invalid API key');
|
|
356
|
-
} else if (
|
|
317
|
+
} else if (err instanceof NotFoundError) {
|
|
357
318
|
console.log('Member not found');
|
|
358
|
-
} else if (
|
|
359
|
-
console.log(`
|
|
360
|
-
} else if (
|
|
361
|
-
console.log(`
|
|
319
|
+
} else if (err instanceof ValidationError) {
|
|
320
|
+
console.log(`Bad request: ${err.message}`);
|
|
321
|
+
} else if (err instanceof OAuthError) {
|
|
322
|
+
console.log(`OAuth error: ${err.message} (code: ${err.errorCode})`);
|
|
323
|
+
} else if (err instanceof VairifiedError) {
|
|
324
|
+
console.log(`API error: ${err.message} (status: ${err.statusCode})`);
|
|
325
|
+
} else {
|
|
326
|
+
throw err;
|
|
362
327
|
}
|
|
363
328
|
}
|
|
364
329
|
```
|
|
365
330
|
|
|
366
|
-
##
|
|
331
|
+
## Models
|
|
367
332
|
|
|
368
|
-
|
|
333
|
+
Response models are immutable classes wrapping the wire payload. They expose computed
|
|
334
|
+
getters (`member.name`, `update.delta`) and support iteration where it makes sense
|
|
335
|
+
(`for (const [key, split] of sportRating)`).
|
|
336
|
+
|
|
337
|
+
### `Member`
|
|
338
|
+
|
|
339
|
+
```ts
|
|
340
|
+
member.memberId // Numeric member ID (public)
|
|
341
|
+
member.id // UUID | null
|
|
342
|
+
member.name // Full name (getter)
|
|
343
|
+
member.displayName // "Mike B."
|
|
344
|
+
member.firstName / lastName
|
|
345
|
+
member.gender // 'MALE' | 'FEMALE' | 'OTHER' | 'UNKNOWN' | null
|
|
346
|
+
member.age / city / state / zip / country
|
|
347
|
+
member.status.isVairified // grouped status flags
|
|
348
|
+
member.status.isConnected
|
|
349
|
+
member.sport // MemberSportMap
|
|
350
|
+
member.sports // readonly string[] of sport codes
|
|
351
|
+
member.ratingFor('pickleball') // number | null
|
|
352
|
+
member.split('overall-open') // RatingSplitWire | null
|
|
353
|
+
```
|
|
369
354
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
355
|
+
### `SportRating` (dict-like)
|
|
356
|
+
|
|
357
|
+
```ts
|
|
358
|
+
const pb = member.sport.get('pickleball');
|
|
359
|
+
pb?.rating // Primary rating for this sport
|
|
360
|
+
pb?.abbr // "VO", "VG", etc.
|
|
361
|
+
pb?.get('overall-open') // Any split key
|
|
362
|
+
pb?.size // Number of splits
|
|
363
|
+
pb?.has('singles-40+') // Membership check
|
|
364
|
+
for (const [key, split] of pb ?? []) { /* iterate */ }
|
|
378
365
|
```
|
|
379
366
|
|
|
367
|
+
## Migrating from 0.1.x
|
|
368
|
+
|
|
369
|
+
Version 0.2.0 is a breaking rewrite. See the
|
|
370
|
+
[migration guide](https://vairified.github.io/vairified.js/documents/Migrating_from_0.1.x.html)
|
|
371
|
+
for the full diff and [CHANGELOG.md](CHANGELOG.md) for the release notes.
|
|
372
|
+
|
|
380
373
|
## Development
|
|
381
374
|
|
|
382
375
|
```bash
|
|
@@ -384,16 +377,17 @@ git clone https://github.com/Vairified/vairified.js.git
|
|
|
384
377
|
cd vairified.js
|
|
385
378
|
npm install
|
|
386
379
|
npm test
|
|
380
|
+
npm run build
|
|
387
381
|
```
|
|
388
382
|
|
|
389
383
|
## License
|
|
390
384
|
|
|
391
|
-
MIT
|
|
385
|
+
MIT — see [LICENSE](LICENSE) for details.
|
|
392
386
|
|
|
393
387
|
---
|
|
394
388
|
|
|
395
389
|
<p align="center">
|
|
396
|
-
<a href="https://vairified.com">vairified.com</a> ·
|
|
397
|
-
<a href="https://vairified.github.io/vairified.js">Documentation</a> ·
|
|
390
|
+
<a href="https://vairified.com">vairified.com</a> ·
|
|
391
|
+
<a href="https://vairified.github.io/vairified.js">Documentation</a> ·
|
|
398
392
|
<a href="mailto:support@vairified.com">Support</a>
|
|
399
393
|
</p>
|