@pfdev/api-types 3.0.1 → 3.0.4
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 +95 -59
- package/index.ts +1 -1
- package/package.json +1 -1
- package/v3.ts +154 -364
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @pfdev/api-types
|
|
2
2
|
|
|
3
|
-
TypeScript types for PF API V3,
|
|
3
|
+
TypeScript types for PF API V3, synced directly from TypeBox schemas in pfapi-fs.
|
|
4
4
|
|
|
5
5
|
## Quick Start
|
|
6
6
|
|
|
@@ -13,28 +13,67 @@ npm install @pfdev/api-types
|
|
|
13
13
|
### Use in React/React Native
|
|
14
14
|
|
|
15
15
|
```typescript
|
|
16
|
-
import
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
16
|
+
import { useState, useEffect } from 'react';
|
|
17
|
+
import type { SetDetail } from '@pfdev/api-types';
|
|
18
|
+
|
|
19
|
+
function SetDisplay({ setId }: { setId: string }) {
|
|
20
|
+
const [set, setSet] = useState<SetDetail | null>(null);
|
|
21
|
+
const [loading, setLoading] = useState(true);
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
const fetchSet = async () => {
|
|
25
|
+
const response = await fetch(`/api/v3/sets/${setId}`);
|
|
26
|
+
const data: SetDetail = await response.json();
|
|
27
|
+
setSet(data);
|
|
28
|
+
setLoading(false);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
fetchSet();
|
|
32
|
+
}, [setId]);
|
|
33
|
+
|
|
34
|
+
if (loading) return <div>Loading...</div>;
|
|
35
|
+
if (!set) return <div>Not found</div>;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div>
|
|
39
|
+
<h1>{set.name}</h1>
|
|
40
|
+
<p>Series: {set.series}</p>
|
|
41
|
+
<p>Released: {set.releaseddate}</p>
|
|
42
|
+
<img src={set.imglogo} alt={set.name} />
|
|
43
|
+
|
|
44
|
+
{set.current_price && (
|
|
45
|
+
<p>Current Price: ${set.current_price}</p>
|
|
46
|
+
)}
|
|
47
|
+
|
|
48
|
+
<div>
|
|
49
|
+
<p>7-day change: {set.change_7d}%</p>
|
|
50
|
+
<p>1-year change: {set.change_1y}%</p>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<h3>Rarity Distribution</h3>
|
|
54
|
+
<ul>
|
|
55
|
+
{set.rarities.map((rarity) => (
|
|
56
|
+
<li key={rarity.rarity}>
|
|
57
|
+
{rarity.rarity}: {rarity.count} cards
|
|
58
|
+
</li>
|
|
59
|
+
))}
|
|
60
|
+
</ul>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
31
64
|
```
|
|
32
65
|
|
|
66
|
+
**Benefits:**
|
|
67
|
+
- ✅ Full IDE autocomplete on `set.` properties
|
|
68
|
+
- ✅ Type errors caught at compile time
|
|
69
|
+
- ✅ Optional fields properly typed (e.g., `current_price?: number`)
|
|
70
|
+
- ✅ Nested types fully typed (e.g., `rarities: RarityCount[]`)
|
|
71
|
+
|
|
33
72
|
## Features
|
|
34
73
|
|
|
35
|
-
✅ **
|
|
74
|
+
✅ **Single Source of Truth** - Types synced from pfapi-fs schemas
|
|
36
75
|
✅ **Fully Typed** - IDE autocompletion on all API responses
|
|
37
|
-
✅ **Zero Dependencies** - Only ~
|
|
76
|
+
✅ **Zero Dependencies** - Only ~5 KB, no runtime overhead
|
|
38
77
|
✅ **Type Safe** - Catch errors at compile time, not runtime
|
|
39
78
|
✅ **Free** - No paid npm account needed
|
|
40
79
|
|
|
@@ -42,19 +81,26 @@ const getSet = async (id: string): Promise<any> => {
|
|
|
42
81
|
|
|
43
82
|
### Prerequisites
|
|
44
83
|
|
|
45
|
-
- pfapi-fs
|
|
84
|
+
- pfapi-fs available locally (no need to run it)
|
|
46
85
|
- npm installed
|
|
47
86
|
|
|
48
87
|
### Step-by-Step
|
|
49
88
|
|
|
50
89
|
#### 1. Make changes in pfapi-fs
|
|
51
90
|
|
|
52
|
-
|
|
53
|
-
cd ../pfapi-fs
|
|
54
|
-
npm run dev # Start the API on https://127.0.0.1:5000
|
|
55
|
-
```
|
|
91
|
+
Add or modify V3 endpoints with TypeBox schemas:
|
|
56
92
|
|
|
57
|
-
|
|
93
|
+
```typescript
|
|
94
|
+
// pfapi-fs/src/routes/v3/sets/schemas.ts
|
|
95
|
+
export const SetDetailSchema = Type.Object({
|
|
96
|
+
setid: Type.String(),
|
|
97
|
+
name: Type.String(),
|
|
98
|
+
series: Type.String(),
|
|
99
|
+
// ... more fields
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
export type SetDetail = Static<typeof SetDetailSchema>;
|
|
103
|
+
```
|
|
58
104
|
|
|
59
105
|
#### 2. Bump version and generate types
|
|
60
106
|
|
|
@@ -64,11 +110,11 @@ npm version patch # or minor/major
|
|
|
64
110
|
```
|
|
65
111
|
|
|
66
112
|
This automatically:
|
|
67
|
-
- ✅
|
|
68
|
-
- ✅
|
|
69
|
-
- ✅ Stages
|
|
70
|
-
- ✅ Bumps
|
|
71
|
-
- ✅ Creates
|
|
113
|
+
- ✅ Syncs TypeBox schemas from `../pfapi-fs/src/routes/v3/*/schemas.ts`
|
|
114
|
+
- ✅ Validates with TypeScript
|
|
115
|
+
- ✅ Stages v3.ts with git
|
|
116
|
+
- ✅ Bumps version (3.0.0 → 3.0.1)
|
|
117
|
+
- ✅ Creates git tag
|
|
72
118
|
|
|
73
119
|
#### 3. Publish to npm
|
|
74
120
|
|
|
@@ -76,7 +122,7 @@ This automatically:
|
|
|
76
122
|
npm publish
|
|
77
123
|
```
|
|
78
124
|
|
|
79
|
-
|
|
125
|
+
Verifies v3.ts changed, then publishes to npm.
|
|
80
126
|
|
|
81
127
|
## Manual Type Generation
|
|
82
128
|
|
|
@@ -86,11 +132,20 @@ To manually regenerate types without versioning:
|
|
|
86
132
|
npm run generate
|
|
87
133
|
```
|
|
88
134
|
|
|
89
|
-
|
|
135
|
+
## How It Works
|
|
90
136
|
|
|
91
|
-
|
|
92
|
-
npm run generate https://api.example.com
|
|
137
|
+
### Single Source of Truth
|
|
93
138
|
```
|
|
139
|
+
pfapi-fs/src/routes/v3/sets/schemas.ts ← Edit here
|
|
140
|
+
↓
|
|
141
|
+
npm run generate
|
|
142
|
+
↓
|
|
143
|
+
pfapi-types/v3.ts ← Auto-synced
|
|
144
|
+
↓
|
|
145
|
+
Consumer imports
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**No drift possible** — types always match the backend schemas.
|
|
94
149
|
|
|
95
150
|
## Version Strategy
|
|
96
151
|
|
|
@@ -98,26 +153,9 @@ npm run generate https://api.example.com
|
|
|
98
153
|
- **Minor** (3.0.0 → 3.1.0) - New endpoints/features
|
|
99
154
|
- **Patch** (3.0.0 → 3.0.1) - Bug fixes in types
|
|
100
155
|
|
|
101
|
-
## How It Works
|
|
102
|
-
|
|
103
|
-
### Old Approach (Manual)
|
|
104
|
-
1. Edit schemas in pfapi-fs
|
|
105
|
-
2. Manually copy to pfapi-types/v3.ts
|
|
106
|
-
3. Bump version
|
|
107
|
-
4. Publish
|
|
108
|
-
|
|
109
|
-
**Problem**: Easy to forget steps, schema drift between packages.
|
|
110
|
-
|
|
111
|
-
### New Approach (Automated)
|
|
112
|
-
1. Edit schemas in pfapi-fs
|
|
113
|
-
2. Run `npm version patch` (auto-generates and stages)
|
|
114
|
-
3. Publish
|
|
115
|
-
|
|
116
|
-
**Benefit**: Types always sync with the API spec, no manual copying.
|
|
117
|
-
|
|
118
156
|
## Files
|
|
119
157
|
|
|
120
|
-
- `generate-types.sh` - Script that
|
|
158
|
+
- `generate-types.sh` - Script that syncs TypeBox schemas from pfapi-fs
|
|
121
159
|
- `package.json` - npm metadata with automated scripts
|
|
122
160
|
- `v3.ts` - Auto-generated types (do not edit manually)
|
|
123
161
|
- `index.ts` - Main entry point (re-exports v3.ts)
|
|
@@ -127,16 +165,14 @@ npm run generate https://api.example.com
|
|
|
127
165
|
### "Error: No changes to v3.ts since last tag"
|
|
128
166
|
|
|
129
167
|
The `preversion` hook prevents versioning if types haven't changed. Make sure:
|
|
130
|
-
-
|
|
131
|
-
-
|
|
132
|
-
- Run `npm run generate` to test type generation
|
|
168
|
+
- You've made actual changes to V3 schemas in pfapi-fs
|
|
169
|
+
- Run `npm run generate` to test schema sync
|
|
133
170
|
|
|
134
|
-
### "
|
|
171
|
+
### "TypeScript validation failed"
|
|
135
172
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
```
|
|
173
|
+
Check that:
|
|
174
|
+
- All TypeBox schemas in pfapi-fs compile
|
|
175
|
+
- Imports are correct in `src/routes/v3/*/schemas.ts`
|
|
140
176
|
|
|
141
177
|
### "npm ERR! code E404"
|
|
142
178
|
|
package/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
// Re-export all V3 API types
|
|
1
|
+
// Re-export all V3 API types synced from pfapi-fs
|
|
2
2
|
export * from './v3';
|
package/package.json
CHANGED
package/v3.ts
CHANGED
|
@@ -1,365 +1,155 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* Do not make direct changes to the file.
|
|
4
|
-
*/
|
|
1
|
+
// Auto-generated from pfapi-fs/src/routes/v3/*/schemas.ts
|
|
2
|
+
// Do not edit manually - run `npm run generate` to update
|
|
5
3
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
/** @description Pokemon TCG Live code (if available) */
|
|
159
|
-
ptcgocode?: string;
|
|
160
|
-
/** @description Format legalities */
|
|
161
|
-
legalities?: {
|
|
162
|
-
[key: string]: string;
|
|
163
|
-
};
|
|
164
|
-
/** @description Current market price */
|
|
165
|
-
current_price?: number;
|
|
166
|
-
/** @description 1-day price change % */
|
|
167
|
-
change_1d?: number;
|
|
168
|
-
/** @description 7-day price change % */
|
|
169
|
-
change_7d?: number;
|
|
170
|
-
/** @description 30-day price change % */
|
|
171
|
-
change_1m?: number;
|
|
172
|
-
/** @description 90-day price change % */
|
|
173
|
-
change_3m?: number;
|
|
174
|
-
/** @description 180-day price change % */
|
|
175
|
-
change_6m?: number;
|
|
176
|
-
/** @description 1-year price change % */
|
|
177
|
-
change_1y?: number;
|
|
178
|
-
/** @description Card rarity distribution */
|
|
179
|
-
rarities: {
|
|
180
|
-
/** @description Rarity level */
|
|
181
|
-
rarity: string;
|
|
182
|
-
/** @description Number of cards with this rarity */
|
|
183
|
-
count: number;
|
|
184
|
-
}[];
|
|
185
|
-
};
|
|
186
|
-
};
|
|
187
|
-
};
|
|
188
|
-
/** @description Default Response */
|
|
189
|
-
404: {
|
|
190
|
-
headers: {
|
|
191
|
-
[name: string]: unknown;
|
|
192
|
-
};
|
|
193
|
-
content: {
|
|
194
|
-
"application/json": {
|
|
195
|
-
error: string;
|
|
196
|
-
message: string;
|
|
197
|
-
};
|
|
198
|
-
};
|
|
199
|
-
};
|
|
200
|
-
/** @description Default Response */
|
|
201
|
-
500: {
|
|
202
|
-
headers: {
|
|
203
|
-
[name: string]: unknown;
|
|
204
|
-
};
|
|
205
|
-
content: {
|
|
206
|
-
"application/json": {
|
|
207
|
-
error: string;
|
|
208
|
-
message: string;
|
|
209
|
-
};
|
|
210
|
-
};
|
|
211
|
-
};
|
|
212
|
-
};
|
|
213
|
-
};
|
|
214
|
-
put?: never;
|
|
215
|
-
post?: never;
|
|
216
|
-
delete?: never;
|
|
217
|
-
options?: never;
|
|
218
|
-
head?: never;
|
|
219
|
-
patch?: never;
|
|
220
|
-
trace?: never;
|
|
221
|
-
};
|
|
222
|
-
"/": {
|
|
223
|
-
parameters: {
|
|
224
|
-
query?: never;
|
|
225
|
-
header?: never;
|
|
226
|
-
path?: never;
|
|
227
|
-
cookie?: never;
|
|
228
|
-
};
|
|
229
|
-
get: {
|
|
230
|
-
parameters: {
|
|
231
|
-
query?: never;
|
|
232
|
-
header?: never;
|
|
233
|
-
path?: never;
|
|
234
|
-
cookie?: never;
|
|
235
|
-
};
|
|
236
|
-
requestBody?: never;
|
|
237
|
-
responses: {
|
|
238
|
-
/** @description Default Response */
|
|
239
|
-
200: {
|
|
240
|
-
headers: {
|
|
241
|
-
[name: string]: unknown;
|
|
242
|
-
};
|
|
243
|
-
content?: never;
|
|
244
|
-
};
|
|
245
|
-
};
|
|
246
|
-
};
|
|
247
|
-
put?: never;
|
|
248
|
-
post?: never;
|
|
249
|
-
delete?: never;
|
|
250
|
-
options?: never;
|
|
251
|
-
head?: never;
|
|
252
|
-
patch?: never;
|
|
253
|
-
trace?: never;
|
|
254
|
-
};
|
|
255
|
-
"/redis": {
|
|
256
|
-
parameters: {
|
|
257
|
-
query?: never;
|
|
258
|
-
header?: never;
|
|
259
|
-
path?: never;
|
|
260
|
-
cookie?: never;
|
|
261
|
-
};
|
|
262
|
-
get: {
|
|
263
|
-
parameters: {
|
|
264
|
-
query?: never;
|
|
265
|
-
header?: never;
|
|
266
|
-
path?: never;
|
|
267
|
-
cookie?: never;
|
|
268
|
-
};
|
|
269
|
-
requestBody?: never;
|
|
270
|
-
responses: {
|
|
271
|
-
/** @description Default Response */
|
|
272
|
-
200: {
|
|
273
|
-
headers: {
|
|
274
|
-
[name: string]: unknown;
|
|
275
|
-
};
|
|
276
|
-
content?: never;
|
|
277
|
-
};
|
|
278
|
-
};
|
|
279
|
-
};
|
|
280
|
-
put?: never;
|
|
281
|
-
post?: never;
|
|
282
|
-
delete?: never;
|
|
283
|
-
options?: never;
|
|
284
|
-
head?: never;
|
|
285
|
-
patch?: never;
|
|
286
|
-
trace?: never;
|
|
287
|
-
};
|
|
288
|
-
"/debug-sentry": {
|
|
289
|
-
parameters: {
|
|
290
|
-
query?: never;
|
|
291
|
-
header?: never;
|
|
292
|
-
path?: never;
|
|
293
|
-
cookie?: never;
|
|
294
|
-
};
|
|
295
|
-
get: {
|
|
296
|
-
parameters: {
|
|
297
|
-
query?: never;
|
|
298
|
-
header?: never;
|
|
299
|
-
path?: never;
|
|
300
|
-
cookie?: never;
|
|
301
|
-
};
|
|
302
|
-
requestBody?: never;
|
|
303
|
-
responses: {
|
|
304
|
-
/** @description Default Response */
|
|
305
|
-
200: {
|
|
306
|
-
headers: {
|
|
307
|
-
[name: string]: unknown;
|
|
308
|
-
};
|
|
309
|
-
content?: never;
|
|
310
|
-
};
|
|
311
|
-
};
|
|
312
|
-
};
|
|
313
|
-
put?: never;
|
|
314
|
-
post?: never;
|
|
315
|
-
delete?: never;
|
|
316
|
-
options?: never;
|
|
317
|
-
head?: never;
|
|
318
|
-
patch?: never;
|
|
319
|
-
trace?: never;
|
|
320
|
-
};
|
|
321
|
-
"/pool-status": {
|
|
322
|
-
parameters: {
|
|
323
|
-
query?: never;
|
|
324
|
-
header?: never;
|
|
325
|
-
path?: never;
|
|
326
|
-
cookie?: never;
|
|
327
|
-
};
|
|
328
|
-
get: {
|
|
329
|
-
parameters: {
|
|
330
|
-
query?: never;
|
|
331
|
-
header?: never;
|
|
332
|
-
path?: never;
|
|
333
|
-
cookie?: never;
|
|
334
|
-
};
|
|
335
|
-
requestBody?: never;
|
|
336
|
-
responses: {
|
|
337
|
-
/** @description Default Response */
|
|
338
|
-
200: {
|
|
339
|
-
headers: {
|
|
340
|
-
[name: string]: unknown;
|
|
341
|
-
};
|
|
342
|
-
content?: never;
|
|
343
|
-
};
|
|
344
|
-
};
|
|
345
|
-
};
|
|
346
|
-
put?: never;
|
|
347
|
-
post?: never;
|
|
348
|
-
delete?: never;
|
|
349
|
-
options?: never;
|
|
350
|
-
head?: never;
|
|
351
|
-
patch?: never;
|
|
352
|
-
trace?: never;
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
export type webhooks = Record<string, never>;
|
|
356
|
-
export interface components {
|
|
357
|
-
schemas: never;
|
|
358
|
-
responses: never;
|
|
359
|
-
parameters: never;
|
|
360
|
-
requestBodies: never;
|
|
361
|
-
headers: never;
|
|
362
|
-
pathItems: never;
|
|
363
|
-
}
|
|
364
|
-
export type $defs = Record<string, never>;
|
|
365
|
-
export type operations = Record<string, never>;
|
|
4
|
+
import { Static, Type } from '@sinclair/typebox';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
// ============= CARDS =============
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
const CardImagesSchema = Type.Object(
|
|
11
|
+
{
|
|
12
|
+
small: Type.String({ format: 'uri', description: 'Small card image URL' }),
|
|
13
|
+
large: Type.String({ format: 'uri', description: 'Large card image URL' })
|
|
14
|
+
},
|
|
15
|
+
{ title: 'CardImages' }
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export const CardSchema = Type.Object(
|
|
19
|
+
{
|
|
20
|
+
cardid: Type.String({ description: 'Unique card identifier' }),
|
|
21
|
+
name: Type.String({ description: 'Card name' }),
|
|
22
|
+
number: Type.String({ description: 'Card number within set' }),
|
|
23
|
+
rarity: Type.Optional(Type.String({ description: 'Card rarity' })),
|
|
24
|
+
images: CardImagesSchema,
|
|
25
|
+
setid: Type.String({ description: 'Set this card belongs to' }),
|
|
26
|
+
setname: Type.String({ description: 'Name of the set' })
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
title: 'Card',
|
|
30
|
+
description: 'A Pokemon card'
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
export type Card = Static<typeof CardSchema>;
|
|
35
|
+
|
|
36
|
+
export const CardListResponseSchema = Type.Object(
|
|
37
|
+
{
|
|
38
|
+
data: Type.Array(CardSchema, { description: 'Array of cards' }),
|
|
39
|
+
total: Type.Number({ description: 'Total number of cards matching query' }),
|
|
40
|
+
count: Type.Number({ description: 'Number of cards returned in this response' })
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
title: 'CardListResponse',
|
|
44
|
+
description: 'Paginated list of Pokemon cards'
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
export type CardListResponse = Static<typeof CardListResponseSchema>;
|
|
49
|
+
|
|
50
|
+
export const CardListQuerySchema = Type.Object(
|
|
51
|
+
{
|
|
52
|
+
setid: Type.Optional(Type.String({ description: 'Filter by set ID' })),
|
|
53
|
+
name: Type.Optional(Type.String({ description: 'Search by card name' })),
|
|
54
|
+
limit: Type.Optional(Type.Number({ minimum: 1, maximum: 250, default: 50, description: 'Max results to return' })),
|
|
55
|
+
offset: Type.Optional(Type.Number({ minimum: 0, default: 0, description: 'Pagination offset' }))
|
|
56
|
+
},
|
|
57
|
+
{ description: 'At least one of setid or name must be provided' }
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
export type CardListQuery = Static<typeof CardListQuerySchema>;
|
|
61
|
+
|
|
62
|
+
// ============= SETS =============
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
// Response for list of sets
|
|
66
|
+
export const SetListItemSchema = Type.Object({
|
|
67
|
+
setid: Type.String({ description: 'Unique set identifier' }),
|
|
68
|
+
name: Type.String({ description: 'Set name' }),
|
|
69
|
+
series: Type.String({ description: 'Pokemon series' }),
|
|
70
|
+
releaseddate: Type.String({ format: 'date', description: 'Release date' }),
|
|
71
|
+
imgsymbol: Type.String({ format: 'uri', description: 'Symbol image URL' }),
|
|
72
|
+
imglogo: Type.String({ format: 'uri', description: 'Logo image URL' }),
|
|
73
|
+
printedtotal: Type.Number({ description: 'Total cards in set' }),
|
|
74
|
+
current_price: Type.Optional(Type.Number({ description: 'Current market price' })),
|
|
75
|
+
change_7d: Type.Optional(Type.Number({ description: '7-day price change %' })),
|
|
76
|
+
slug: Type.String({ description: 'URL-friendly slug' })
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
export type SetListItem = Static<typeof SetListItemSchema>;
|
|
80
|
+
|
|
81
|
+
export const SetListResponseSchema = Type.Object(
|
|
82
|
+
{
|
|
83
|
+
data: Type.Array(SetListItemSchema, { description: 'Array of sets' }),
|
|
84
|
+
total: Type.Number({ description: 'Total number of sets available' }),
|
|
85
|
+
count: Type.Number({ description: 'Number of sets returned in this response' })
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
title: 'SetListResponse',
|
|
89
|
+
description: 'Paginated list of Pokemon card sets'
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
export type SetListResponse = Static<typeof SetListResponseSchema>;
|
|
94
|
+
|
|
95
|
+
// Response for set details
|
|
96
|
+
export const RarityCountSchema = Type.Object(
|
|
97
|
+
{
|
|
98
|
+
rarity: Type.String({ description: 'Rarity level' }),
|
|
99
|
+
count: Type.Number({ description: 'Number of cards with this rarity' })
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
title: 'RarityCount',
|
|
103
|
+
description: 'Card rarity distribution in a set'
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
export type RarityCount = Static<typeof RarityCountSchema>;
|
|
108
|
+
|
|
109
|
+
export const SetDetailSchema = Type.Object(
|
|
110
|
+
{
|
|
111
|
+
setid: Type.String({ description: 'Unique set identifier' }),
|
|
112
|
+
name: Type.String({ description: 'Set name' }),
|
|
113
|
+
printedtotal: Type.Number({ description: 'Total cards printed (official)' }),
|
|
114
|
+
total: Type.Number({ description: 'Total unique cards in set' }),
|
|
115
|
+
series: Type.String({ description: 'Pokemon series name' }),
|
|
116
|
+
releaseddate: Type.String({ format: 'date', description: 'Release date' }),
|
|
117
|
+
updatedat: Type.String({ format: 'date-time', description: 'Last update timestamp' }),
|
|
118
|
+
imgsymbol: Type.String({ format: 'uri', description: 'Set symbol image URL' }),
|
|
119
|
+
imglogo: Type.String({ format: 'uri', description: 'Set logo image URL' }),
|
|
120
|
+
slug: Type.String({ description: 'URL-friendly identifier' }),
|
|
121
|
+
ptcgocode: Type.Optional(Type.String({ description: 'Pokemon TCG Live code (if available)' })),
|
|
122
|
+
legalities: Type.Optional(Type.Record(Type.String(), Type.String(), { description: 'Format legalities' })),
|
|
123
|
+
current_price: Type.Optional(Type.Number({ description: 'Current market price' })),
|
|
124
|
+
change_1d: Type.Optional(Type.Number({ description: '1-day price change %' })),
|
|
125
|
+
change_7d: Type.Optional(Type.Number({ description: '7-day price change %' })),
|
|
126
|
+
change_1m: Type.Optional(Type.Number({ description: '30-day price change %' })),
|
|
127
|
+
change_3m: Type.Optional(Type.Number({ description: '90-day price change %' })),
|
|
128
|
+
change_6m: Type.Optional(Type.Number({ description: '180-day price change %' })),
|
|
129
|
+
change_1y: Type.Optional(Type.Number({ description: '1-year price change %' })),
|
|
130
|
+
rarities: Type.Array(RarityCountSchema, { description: 'Card rarity distribution' })
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
title: 'SetDetail',
|
|
134
|
+
description: 'Detailed information about a Pokemon card set'
|
|
135
|
+
}
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
export type SetDetail = Static<typeof SetDetailSchema>;
|
|
139
|
+
|
|
140
|
+
// Query parameters
|
|
141
|
+
export const SetListQuerySchema = Type.Object({
|
|
142
|
+
search: Type.Optional(Type.String({ description: 'Search by set name' })),
|
|
143
|
+
series: Type.Optional(Type.String({ description: 'Filter by series' })),
|
|
144
|
+
limit: Type.Optional(Type.Number({ minimum: 1, maximum: 100, default: 20 })),
|
|
145
|
+
offset: Type.Optional(Type.Number({ minimum: 0, default: 0 }))
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
export type SetListQuery = Static<typeof SetListQuerySchema>;
|
|
149
|
+
|
|
150
|
+
// Path parameters
|
|
151
|
+
export const SetDetailParamSchema = Type.Object({
|
|
152
|
+
setid: Type.String({ description: 'Set ID or slug' })
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
export type SetDetailParam = Static<typeof SetDetailParamSchema>;
|