sound-tank 1.3.0 โ 1.3.1
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/CHANGELOG.md +6 -0
- package/README.md +665 -21
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -1,37 +1,681 @@
|
|
|
1
|
-
<h1 align="center">
|
|
2
|
-
Sound Tank
|
|
3
|
-
</h1>
|
|
1
|
+
<h1 align="center">๐ธ Sound Tank</h1>
|
|
4
2
|
|
|
5
3
|
<p align="center">
|
|
6
|
-
|
|
4
|
+
<strong>TypeScript SDK for the <a href="https://reverb.com">Reverb Marketplace</a> API</strong>
|
|
7
5
|
</p>
|
|
8
6
|
|
|
9
7
|
<p align="center">
|
|
10
|
-
|
|
8
|
+
A modern, type-safe interface for buying, selling, and managing musical instruments and gear on Reverb
|
|
11
9
|
</p>
|
|
12
10
|
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://www.npmjs.com/package/sound-tank"><img src="https://img.shields.io/npm/v/sound-tank.svg" alt="npm version"></a>
|
|
13
|
+
<a href="https://www.npmjs.com/package/sound-tank"><img src="https://img.shields.io/npm/dm/sound-tank.svg" alt="npm downloads"></a>
|
|
14
|
+
<a href="https://github.com/ZacharyEggert/sound-tank/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
|
|
15
|
+
<a href="https://github.com/ZacharyEggert/sound-tank/actions"><img src="https://github.com/ZacharyEggert/sound-tank/workflows/CI/badge.svg" alt="Build Status"></a>
|
|
16
|
+
<img src="https://img.shields.io/badge/TypeScript-5.8-blue" alt="TypeScript">
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install sound-tank
|
|
25
|
+
# or
|
|
26
|
+
yarn add sound-tank
|
|
27
|
+
# or
|
|
28
|
+
pnpm add sound-tank
|
|
29
|
+
```
|
|
16
30
|
|
|
17
31
|
```typescript
|
|
18
32
|
import Reverb from 'sound-tank';
|
|
19
33
|
|
|
20
|
-
const
|
|
34
|
+
const reverb = new Reverb({ apiKey: process.env.REVERB_API_KEY });
|
|
35
|
+
const { data } = await reverb.getMyListings({ perPage: 10, state: 'live' });
|
|
36
|
+
|
|
37
|
+
data.listings.forEach(listing => {
|
|
38
|
+
console.log(`${listing.title}: ${listing.price.display}`);
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Features
|
|
43
|
+
|
|
44
|
+
- โ
**Full TypeScript Support** - Complete type definitions for all API entities
|
|
45
|
+
- โ
**Automatic Pagination** - Built-in helpers to fetch all results seamlessly
|
|
46
|
+
- โ
**Configuration Management** - Easy setup for currency, locale, and shipping preferences
|
|
47
|
+
- โ
**Comprehensive Coverage** - Listings, orders, and arbitrary endpoint access
|
|
48
|
+
- โ
**HTTP Client Abstraction** - Clean architecture with testable mock implementations
|
|
49
|
+
- โ
**Dual Module Support** - Both CommonJS and ESM builds included
|
|
50
|
+
- โ
**Well Tested** - Extensive unit and integration test coverage
|
|
51
|
+
- โ
**Minimal Dependencies** - Only requires axios
|
|
52
|
+
|
|
53
|
+
## Table of Contents
|
|
54
|
+
|
|
55
|
+
- [Installation](#installation)
|
|
56
|
+
- [Getting Started](#getting-started)
|
|
57
|
+
- [Configuration](#configuration)
|
|
58
|
+
- [API Methods](#api-methods)
|
|
59
|
+
- [getMyListings](#getmylistingsoptions)
|
|
60
|
+
- [getAllMyListings](#getallmylistingsoptions)
|
|
61
|
+
- [getOneListing](#getonelistingoptions)
|
|
62
|
+
- [getMyOrders](#getmyordersoptions)
|
|
63
|
+
- [getArbitraryEndpoint](#getarbitraryendpointoptions)
|
|
64
|
+
- [TypeScript Usage](#typescript-usage)
|
|
65
|
+
- [Advanced Features](#advanced-features)
|
|
66
|
+
- [Examples](#examples)
|
|
67
|
+
- [Development](#development)
|
|
68
|
+
- [Testing](#testing)
|
|
69
|
+
- [Contributing](#contributing)
|
|
70
|
+
- [License](#license)
|
|
71
|
+
|
|
72
|
+
## Installation
|
|
73
|
+
|
|
74
|
+
Install via your preferred package manager:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npm install sound-tank
|
|
78
|
+
# or
|
|
79
|
+
yarn add sound-tank
|
|
80
|
+
# or
|
|
81
|
+
pnpm add sound-tank
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Getting a Reverb API Key
|
|
85
|
+
|
|
86
|
+
1. Visit [Reverb API Settings](https://reverb.com/my/api)
|
|
87
|
+
2. Generate a new API token
|
|
88
|
+
3. Set the appropriate scopes for your use case
|
|
89
|
+
4. Store securely in environment variables
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# .env
|
|
93
|
+
REVERB_API_KEY=your_api_key_here
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
You can use the provided `.env.example` file as a template:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
cp .env.example .env
|
|
100
|
+
# Edit .env and add your REVERB_API_KEY
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Getting Started
|
|
104
|
+
|
|
105
|
+
### Initialize the Client
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import Reverb from 'sound-tank';
|
|
109
|
+
|
|
110
|
+
const reverb = new Reverb({
|
|
111
|
+
apiKey: process.env.REVERB_API_KEY,
|
|
112
|
+
displayCurrency: 'USD', // optional
|
|
113
|
+
locale: 'en', // optional
|
|
114
|
+
version: '3.0', // optional
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Fetch Your Listings
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
const response = await reverb.getMyListings({
|
|
122
|
+
perPage: 25,
|
|
123
|
+
page: 1,
|
|
124
|
+
state: 'live',
|
|
125
|
+
query: 'Gibson Les Paul'
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
console.log(response.data.listings);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Error Handling
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
try {
|
|
135
|
+
const response = await reverb.getMyListings({ state: 'live' });
|
|
136
|
+
console.log(`Found ${response.data.listings.length} listings`);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error('Failed to fetch listings:', error.message);
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Configuration
|
|
143
|
+
|
|
144
|
+
### Constructor Options
|
|
145
|
+
|
|
146
|
+
| Option | Type | Required | Default | Description |
|
|
147
|
+
|--------|------|----------|---------|-------------|
|
|
148
|
+
| `apiKey` | `string` | โ
Yes | - | Your Reverb API key |
|
|
149
|
+
| `version` | `string` | No | `'3.0'` | API version to use |
|
|
150
|
+
| `rootEndpoint` | `string` | No | `'https://api.reverb.com/api'` | API base URL |
|
|
151
|
+
| `displayCurrency` | `string` | No | `'USD'` | Currency for price display |
|
|
152
|
+
| `locale` | `string` | No | `'en'` | Language locale (e.g., 'en', 'fr', 'de') |
|
|
153
|
+
| `shippingRegion` | `string` | No | `undefined` | Shipping region code (e.g., 'US', 'EU') |
|
|
154
|
+
|
|
155
|
+
### Runtime Configuration
|
|
156
|
+
|
|
157
|
+
You can update configuration after initialization using setters:
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
reverb.displayCurrency = 'EUR';
|
|
161
|
+
reverb.locale = 'fr';
|
|
162
|
+
reverb.shippingRegion = 'FR';
|
|
163
|
+
reverb.version = '3.0';
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
These changes automatically update the internal headers and configuration for subsequent API requests.
|
|
167
|
+
|
|
168
|
+
## API Methods
|
|
169
|
+
|
|
170
|
+
### getMyListings(options?)
|
|
171
|
+
|
|
172
|
+
Fetch a paginated list of your listings.
|
|
173
|
+
|
|
174
|
+
**Parameters:**
|
|
175
|
+
- `perPage?: number` - Items per page
|
|
176
|
+
- `page?: number` - Page number (starts at 1)
|
|
177
|
+
- `query?: string` - Search query to filter listings
|
|
178
|
+
- `state?: string` - Filter by state: `'live'`, `'sold'`, `'draft'`, or `'all'`
|
|
179
|
+
|
|
180
|
+
**Returns:** `Promise<AxiosResponse<PaginatedReverbResponse<{ listings: Listing[] }>>>`
|
|
181
|
+
|
|
182
|
+
**Example:**
|
|
183
|
+
```typescript
|
|
184
|
+
const response = await reverb.getMyListings({
|
|
185
|
+
perPage: 50,
|
|
186
|
+
page: 1,
|
|
187
|
+
state: 'live',
|
|
188
|
+
query: 'Fender Stratocaster'
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const { listings } = response.data;
|
|
192
|
+
console.log(`Page 1: ${listings.length} listings`);
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
### getAllMyListings(options?)
|
|
198
|
+
|
|
199
|
+
Automatically fetches **all** listings across all pages using automatic pagination.
|
|
200
|
+
|
|
201
|
+
**Parameters:**
|
|
202
|
+
- `query?: string` - Search query to filter listings
|
|
203
|
+
- `state?: ListingStates` - Filter by state: `ListingStates.LIVE`, `ListingStates.SOLD`, or `ListingStates.DRAFT`
|
|
204
|
+
|
|
205
|
+
**Returns:** `Promise<AxiosResponse<Listing[]>>`
|
|
206
|
+
|
|
207
|
+
**Example:**
|
|
208
|
+
```typescript
|
|
209
|
+
const response = await reverb.getAllMyListings({ state: 'live' });
|
|
210
|
+
const allListings = response.data; // All listings from all pages
|
|
211
|
+
|
|
212
|
+
console.log(`Total listings: ${allListings.length}`);
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
> **Note:** This method automatically handles pagination by fetching all pages sequentially until all results are retrieved.
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
### getOneListing(options)
|
|
220
|
+
|
|
221
|
+
Fetch a single listing by ID.
|
|
222
|
+
|
|
223
|
+
**Parameters:**
|
|
224
|
+
- `id: string` - Listing ID (required)
|
|
225
|
+
|
|
226
|
+
**Returns:** `Promise<AxiosResponse<Listing>>`
|
|
227
|
+
|
|
228
|
+
**Example:**
|
|
229
|
+
```typescript
|
|
230
|
+
const response = await reverb.getOneListing({ id: '12345' });
|
|
231
|
+
const listing = response.data;
|
|
232
|
+
|
|
233
|
+
console.log(`${listing.title} - ${listing.price.display}`);
|
|
234
|
+
console.log(`Condition: ${listing.condition.displayName}`);
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
### getMyOrders(options?)
|
|
240
|
+
|
|
241
|
+
Fetch your orders with pagination.
|
|
242
|
+
|
|
243
|
+
**Parameters:**
|
|
244
|
+
- `page?: number` - Page number (starts at 1)
|
|
245
|
+
- `perPage?: number` - Items per page
|
|
246
|
+
|
|
247
|
+
**Returns:** `Promise<AxiosResponse<PaginatedReverbResponse<{ orders: Order[] }>>>`
|
|
248
|
+
|
|
249
|
+
**Example:**
|
|
250
|
+
```typescript
|
|
251
|
+
const response = await reverb.getMyOrders({
|
|
252
|
+
page: 1,
|
|
253
|
+
perPage: 25
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const { orders } = response.data;
|
|
257
|
+
orders.forEach(order => {
|
|
258
|
+
console.log(`Order ${order.order_number}: ${order.status}`);
|
|
259
|
+
});
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
### getArbitraryEndpoint(options)
|
|
265
|
+
|
|
266
|
+
Make requests to any Reverb API endpoint not explicitly covered by other methods.
|
|
267
|
+
|
|
268
|
+
**Parameters:**
|
|
269
|
+
- `url: string` - Endpoint URL (absolute or relative to root endpoint)
|
|
270
|
+
- `params?: object` - Query parameters
|
|
271
|
+
|
|
272
|
+
**Returns:** `Promise<AxiosResponse<unknown>>`
|
|
273
|
+
|
|
274
|
+
**Example:**
|
|
275
|
+
```typescript
|
|
276
|
+
// Fetch categories
|
|
277
|
+
const categories = await reverb.getArbitraryEndpoint({
|
|
278
|
+
url: '/categories/flat'
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Fetch listing conditions
|
|
282
|
+
const conditions = await reverb.getArbitraryEndpoint({
|
|
283
|
+
url: '/listing_conditions'
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
console.log(categories.data);
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## TypeScript Usage
|
|
290
|
+
|
|
291
|
+
Sound Tank is written in TypeScript and provides comprehensive type definitions for the entire Reverb API.
|
|
292
|
+
|
|
293
|
+
### Importing Types
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
import Reverb, {
|
|
297
|
+
Listing,
|
|
298
|
+
Order,
|
|
299
|
+
Price,
|
|
300
|
+
ListingStates,
|
|
301
|
+
ListingCondition,
|
|
302
|
+
ShippingRate,
|
|
303
|
+
Category,
|
|
304
|
+
ReverbOptions
|
|
305
|
+
} from 'sound-tank';
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Working with Typed Responses
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
const response = await reverb.getMyListings();
|
|
312
|
+
const listings: Listing[] = response.data.listings;
|
|
313
|
+
|
|
314
|
+
listings.forEach((listing: Listing) => {
|
|
315
|
+
const price: Price = listing.price;
|
|
316
|
+
const condition: ListingCondition = listing.condition;
|
|
317
|
+
|
|
318
|
+
console.log(`${listing.title}`);
|
|
319
|
+
console.log(` Price: ${price.display} (${price.currency})`);
|
|
320
|
+
console.log(` Condition: ${condition.displayName}`);
|
|
321
|
+
console.log(` Year: ${listing.year}`);
|
|
322
|
+
});
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Available Types
|
|
326
|
+
|
|
327
|
+
The SDK exports comprehensive TypeScript types including:
|
|
328
|
+
|
|
329
|
+
**Listing Types:**
|
|
330
|
+
- `Listing` - Complete listing data with make, model, price, condition, shipping, etc.
|
|
331
|
+
- `ListingState` - Listing status information
|
|
332
|
+
- `ListingStates` - Enum for state values (LIVE, SOLD, DRAFT)
|
|
333
|
+
- `ListingCondition` - Item condition with UUID and display name
|
|
334
|
+
- `ListingShipping` - Shipping information and rates
|
|
335
|
+
- `ListingStats` - View and watch counts
|
|
21
336
|
|
|
22
|
-
|
|
23
|
-
|
|
337
|
+
**Order Types:**
|
|
338
|
+
- `Order` - Complete order information with buyer, seller, shipping, pricing details
|
|
339
|
+
- `OrderStatus` - Order status information
|
|
340
|
+
- `ShippingAddress` - Complete address information
|
|
24
341
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
state: 'all',
|
|
29
|
-
});
|
|
342
|
+
**Pricing Types:**
|
|
343
|
+
- `Price` - Currency-aware price with amount, currency, symbol, and formatted display
|
|
344
|
+
- `ShippingRate` - Regional shipping costs
|
|
30
345
|
|
|
31
|
-
|
|
346
|
+
**Other Types:**
|
|
347
|
+
- `Category` - Product categorization
|
|
348
|
+
- `Link` - HATEOAS navigation links
|
|
349
|
+
- `ReverbOptions` - SDK configuration options
|
|
350
|
+
|
|
351
|
+
## Advanced Features
|
|
352
|
+
|
|
353
|
+
### Automatic Pagination
|
|
354
|
+
|
|
355
|
+
The `getAllMyListings` method demonstrates automatic pagination, fetching all results across multiple API pages:
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
// Manually paginate
|
|
359
|
+
let page = 1;
|
|
360
|
+
let allListings = [];
|
|
361
|
+
let response;
|
|
362
|
+
|
|
363
|
+
do {
|
|
364
|
+
response = await reverb.getMyListings({ page, perPage: 50 });
|
|
365
|
+
allListings = allListings.concat(response.data.listings);
|
|
366
|
+
page++;
|
|
367
|
+
} while (response.data.listings.length === 50);
|
|
368
|
+
|
|
369
|
+
// Or use the built-in helper
|
|
370
|
+
const autoResponse = await reverb.getAllMyListings();
|
|
371
|
+
const listings = autoResponse.data; // Same result, simpler code
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### HTTP Client Abstraction
|
|
375
|
+
|
|
376
|
+
Sound Tank uses an HTTP client abstraction for testability:
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
import { MockHttpClient } from 'sound-tank/http';
|
|
380
|
+
|
|
381
|
+
// Use mock client for testing
|
|
382
|
+
const mockClient = new MockHttpClient();
|
|
383
|
+
// Your tests here
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### Configuration Access
|
|
387
|
+
|
|
388
|
+
Access the internal configuration and headers:
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
const config = reverb.config;
|
|
392
|
+
console.log(config.rootEndpoint); // 'https://api.reverb.com/api'
|
|
393
|
+
console.log(config.displayCurrency); // 'USD'
|
|
394
|
+
console.log(config.locale); // 'en'
|
|
395
|
+
|
|
396
|
+
const headers = reverb.headers;
|
|
397
|
+
console.log(headers['Authorization']); // 'Bearer your_api_key'
|
|
398
|
+
console.log(headers['X-Display-Currency']); // 'USD'
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
## Examples
|
|
402
|
+
|
|
403
|
+
### Find All Guitars Under $1000
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
const reverb = new Reverb({ apiKey: process.env.REVERB_API_KEY });
|
|
407
|
+
|
|
408
|
+
const response = await reverb.getAllMyListings({ query: 'guitar' });
|
|
409
|
+
const affordable = response.data.filter(listing =>
|
|
410
|
+
listing.price.amount_cents < 100000 // $1000 = 100,000 cents
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
console.log(`Found ${affordable.length} guitars under $1000`);
|
|
414
|
+
affordable.forEach(listing => {
|
|
415
|
+
console.log(` ${listing.title}: ${listing.price.display}`);
|
|
416
|
+
});
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Multi-Currency Price Display
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
const reverb = new Reverb({ apiKey: process.env.REVERB_API_KEY });
|
|
423
|
+
|
|
424
|
+
// Get prices in USD
|
|
425
|
+
reverb.displayCurrency = 'USD';
|
|
426
|
+
const usdResponse = await reverb.getMyListings({ perPage: 5 });
|
|
427
|
+
console.log('USD Prices:');
|
|
428
|
+
usdResponse.data.listings.forEach(l =>
|
|
429
|
+
console.log(` ${l.title}: ${l.price.display}`)
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
// Switch to EUR
|
|
433
|
+
reverb.displayCurrency = 'EUR';
|
|
434
|
+
const eurResponse = await reverb.getMyListings({ perPage: 5 });
|
|
435
|
+
console.log('\nEUR Prices:');
|
|
436
|
+
eurResponse.data.listings.forEach(l =>
|
|
437
|
+
console.log(` ${l.title}: ${l.price.display}`)
|
|
438
|
+
);
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### Export Listings to CSV
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
const response = await reverb.getAllMyListings({ state: 'live' });
|
|
445
|
+
|
|
446
|
+
const csvHeader = 'ID,Title,Price,Currency,Condition,Year,State\n';
|
|
447
|
+
const csvRows = response.data.map(listing =>
|
|
448
|
+
`${listing.id},"${listing.title}",${listing.price.amount},${listing.price.currency},${listing.condition.displayName},${listing.year},${listing.state.slug}`
|
|
449
|
+
).join('\n');
|
|
450
|
+
|
|
451
|
+
const csv = csvHeader + csvRows;
|
|
452
|
+
console.log(csv);
|
|
453
|
+
// Save to file or send via API
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### Search and Filter
|
|
457
|
+
|
|
458
|
+
```typescript
|
|
459
|
+
// Search for specific items
|
|
460
|
+
const response = await reverb.getMyListings({
|
|
461
|
+
query: 'Les Paul',
|
|
462
|
+
state: 'live',
|
|
463
|
+
perPage: 100
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// Filter by condition
|
|
467
|
+
const excellent = response.data.listings.filter(
|
|
468
|
+
listing => listing.condition.displayName === 'Excellent'
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
// Filter by year
|
|
472
|
+
const vintage = response.data.listings.filter(
|
|
473
|
+
listing => parseInt(listing.year) < 1980
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
console.log(`Found ${excellent.length} in excellent condition`);
|
|
477
|
+
console.log(`Found ${vintage.length} vintage items`);
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
## Development
|
|
481
|
+
|
|
482
|
+
### Prerequisites
|
|
483
|
+
|
|
484
|
+
- **Node.js** 18 or higher
|
|
485
|
+
- **Yarn** package manager
|
|
486
|
+
|
|
487
|
+
### Setup
|
|
488
|
+
|
|
489
|
+
```bash
|
|
490
|
+
# Clone the repository
|
|
491
|
+
git clone https://github.com/ZacharyEggert/sound-tank.git
|
|
492
|
+
cd sound-tank
|
|
493
|
+
|
|
494
|
+
# Install dependencies
|
|
495
|
+
yarn install
|
|
496
|
+
|
|
497
|
+
# Copy environment template
|
|
498
|
+
cp .env.example .env
|
|
499
|
+
# Edit .env and add your REVERB_API_KEY
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### Project Structure
|
|
32
503
|
|
|
33
|
-
listings.forEach((listing) => {
|
|
34
|
-
console.log(listing.title);
|
|
35
|
-
});
|
|
36
|
-
})();
|
|
37
504
|
```
|
|
505
|
+
sound-tank/
|
|
506
|
+
โโโ src/
|
|
507
|
+
โ โโโ Reverb.ts # Main SDK class
|
|
508
|
+
โ โโโ index.ts # Entry point
|
|
509
|
+
โ โโโ types.ts # TypeScript type definitions
|
|
510
|
+
โ โโโ config/
|
|
511
|
+
โ โ โโโ ReverbConfig.ts # Configuration management
|
|
512
|
+
โ โโโ http/
|
|
513
|
+
โ โ โโโ HttpClient.ts # HTTP client interface
|
|
514
|
+
โ โ โโโ AxiosHttpClient.ts # Axios implementation
|
|
515
|
+
โ โ โโโ MockHttpClient.ts # Mock for testing
|
|
516
|
+
โ โโโ methods/
|
|
517
|
+
โ โ โโโ listings/ # Listing operations
|
|
518
|
+
โ โ โโโ orders/ # Order operations
|
|
519
|
+
โ โโโ utils/ # Helper utilities
|
|
520
|
+
โโโ tests/ # Test files
|
|
521
|
+
โโโ dist/ # Build output (git-ignored)
|
|
522
|
+
โโโ package.json
|
|
523
|
+
โโโ tsconfig.json # TypeScript configuration
|
|
524
|
+
โโโ tsup.config.ts # Build configuration
|
|
525
|
+
โโโ vite.config.mts # Test configuration
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### Available Scripts
|
|
529
|
+
|
|
530
|
+
| Command | Description |
|
|
531
|
+
|---------|-------------|
|
|
532
|
+
| `yarn dev` | Run tests in watch mode during development |
|
|
533
|
+
| `yarn test` | Run all tests once |
|
|
534
|
+
| `yarn build` | Build production bundles (CJS + ESM + declarations) |
|
|
535
|
+
| `yarn lint` | Type-check code with TypeScript compiler |
|
|
536
|
+
| `yarn ci` | Run full CI pipeline (install, lint, test, build) |
|
|
537
|
+
| `yarn release` | Build and publish to npm (uses changesets) |
|
|
538
|
+
|
|
539
|
+
### Build System
|
|
540
|
+
|
|
541
|
+
Sound Tank uses [tsup](https://tsup.egoist.dev/) for building:
|
|
542
|
+
|
|
543
|
+
- **Dual Output**: CommonJS (`dist/index.js`) and ESM (`dist/index.mjs`)
|
|
544
|
+
- **Type Declarations**: Full TypeScript `.d.ts` files
|
|
545
|
+
- **Source Maps**: Included for debugging
|
|
546
|
+
- **Tree-shaking**: Optimized bundles
|
|
547
|
+
- **External Dependencies**: `axios` marked as external (not bundled)
|
|
548
|
+
|
|
549
|
+
Build outputs:
|
|
550
|
+
```
|
|
551
|
+
dist/
|
|
552
|
+
โโโ index.js # CommonJS
|
|
553
|
+
โโโ index.mjs # ES Modules
|
|
554
|
+
โโโ index.d.ts # TypeScript declarations
|
|
555
|
+
โโโ *.map # Source maps
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
## Testing
|
|
559
|
+
|
|
560
|
+
Sound Tank uses [Vitest](https://vitest.dev/) for testing with comprehensive coverage.
|
|
561
|
+
|
|
562
|
+
### Run Tests
|
|
563
|
+
|
|
564
|
+
```bash
|
|
565
|
+
# Run all tests once
|
|
566
|
+
yarn test
|
|
567
|
+
|
|
568
|
+
# Run tests in watch mode (for development)
|
|
569
|
+
yarn dev
|
|
570
|
+
|
|
571
|
+
# Run tests with coverage report
|
|
572
|
+
yarn test --coverage
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
### Test Structure
|
|
576
|
+
|
|
577
|
+
- **Unit Tests**: Test individual functions in isolation
|
|
578
|
+
- **Integration Tests**: Test real API interactions (require `REVERB_API_KEY` in `.env`)
|
|
579
|
+
|
|
580
|
+
### Writing Tests
|
|
581
|
+
|
|
582
|
+
Integration tests require a valid API key:
|
|
583
|
+
|
|
584
|
+
```typescript
|
|
585
|
+
import { config } from 'dotenv';
|
|
586
|
+
config(); // Load .env
|
|
587
|
+
|
|
588
|
+
const reverb = new Reverb({ apiKey: process.env.REVERB_API_KEY });
|
|
589
|
+
|
|
590
|
+
// Your tests here
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
Tests are located in the `tests/` directory and mirror the `src/` structure.
|
|
594
|
+
|
|
595
|
+
## Contributing
|
|
596
|
+
|
|
597
|
+
Contributions are welcome! Here's how to get started:
|
|
598
|
+
|
|
599
|
+
### Process
|
|
600
|
+
|
|
601
|
+
1. **Fork** the repository on GitHub
|
|
602
|
+
2. **Clone** your fork locally
|
|
603
|
+
3. **Create** a feature branch: `git checkout -b feature/amazing-feature`
|
|
604
|
+
4. **Make** your changes with clear, focused commits
|
|
605
|
+
5. **Test** thoroughly: `yarn test`
|
|
606
|
+
6. **Lint** your code: `yarn lint`
|
|
607
|
+
7. **Commit** with descriptive messages
|
|
608
|
+
8. **Push** to your fork: `git push origin feature/amazing-feature`
|
|
609
|
+
9. **Open** a Pull Request
|
|
610
|
+
|
|
611
|
+
### Guidelines
|
|
612
|
+
|
|
613
|
+
- Write **TypeScript** with strict types
|
|
614
|
+
- Add **tests** for new features
|
|
615
|
+
- Update **documentation** as needed
|
|
616
|
+
- Follow **existing code style** (Prettier configured)
|
|
617
|
+
- Ensure all **tests pass** (`yarn test`)
|
|
618
|
+
- Keep commits **atomic** and well-described
|
|
619
|
+
- Run **full CI** locally before pushing: `yarn ci`
|
|
620
|
+
|
|
621
|
+
### Code Style
|
|
622
|
+
|
|
623
|
+
This project uses:
|
|
624
|
+
- **TypeScript** strict mode
|
|
625
|
+
- **Prettier** for formatting (run automatically)
|
|
626
|
+
- **ESLint** for linting
|
|
627
|
+
- 2-space indentation
|
|
628
|
+
- Single quotes for strings
|
|
629
|
+
- Trailing commas
|
|
630
|
+
|
|
631
|
+
### Changesets
|
|
632
|
+
|
|
633
|
+
This project uses [Changesets](https://github.com/changesets/changesets) for version management:
|
|
634
|
+
|
|
635
|
+
```bash
|
|
636
|
+
# After making changes, create a changeset
|
|
637
|
+
npx changeset
|
|
638
|
+
|
|
639
|
+
# Follow prompts to describe your changes
|
|
640
|
+
# Commit the generated changeset file
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
### Release Process
|
|
644
|
+
|
|
645
|
+
Releases are automated via GitHub Actions:
|
|
646
|
+
|
|
647
|
+
1. Create and commit a changeset for your changes
|
|
648
|
+
2. Push to `main` branch (after PR approval)
|
|
649
|
+
3. Changesets GitHub Action creates a "Version Packages" PR
|
|
650
|
+
4. Review and merge the Version Packages PR
|
|
651
|
+
5. Package automatically publishes to npm with provenance
|
|
652
|
+
|
|
653
|
+
The project uses **OIDC trusted publishing** for npm, so no `NPM_TOKEN` is needed.
|
|
654
|
+
|
|
655
|
+
## Links & Resources
|
|
656
|
+
|
|
657
|
+
- **npm Package**: [sound-tank on npm](https://www.npmjs.com/package/sound-tank)
|
|
658
|
+
- **GitHub Repository**: [ZacharyEggert/sound-tank](https://github.com/ZacharyEggert/sound-tank)
|
|
659
|
+
- **Report Issues**: [GitHub Issues](https://github.com/ZacharyEggert/sound-tank/issues)
|
|
660
|
+
- **Reverb API Docs**: [reverb.com/page/api](https://reverb.com/page/api)
|
|
661
|
+
- **Reverb Marketplace**: [reverb.com](https://reverb.com)
|
|
662
|
+
|
|
663
|
+
## License
|
|
664
|
+
|
|
665
|
+
MIT ยฉ [Zachary Eggert](https://github.com/ZacharyEggert)
|
|
666
|
+
|
|
667
|
+
See [LICENSE](./LICENSE) for details.
|
|
668
|
+
|
|
669
|
+
---
|
|
670
|
+
|
|
671
|
+
<p align="center">
|
|
672
|
+
Made with โฅ by <a href="https://github.com/ZacharyEggert">Zachary Eggert</a>
|
|
673
|
+
</p>
|
|
674
|
+
|
|
675
|
+
<p align="center">
|
|
676
|
+
<a href="https://github.com/ZacharyEggert/sound-tank/issues">Report Bug</a>
|
|
677
|
+
ยท
|
|
678
|
+
<a href="https://github.com/ZacharyEggert/sound-tank/issues">Request Feature</a>
|
|
679
|
+
ยท
|
|
680
|
+
<a href="https://reverb.com">Reverb.com</a>
|
|
681
|
+
</p>
|