@jukasdrj/bookstrack-api-client 1.0.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 +374 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/schema.d.ts +1111 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +5 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# @bookstrack/api-client
|
|
2
|
+
|
|
3
|
+
**Official TypeScript SDK for BooksTrack API**
|
|
4
|
+
|
|
5
|
+
Auto-generated from OpenAPI specification using `openapi-typescript` + `openapi-fetch`.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Type-Safe** - Full TypeScript support with auto-generated types
|
|
10
|
+
- **Lightweight** - ~2KB gzipped client (tree-shakable)
|
|
11
|
+
- **Modern** - Uses native `fetch` API (works in browser, Node.js, Cloudflare Workers)
|
|
12
|
+
- **Auto-Generated** - Always in sync with API contract via OpenAPI spec
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @bookstrack/api-client
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Note:** This package is published to GitHub Packages. Ensure your `.npmrc` is configured:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
@bookstrack:registry=https://npm.pkg.github.com
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
### Basic Usage
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { createBooksTrackClient } from '@bookstrack/api-client'
|
|
36
|
+
|
|
37
|
+
const client = createBooksTrackClient({
|
|
38
|
+
baseUrl: 'https://api.oooefam.net'
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
// Search by ISBN
|
|
42
|
+
const { data, error } = await client.GET('/v1/search/isbn', {
|
|
43
|
+
params: { query: { isbn: '9780439708180' } }
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
if (error) {
|
|
47
|
+
console.error('API Error:', error)
|
|
48
|
+
} else {
|
|
49
|
+
console.log('Book:', data.data)
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Advanced Usage
|
|
54
|
+
|
|
55
|
+
#### Custom Headers (Authentication)
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
const client = createBooksTrackClient({
|
|
59
|
+
baseUrl: 'https://api.oooefam.net',
|
|
60
|
+
headers: {
|
|
61
|
+
'Authorization': 'Bearer YOUR_TOKEN'
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
#### POST Requests (Batch Enrichment)
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
const { data, error } = await client.POST('/v1/enrich/batch', {
|
|
70
|
+
body: {
|
|
71
|
+
workIds: ['OL123W', 'OL456W'],
|
|
72
|
+
force: false
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
if (error) {
|
|
77
|
+
console.error('Failed to enrich:', error)
|
|
78
|
+
} else {
|
|
79
|
+
console.log('Job ID:', data.data.jobId)
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
#### WebSocket Progress Tracking
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
// Start batch job
|
|
87
|
+
const { data: job } = await client.POST('/v1/enrich/batch', {
|
|
88
|
+
body: { workIds: [...] }
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
const jobId = job.data.jobId
|
|
92
|
+
|
|
93
|
+
// Connect to WebSocket
|
|
94
|
+
const ws = new WebSocket(`wss://api.oooefam.net/ws/progress?jobId=${jobId}`)
|
|
95
|
+
|
|
96
|
+
ws.onmessage = (event) => {
|
|
97
|
+
const progress = JSON.parse(event.data)
|
|
98
|
+
console.log(`Progress: ${progress.progress * 100}%`)
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
#### Polling Job Status (Fallback)
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
const { data } = await client.GET('/v1/jobs/{jobId}/status', {
|
|
106
|
+
params: { path: { jobId: 'job-uuid' } }
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
console.log('Job Status:', data.data.status)
|
|
110
|
+
console.log('Progress:', data.data.progress)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## API Reference
|
|
116
|
+
|
|
117
|
+
### Client Creation
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
createBooksTrackClient(options?: {
|
|
121
|
+
baseUrl?: string
|
|
122
|
+
headers?: HeadersInit
|
|
123
|
+
credentials?: RequestCredentials
|
|
124
|
+
})
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Available Endpoints
|
|
128
|
+
|
|
129
|
+
All endpoints are fully typed. Use TypeScript autocomplete to explore:
|
|
130
|
+
|
|
131
|
+
- `GET /health` - Health check
|
|
132
|
+
- `GET /v1/search/isbn` - Search by ISBN
|
|
133
|
+
- `GET /v1/search/title` - Search by title
|
|
134
|
+
- `POST /v1/enrich/batch` - Batch enrichment
|
|
135
|
+
- `POST /v2/import/workflow` - CSV import workflow
|
|
136
|
+
- `GET /v1/jobs/{jobId}/status` - Job status polling
|
|
137
|
+
- `GET /ws/progress` - WebSocket progress (upgrade)
|
|
138
|
+
|
|
139
|
+
### Response Format
|
|
140
|
+
|
|
141
|
+
All API responses follow the canonical `ResponseEnvelope` format:
|
|
142
|
+
|
|
143
|
+
**Success:**
|
|
144
|
+
```typescript
|
|
145
|
+
{
|
|
146
|
+
success: true,
|
|
147
|
+
data: { /* canonical book object */ },
|
|
148
|
+
metadata: {
|
|
149
|
+
source: 'google_books',
|
|
150
|
+
cached: true,
|
|
151
|
+
timestamp: '2025-01-10T12:00:00Z'
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Error:**
|
|
157
|
+
```typescript
|
|
158
|
+
{
|
|
159
|
+
success: false,
|
|
160
|
+
error: {
|
|
161
|
+
code: 'RATE_LIMIT_EXCEEDED',
|
|
162
|
+
message: 'Too many requests',
|
|
163
|
+
statusCode: 429,
|
|
164
|
+
retryable: true,
|
|
165
|
+
retryAfterMs: 60000
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Error Handling
|
|
173
|
+
|
|
174
|
+
### Circuit Breaker Errors
|
|
175
|
+
|
|
176
|
+
The API uses circuit breakers for external providers. Handle these gracefully:
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
const { data, error } = await client.GET('/v1/search/isbn', {
|
|
180
|
+
params: { query: { isbn: '...' } }
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
if (error) {
|
|
184
|
+
if (error.code === 'CIRCUIT_OPEN') {
|
|
185
|
+
// Provider is temporarily unavailable
|
|
186
|
+
console.warn('Provider down, try again in 60s')
|
|
187
|
+
} else if (error.retryable) {
|
|
188
|
+
// Safe to retry after retryAfterMs
|
|
189
|
+
setTimeout(() => retry(), error.retryAfterMs)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Common Error Codes
|
|
195
|
+
|
|
196
|
+
- `MISSING_ISBN` - Required parameter missing
|
|
197
|
+
- `RATE_LIMIT_EXCEEDED` - Too many requests
|
|
198
|
+
- `CIRCUIT_OPEN` - External provider circuit breaker open
|
|
199
|
+
- `NOT_FOUND` - Book not found
|
|
200
|
+
- `API_ERROR` - External API failure
|
|
201
|
+
- `INTERNAL_ERROR` - Server error
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Development
|
|
206
|
+
|
|
207
|
+
### Generating Types
|
|
208
|
+
|
|
209
|
+
The SDK is auto-generated from `docs/openapi.yaml`:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
npm run generate
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
This creates `src/schema.ts` with all API types.
|
|
216
|
+
|
|
217
|
+
### Building
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
npm run build
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Output: `dist/index.js` and `dist/index.d.ts`
|
|
224
|
+
|
|
225
|
+
### Publishing (Automated via CI/CD)
|
|
226
|
+
|
|
227
|
+
Publishing is handled automatically by GitHub Actions when:
|
|
228
|
+
- Pushing to `main` branch
|
|
229
|
+
- Creating a new release/tag
|
|
230
|
+
|
|
231
|
+
Manual publish:
|
|
232
|
+
```bash
|
|
233
|
+
npm run prepublishOnly
|
|
234
|
+
npm publish
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Framework Examples
|
|
240
|
+
|
|
241
|
+
### React (with React Query)
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
import { useQuery } from '@tanstack/react-query'
|
|
245
|
+
import { client } from '@bookstrack/api-client'
|
|
246
|
+
|
|
247
|
+
function BookSearch({ isbn }: { isbn: string }) {
|
|
248
|
+
const { data, error, isLoading } = useQuery({
|
|
249
|
+
queryKey: ['book', isbn],
|
|
250
|
+
queryFn: async () => {
|
|
251
|
+
const res = await client.GET('/v1/search/isbn', {
|
|
252
|
+
params: { query: { isbn } }
|
|
253
|
+
})
|
|
254
|
+
if (res.error) throw new Error(res.error.message)
|
|
255
|
+
return res.data.data
|
|
256
|
+
}
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
if (isLoading) return <div>Loading...</div>
|
|
260
|
+
if (error) return <div>Error: {error.message}</div>
|
|
261
|
+
|
|
262
|
+
return <div>{data?.title}</div>
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Vue 3 (with Composition API)
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
import { ref } from 'vue'
|
|
270
|
+
import { client } from '@bookstrack/api-client'
|
|
271
|
+
|
|
272
|
+
export function useBookSearch(isbn: string) {
|
|
273
|
+
const book = ref(null)
|
|
274
|
+
const error = ref(null)
|
|
275
|
+
const loading = ref(false)
|
|
276
|
+
|
|
277
|
+
async function search() {
|
|
278
|
+
loading.value = true
|
|
279
|
+
const res = await client.GET('/v1/search/isbn', {
|
|
280
|
+
params: { query: { isbn } }
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
if (res.error) {
|
|
284
|
+
error.value = res.error
|
|
285
|
+
} else {
|
|
286
|
+
book.value = res.data.data
|
|
287
|
+
}
|
|
288
|
+
loading.value = false
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return { book, error, loading, search }
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Svelte
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
import { writable } from 'svelte/store'
|
|
299
|
+
import { client } from '@bookstrack/api-client'
|
|
300
|
+
|
|
301
|
+
export function createBookStore() {
|
|
302
|
+
const { subscribe, set, update } = writable({
|
|
303
|
+
book: null,
|
|
304
|
+
loading: false,
|
|
305
|
+
error: null
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
async function searchByISBN(isbn: string) {
|
|
309
|
+
update(s => ({ ...s, loading: true }))
|
|
310
|
+
|
|
311
|
+
const { data, error } = await client.GET('/v1/search/isbn', {
|
|
312
|
+
params: { query: { isbn } }
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
if (error) {
|
|
316
|
+
set({ book: null, loading: false, error })
|
|
317
|
+
} else {
|
|
318
|
+
set({ book: data.data, loading: false, error: null })
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return { subscribe, searchByISBN }
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## Migration from Legacy API
|
|
329
|
+
|
|
330
|
+
If you're migrating from the old `/search/*` endpoints (deprecated March 1, 2026):
|
|
331
|
+
|
|
332
|
+
### Before (Legacy)
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
const res = await fetch('https://api.oooefam.net/search/isbn?isbn=123')
|
|
336
|
+
const book = await res.json()
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### After (SDK)
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
import { client } from '@bookstrack/api-client'
|
|
343
|
+
|
|
344
|
+
const { data } = await client.GET('/v1/search/isbn', {
|
|
345
|
+
params: { query: { isbn: '123' } }
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
const book = data.data
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
**Key Changes:**
|
|
352
|
+
- All responses now use `ResponseEnvelope` format
|
|
353
|
+
- Success/error discriminator via `success` field
|
|
354
|
+
- Metadata includes source, cache status, timestamp
|
|
355
|
+
- Error responses include structured error codes
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## Support
|
|
360
|
+
|
|
361
|
+
- **Documentation:** https://github.com/yourusername/bendv3/tree/main/docs
|
|
362
|
+
- **API Contract:** https://github.com/yourusername/bendv3/blob/main/docs/API_CONTRACT.md
|
|
363
|
+
- **Issues:** https://github.com/yourusername/bendv3/issues
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## License
|
|
368
|
+
|
|
369
|
+
MIT
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
**Generated from OpenAPI Spec Version:** (auto-updated on publish)
|
|
374
|
+
**Last Updated:** 2025-11-28
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { paths } from './schema';
|
|
2
|
+
export type { paths, components } from './schema';
|
|
3
|
+
/**
|
|
4
|
+
* Create a type-safe BooksTrack API client
|
|
5
|
+
*
|
|
6
|
+
* @param baseUrl - API base URL (default: https://api.oooefam.net)
|
|
7
|
+
* @param options - Additional fetch options (headers, credentials, etc.)
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { createBooksTrackClient } from '@bookstrack/api-client'
|
|
12
|
+
*
|
|
13
|
+
* const client = createBooksTrackClient({
|
|
14
|
+
* baseUrl: 'https://api.oooefam.net'
|
|
15
|
+
* })
|
|
16
|
+
*
|
|
17
|
+
* // Type-safe API calls
|
|
18
|
+
* const { data, error } = await client.GET('/v1/search/isbn', {
|
|
19
|
+
* params: { query: { isbn: '9780439708180' } }
|
|
20
|
+
* })
|
|
21
|
+
*
|
|
22
|
+
* if (error) {
|
|
23
|
+
* console.error('API Error:', error)
|
|
24
|
+
* } else {
|
|
25
|
+
* console.log('Book:', data.data)
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function createBooksTrackClient(options?: {
|
|
30
|
+
baseUrl?: string;
|
|
31
|
+
headers?: HeadersInit;
|
|
32
|
+
credentials?: RequestCredentials;
|
|
33
|
+
}): import("openapi-fetch").Client<paths, `${string}/${string}`>;
|
|
34
|
+
/**
|
|
35
|
+
* Default client instance for convenience
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* import { client } from '@bookstrack/api-client'
|
|
40
|
+
*
|
|
41
|
+
* const { data } = await client.GET('/health')
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare const client: import("openapi-fetch").Client<paths, `${string}/${string}`>;
|
|
45
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAA;AAErC,YAAY,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,CAAC,EAAE;IAC/C,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB,WAAW,CAAC,EAAE,kBAAkB,CAAA;CACjC,gEAWA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,MAAM,8DAA2B,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import createClient from 'openapi-fetch';
|
|
2
|
+
/**
|
|
3
|
+
* Create a type-safe BooksTrack API client
|
|
4
|
+
*
|
|
5
|
+
* @param baseUrl - API base URL (default: https://api.oooefam.net)
|
|
6
|
+
* @param options - Additional fetch options (headers, credentials, etc.)
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { createBooksTrackClient } from '@bookstrack/api-client'
|
|
11
|
+
*
|
|
12
|
+
* const client = createBooksTrackClient({
|
|
13
|
+
* baseUrl: 'https://api.oooefam.net'
|
|
14
|
+
* })
|
|
15
|
+
*
|
|
16
|
+
* // Type-safe API calls
|
|
17
|
+
* const { data, error } = await client.GET('/v1/search/isbn', {
|
|
18
|
+
* params: { query: { isbn: '9780439708180' } }
|
|
19
|
+
* })
|
|
20
|
+
*
|
|
21
|
+
* if (error) {
|
|
22
|
+
* console.error('API Error:', error)
|
|
23
|
+
* } else {
|
|
24
|
+
* console.log('Book:', data.data)
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export function createBooksTrackClient(options) {
|
|
29
|
+
const baseUrl = options?.baseUrl || 'https://api.oooefam.net';
|
|
30
|
+
return createClient({
|
|
31
|
+
baseUrl,
|
|
32
|
+
headers: {
|
|
33
|
+
'Content-Type': 'application/json',
|
|
34
|
+
...options?.headers
|
|
35
|
+
},
|
|
36
|
+
credentials: options?.credentials
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Default client instance for convenience
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* import { client } from '@bookstrack/api-client'
|
|
45
|
+
*
|
|
46
|
+
* const { data } = await client.GET('/health')
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export const client = createBooksTrackClient();
|