@harmoni-org/sdk 0.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Harmoni SDK
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,694 @@
1
+ # ๐ŸŽต Harmoni SDK
2
+
3
+ > A powerful, type-safe SDK for seamless backend integration with comprehensive utilities and tools.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@harmoni-org/sdk.svg)](https://www.npmjs.com/package/@harmoni-org/sdk)
6
+ [![npm downloads](https://img.shields.io/npm/dm/@harmoni-org/sdk.svg)](https://www.npmjs.com/package/@harmoni-org/sdk)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
8
+ [![CI](https://github.com/HarmOni-Official/harmoni-sdk/workflows/CI/badge.svg)](https://github.com/HarmOni-Official/harmoni-sdk/actions)
9
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.3-blue.svg)](https://www.typescriptlang.org/)
10
+
11
+ ## โœจ Features
12
+
13
+ - ๐Ÿš€ **Modern Architecture** - Built with TypeScript, supports ESM & CJS
14
+ - ๐Ÿ” **Real Token Refresh** - Auto-refresh with proper request retry on 401
15
+ - ๐Ÿ”„ **Smart Retry** - Exponential backoff that doesn't break POST requests
16
+ - ๐Ÿ“ค **Upload Support** - Automatic FormData handling with progress tracking
17
+ - ๐ŸŒ **SSR-Safe** - Works in Node.js, browsers, and edge runtimes
18
+ - ๐ŸŽฏ **Type Safety** - Full TypeScript support with comprehensive types
19
+ - ๐Ÿงฉ **Modular Design** - Easy to extend with new modules
20
+ - ๐ŸŽฌ **Watch Together** - Real-time synchronized video playback with WebSocket
21
+ - ๐Ÿ’ฌ **Real-time Chat** - Built-in chat for Watch Together rooms
22
+ - ๐ŸŽฎ **Player Integration** - Abstract interfaces for any video player (HTML5, VLC, custom)
23
+ - ๐Ÿ› ๏ธ **Rich Utilities** - String, date, object, validation helpers included
24
+ - ๐Ÿ“ฆ **Tree-shakeable** - Only bundle what you use (~8KB core, ~15KB with utils)
25
+ - โœ… **Well Tested** - Comprehensive test coverage
26
+ - ๐Ÿ“š **Production Ready** - Battle-tested patterns, see [PRODUCTION_READY.md](docs/guides/PRODUCTION_READY.md)
27
+
28
+ ## ๐Ÿ“ฆ Installation
29
+
30
+ ```bash
31
+ npm install @harmoni-org/sdk
32
+ ```
33
+
34
+ ```bash
35
+ yarn add @harmoni-org/sdk
36
+ ```
37
+
38
+ ```bash
39
+ pnpm add @harmoni/sdk
40
+ ```
41
+
42
+ ## ๐Ÿš€ Quick Start
43
+
44
+ ### Basic Usage
45
+
46
+ ```typescript
47
+ import { HarmoniSDK } from '@harmoni-org/sdk';
48
+
49
+ // Initialize the SDK
50
+ const sdk = new HarmoniSDK({
51
+ baseURL: 'https://api.yourbackend.com',
52
+ timeout: 30000,
53
+ autoRefreshToken: true, // Automatically refresh tokens on 401
54
+ retryConfig: {
55
+ maxRetries: 3,
56
+ retryDelay: 1000,
57
+ retryableStatuses: [408, 429, 500, 502, 503, 504],
58
+ },
59
+ });
60
+
61
+ // Login
62
+ const user = await sdk.auth.login({
63
+ emailOrUsername: 'user@example.com',
64
+ password: 'secure_password',
65
+ });
66
+ console.log('Logged in as:', user.username);
67
+
68
+ // Update user profile
69
+ await sdk.user.updateProfile({
70
+ username: 'newusername',
71
+ });
72
+
73
+ // Watch Together - Synchronized playback
74
+ await sdk.watchTogether.connect();
75
+ const room = await sdk.watchTogether.createRoom({ roomName: 'Movie Night' });
76
+
77
+ sdk.watchTogether.onRoomUpdate((update) => {
78
+ if (update.metadata.action === 'play') player.play();
79
+ });
80
+
81
+ sdk.watchTogether.play(); // Syncs to all users
82
+ await sdk.watchTogether.sendMessage('Hello! ๐Ÿ‘‹');
83
+ ```
84
+
85
+ > **Note:** The SDK is fully SSR-safe and works in Node.js, browsers, and edge runtimes.
86
+
87
+ ### Advanced Configuration
88
+
89
+ ```typescript
90
+ import { HarmoniSDK } from '@harmoni-org/sdk';
91
+
92
+ const sdk = new HarmoniSDK({
93
+ baseURL: 'https://api.yourbackend.com',
94
+ timeout: 30000,
95
+ headers: {
96
+ 'X-Custom-Header': 'value',
97
+ },
98
+ retryConfig: {
99
+ maxRetries: 3,
100
+ retryDelay: 1000,
101
+ retryableStatuses: [408, 429, 500, 502, 503, 504],
102
+ },
103
+ autoRefreshToken: true,
104
+ });
105
+
106
+ // Set auth token manually if you have it stored
107
+ sdk.setAuthToken('your-access-token');
108
+ ```
109
+
110
+ ## ๐Ÿ“– API Documentation
111
+
112
+ ### Authentication Module
113
+
114
+ ```typescript
115
+ // Check if username is available
116
+ const isAvailable = await sdk.auth.isUsernameUnique('johndoe');
117
+
118
+ // Check if email is available
119
+ const emailAvailable = await sdk.auth.isEmailUnique('john@example.com');
120
+
121
+ // Register a new user
122
+ const user = await sdk.auth.register({
123
+ username: 'johndoe',
124
+ password: 'secure_password',
125
+ email: 'john@example.com', // optional
126
+ });
127
+ // Returns: { id, username, email, token, refreshToken }
128
+
129
+ // Login with email or username
130
+ const user = await sdk.auth.login({
131
+ emailOrUsername: 'johndoe', // can be email or username
132
+ password: 'password',
133
+ });
134
+ // Returns: { id, username, email, token, refreshToken }
135
+
136
+ // Verify current token
137
+ const verified = await sdk.auth.verifyToken();
138
+ // Returns: { user: { id, username, email, createdAt } }
139
+
140
+ // Refresh access token
141
+ const newToken = await sdk.auth.refreshAccessToken();
142
+ // Returns: string (new access token)
143
+
144
+ // Logout
145
+ await sdk.auth.logout();
146
+
147
+ // Password reset flow
148
+ await sdk.auth.requestPasswordReset('user@example.com');
149
+ await sdk.auth.resetPassword('reset-token', 'new-password');
150
+
151
+ // Change password (authenticated)
152
+ await sdk.auth.changePassword('old-password', 'new-password');
153
+
154
+ // Email verification
155
+ await sdk.auth.verifyEmail('verification-token');
156
+ await sdk.auth.resendVerificationEmail('user@example.com');
157
+ ```
158
+
159
+ ### Watch Together Module
160
+
161
+ ```typescript
162
+ // Connect to Watch Together service
163
+ await sdk.watchTogether.connect();
164
+
165
+ // Create a room
166
+ const room = await sdk.watchTogether.createRoom({
167
+ roomName: 'Movie Night',
168
+ });
169
+ console.log('Room ID:', room.roomId);
170
+
171
+ // Or join existing room
172
+ const room = await sdk.watchTogether.joinRoom({
173
+ roomId: 'abc123',
174
+ });
175
+
176
+ // Listen for events
177
+ sdk.watchTogether.onRoomUpdate((update) => {
178
+ if (update.metadata.userId !== myUserId) {
179
+ switch (update.metadata.action) {
180
+ case 'play':
181
+ player.play();
182
+ break;
183
+ case 'pause':
184
+ player.pause();
185
+ break;
186
+ case 'seek':
187
+ player.seek(update.roomUpdates.syncState?.time);
188
+ break;
189
+ }
190
+ }
191
+ });
192
+
193
+ // Sync check (automatic every 30s, or manual)
194
+ sdk.watchTogether.onSyncState((syncState) => {
195
+ const diff = Math.abs(player.getCurrentTime() - syncState.time);
196
+ if (diff > 1) player.seek(syncState.time);
197
+ });
198
+
199
+ // Control playback (synced to all users)
200
+ sdk.watchTogether.play();
201
+ sdk.watchTogether.pause();
202
+ sdk.watchTogether.seek(125.5);
203
+
204
+ // Send chat messages
205
+ await sdk.watchTogether.sendMessage('Hello everyone!');
206
+
207
+ sdk.watchTogether.onChatMessage((message) => {
208
+ console.log(`${message.username}: ${message.content}`);
209
+ });
210
+
211
+ // Update file info
212
+ sdk.watchTogether.updateFileInfo({
213
+ fileId: 'video-001',
214
+ name: 'movie.mkv',
215
+ fullTime: 7200, // 2 hours
216
+ hash: 'abc123',
217
+ });
218
+
219
+ // Leave room
220
+ sdk.watchTogether.leaveRoom();
221
+
222
+ // Disconnect
223
+ sdk.watchTogether.disconnect();
224
+ ```
225
+
226
+ See [Watch Together Module Documentation](src/modules/watchTogether/README.md) for complete API reference.
227
+
228
+ ### Video Player Integration
229
+
230
+ The SDK provides abstract interfaces for integrating any video player with Watch Together:
231
+
232
+ ```typescript
233
+ import { HTML5VideoController, SyncedPlayer } from '@harmoni-org/sdk';
234
+
235
+ // Create player (HTML5, VLC, YouTube, custom, etc.)
236
+ const player = new HTML5VideoController('video-element');
237
+ await player.start();
238
+
239
+ // Connect player to Watch Together
240
+ const syncedPlayer = new SyncedPlayer(player, sdk.watchTogether, {
241
+ getCurrentUserId: () => currentUser.id,
242
+ onPlayerStopped: () => sdk.watchTogether.leaveRoom(),
243
+ });
244
+
245
+ // Load and play - automatically syncs to all users!
246
+ await player.loadMedia('https://example.com/video.mp4');
247
+ await player.play();
248
+
249
+ // When user seeks, pauses, or plays - all users follow
250
+ // When other users control their player - yours follows
251
+ ```
252
+
253
+ **Key Features:**
254
+
255
+ - โœ… Abstract interfaces for any player (HTML5, VLC, YouTube, custom)
256
+ - โœ… Automatic user action detection (play, pause, seek)
257
+ - โœ… Smart sync (distinguishes user actions from sync commands)
258
+ - โœ… No feedback loops
259
+ - โœ… Built-in HTML5 implementation
260
+ - โœ… Create custom controllers for any player
261
+
262
+ **Create Custom Player:**
263
+
264
+ ```typescript
265
+ import { VideoPlayerController } from '@harmoni-org/sdk';
266
+
267
+ class MyCustomPlayer implements VideoPlayerController {
268
+ async play(): Promise<void> {
269
+ // Your play logic
270
+ }
271
+
272
+ async getStatus(): Promise<IPlayerState> {
273
+ return {
274
+ isPlaying: this.myPlayer.isPlaying(),
275
+ currentTime: this.myPlayer.getCurrentTime(),
276
+ // ... map your player's state
277
+ };
278
+ }
279
+
280
+ // Implement other methods...
281
+ }
282
+
283
+ // Use with Watch Together
284
+ const syncedPlayer = new SyncedPlayer(new MyCustomPlayer(), sdk.watchTogether, options);
285
+ ```
286
+
287
+ See [Player Integration Guide](docs/features/PLAYER_INTEGRATION.md) for complete documentation.
288
+
289
+ ### User Module
290
+
291
+ ```typescript
292
+ // Get user by ID
293
+ const user = await sdk.user.getById('user-id-123');
294
+
295
+ // Get current user profile
296
+ const profile = await sdk.user.getCurrentUser();
297
+
298
+ // Update profile
299
+ await sdk.user.updateProfile({
300
+ username: 'johndoe_new',
301
+ email: 'newemail@example.com',
302
+ });
303
+
304
+ // List users with pagination
305
+ const users = await sdk.user.list({
306
+ page: 1,
307
+ limit: 20,
308
+ });
309
+
310
+ // Search users
311
+ const results = await sdk.user.search('john', {
312
+ page: 1,
313
+ limit: 10,
314
+ });
315
+
316
+ // Upload avatar
317
+ const file = new File(['...'], 'avatar.jpg');
318
+ const { url } = await sdk.user.uploadAvatar(file);
319
+
320
+ // Delete avatar
321
+ await sdk.user.deleteAvatar();
322
+
323
+ // Delete account
324
+ await sdk.user.deleteAccount();
325
+ ```
326
+
327
+ ## ๐Ÿ› ๏ธ Utilities
328
+
329
+ ### String Utilities
330
+
331
+ ```typescript
332
+ import { stringUtils } from '@harmoni-org/sdk';
333
+
334
+ stringUtils.capitalize('hello'); // 'Hello'
335
+ stringUtils.titleCase('hello world'); // 'Hello World'
336
+ stringUtils.slugify('Hello World!'); // 'hello-world'
337
+ stringUtils.truncate('Long text...', 10); // 'Long te...'
338
+ stringUtils.isValidEmail('test@example.com'); // true
339
+ stringUtils.camelToSnake('myVariable'); // 'my_variable'
340
+ stringUtils.snakeToCamel('my_variable'); // 'myVariable'
341
+ stringUtils.randomString(16); // 'aB3dEf5gH7jK9mN0'
342
+ ```
343
+
344
+ ### Date Utilities
345
+
346
+ ```typescript
347
+ import { dateUtils } from '@harmoni-org/sdk';
348
+
349
+ dateUtils.formatDate(new Date(), 'YYYY-MM-DD'); // '2024-01-15'
350
+ dateUtils.timeAgo(new Date('2024-01-01')); // '2 weeks ago'
351
+ dateUtils.addDays(new Date(), 7); // Date 7 days from now
352
+ dateUtils.addHours(new Date(), 3); // Date 3 hours from now
353
+ dateUtils.isToday(new Date()); // true
354
+ dateUtils.isPast(new Date('2023-01-01')); // true
355
+ dateUtils.isFuture(new Date('2025-01-01')); // true
356
+ ```
357
+
358
+ ### Object Utilities
359
+
360
+ ```typescript
361
+ import { objectUtils } from '@harmoni-org/sdk';
362
+
363
+ const original = { a: 1, b: { c: 2 } };
364
+ const cloned = objectUtils.deepClone(original);
365
+
366
+ const merged = objectUtils.deepMerge({ a: 1, b: 2 }, { b: 3, c: 4 }); // { a: 1, b: 3, c: 4 }
367
+
368
+ const picked = objectUtils.pick({ a: 1, b: 2, c: 3 }, ['a', 'c']); // { a: 1, c: 3 }
369
+ const omitted = objectUtils.omit({ a: 1, b: 2, c: 3 }, ['b']); // { a: 1, c: 3 }
370
+
371
+ objectUtils.isEmpty({}); // true
372
+ objectUtils.isEmpty({ a: 1 }); // false
373
+
374
+ const value = objectUtils.getNestedValue({ a: { b: { c: 1 } } }, 'a.b.c'); // 1
375
+ ```
376
+
377
+ ### Validation Utilities
378
+
379
+ ```typescript
380
+ import { validationUtils } from '@harmoni-org/sdk';
381
+
382
+ // Email validation
383
+ const emailResult = validationUtils.validateEmail('test@example.com');
384
+ // { valid: true }
385
+
386
+ // Password validation
387
+ const passwordResult = validationUtils.validatePassword('MyP@ssw0rd', {
388
+ minLength: 8,
389
+ requireUppercase: true,
390
+ requireLowercase: true,
391
+ requireNumbers: true,
392
+ requireSpecialChars: true,
393
+ });
394
+ // { valid: true }
395
+
396
+ // URL validation
397
+ const urlResult = validationUtils.validateUrl('https://example.com');
398
+ // { valid: true }
399
+
400
+ // Phone validation
401
+ const phoneResult = validationUtils.validatePhone('+1234567890');
402
+ // { valid: true }
403
+
404
+ // Required field
405
+ const requiredResult = validationUtils.required('value', 'Username');
406
+ // { valid: true }
407
+
408
+ // Length validation
409
+ validationUtils.minLength('test', 3, 'Username'); // { valid: true }
410
+ validationUtils.maxLength('test', 10, 'Username'); // { valid: true }
411
+ ```
412
+
413
+ ### Storage Utilities
414
+
415
+ ```typescript
416
+ import { localStorage, sessionStorage } from '@harmoni-org/sdk';
417
+
418
+ // Local storage (SSR-safe - uses memory fallback in Node.js)
419
+ localStorage.set('user', { id: 1, name: 'John' });
420
+ const user = localStorage.get<{ id: number; name: string }>('user');
421
+ localStorage.remove('user');
422
+ localStorage.clear();
423
+
424
+ // Check if browser storage is available
425
+ if (localStorage.isAvailable()) {
426
+ console.log('Using browser localStorage');
427
+ } else {
428
+ console.log('Using in-memory storage (SSR/Node)');
429
+ }
430
+
431
+ // Session storage
432
+ sessionStorage.set('token', 'abc123');
433
+ const token = sessionStorage.get<string>('token');
434
+ ```
435
+
436
+ ## ๐Ÿ”Œ Advanced Usage
437
+
438
+ ### File Uploads
439
+
440
+ The SDK automatically handles `FormData` for file uploads:
441
+
442
+ ```typescript
443
+ // Upload user avatar
444
+ const file = document.querySelector('input[type="file"]').files[0];
445
+ const result = await sdk.user.uploadAvatar(file);
446
+ console.log('Avatar URL:', result.url);
447
+
448
+ // Upload with progress tracking
449
+ const formData = new FormData();
450
+ formData.append('file', file);
451
+
452
+ await sdk.getHttpClient().post('/uploads', formData, {
453
+ onUploadProgress: (progressEvent) => {
454
+ const progress = Math.round((progressEvent.loaded * 100) / progressEvent.total);
455
+ console.log(`Upload progress: ${progress}%`);
456
+ },
457
+ });
458
+ ```
459
+
460
+ See [examples/upload-example.ts](examples/upload-example.ts) for more upload patterns.
461
+
462
+ ### Token Refresh with Request Retry
463
+
464
+ The SDK automatically:
465
+
466
+ 1. Detects 401 errors
467
+ 2. Calls refresh token endpoint
468
+ 3. Retries the original request with new token
469
+
470
+ ```typescript
471
+ const sdk = new HarmoniSDK({
472
+ baseURL: 'https://api.example.com',
473
+ autoRefreshToken: true, // Enable auto-refresh
474
+ });
475
+
476
+ // This request will automatically retry if token expires
477
+ const data = await sdk.user.getCurrentUser();
478
+ // If 401 โ†’ refreshes token โ†’ retries request โ†’ returns data
479
+ ```
480
+
481
+ ### Smart Retry Logic
482
+
483
+ The SDK only retries safe operations (GET, HEAD, OPTIONS) by default:
484
+
485
+ ```typescript
486
+ const sdk = new HarmoniSDK({
487
+ baseURL: 'https://api.example.com',
488
+ retryConfig: {
489
+ maxRetries: 3,
490
+ retryDelay: 1000, // Base delay (uses exponential backoff)
491
+ retryableStatuses: [408, 429, 500, 502, 503, 504],
492
+ },
493
+ });
494
+
495
+ // GET requests will retry on network errors or 5xx errors
496
+ await sdk.user.list(); // โœ… Will retry
497
+
498
+ // POST/PATCH/DELETE won't retry (not idempotent)
499
+ await sdk.auth.login(creds); // โŒ Won't retry (POST)
500
+ ```
501
+
502
+ ### Custom Interceptors
503
+
504
+ ```typescript
505
+ const sdk = new HarmoniSDK({ baseURL: 'https://api.example.com' });
506
+
507
+ // Add request interceptor
508
+ sdk.getHttpClient().addRequestInterceptor({
509
+ onFulfilled: (config) => {
510
+ console.log('Request:', config.url);
511
+ return config;
512
+ },
513
+ onRejected: (error) => {
514
+ console.error('Request error:', error);
515
+ return Promise.reject(error);
516
+ },
517
+ });
518
+
519
+ // Add response interceptor
520
+ sdk.getHttpClient().addResponseInterceptor({
521
+ onFulfilled: (response) => {
522
+ console.log('Response:', response.status);
523
+ return response;
524
+ },
525
+ onRejected: (error) => {
526
+ console.error('Response error:', error);
527
+ return Promise.reject(error);
528
+ },
529
+ });
530
+ ```
531
+
532
+ ### Custom Error Handling
533
+
534
+ ```typescript
535
+ import { ApiError } from '@harmoni-org/sdk';
536
+
537
+ try {
538
+ await sdk.auth.login(credentials);
539
+ } catch (error) {
540
+ if (ApiError.isApiError(error)) {
541
+ console.error('API Error:', error.status, error.message);
542
+ console.error('Error code:', error.code);
543
+ console.error('Details:', error.details);
544
+ } else {
545
+ console.error('Unknown error:', error);
546
+ }
547
+ }
548
+ ```
549
+
550
+ ### Creating Custom Modules
551
+
552
+ ```typescript
553
+ import { HttpClient } from '@harmoni-org/sdk';
554
+
555
+ class CustomModule {
556
+ constructor(private http: HttpClient) {}
557
+
558
+ async customEndpoint() {
559
+ return this.http.get('/custom/endpoint');
560
+ }
561
+ }
562
+
563
+ // Extend the SDK
564
+ const sdk = new HarmoniSDK({ baseURL: 'https://api.example.com' });
565
+ const customModule = new CustomModule(sdk.getHttpClient());
566
+ ```
567
+
568
+ ## ๐Ÿ—๏ธ Project Structure
569
+
570
+ ```
571
+ harmoni-sdk/
572
+ โ”œโ”€โ”€ src/
573
+ โ”‚ โ”œโ”€โ”€ core/ # Core HTTP client & error handling
574
+ โ”‚ โ”‚ โ”œโ”€โ”€ http/
575
+ โ”‚ โ”‚ โ””โ”€โ”€ errors/
576
+ โ”‚ โ”œโ”€โ”€ modules/ # Feature modules (auth, user, etc.)
577
+ โ”‚ โ”‚ โ”œโ”€โ”€ auth/
578
+ โ”‚ โ”‚ โ””โ”€โ”€ user/
579
+ โ”‚ โ”œโ”€โ”€ utils/ # Utility functions
580
+ โ”‚ โ”œโ”€โ”€ types/ # TypeScript types
581
+ โ”‚ โ”œโ”€โ”€ sdk/ # Main SDK class
582
+ โ”‚ โ””โ”€โ”€ index.ts # Main entry point
583
+ โ”œโ”€โ”€ tests/ # Test files
584
+ โ”œโ”€โ”€ .github/workflows/ # CI/CD pipelines
585
+ โ””โ”€โ”€ dist/ # Build output (ESM + CJS)
586
+ ```
587
+
588
+ ## ๐Ÿงช Testing
589
+
590
+ ```bash
591
+ # Run tests
592
+ npm test
593
+
594
+ # Run tests in watch mode
595
+ npm run test:watch
596
+
597
+ # Generate coverage report
598
+ npm run test -- --coverage
599
+ ```
600
+
601
+ ## ๐Ÿ”จ Development
602
+
603
+ ```bash
604
+ # Install dependencies
605
+ npm install
606
+
607
+ # Start development mode (watch)
608
+ npm run dev
609
+
610
+ # Build the package
611
+ npm run build
612
+
613
+ # Run linter
614
+ npm run lint
615
+
616
+ # Format code
617
+ npm run format
618
+
619
+ # Type check
620
+ npm run typecheck
621
+ ```
622
+
623
+ ## ๐Ÿ“ Creating a Release
624
+
625
+ This project uses [Changesets](https://github.com/changesets/changesets) for version management.
626
+
627
+ 1. Create a changeset:
628
+
629
+ ```bash
630
+ npm run changeset
631
+ ```
632
+
633
+ 2. Commit the changeset files
634
+ 3. Push to GitHub
635
+ 4. A "Version Packages" PR will be created automatically
636
+ 5. Merge the PR to publish to npm
637
+
638
+ ## ๐Ÿค Contributing
639
+
640
+ Contributions are welcome! Please follow these steps:
641
+
642
+ 1. Fork the repository
643
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
644
+ 3. Make your changes
645
+ 4. Add tests for your changes
646
+ 5. Run tests and linting (`npm test && npm run lint`)
647
+ 6. Commit your changes (`git commit -m 'feat: add amazing feature'`)
648
+ 7. Push to the branch (`git push origin feature/amazing-feature`)
649
+ 8. Open a Pull Request
650
+
651
+ ## ๐Ÿ“„ License
652
+
653
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
654
+
655
+ ## ๐Ÿ“š Additional Resources
656
+
657
+ ### Documentation
658
+
659
+ - ๐Ÿ“š [Documentation](docs/README.md) - Complete documentation index
660
+ - ๐Ÿ“– [Production Ready Features](docs/guides/PRODUCTION_READY.md) - Why this SDK is production-ready
661
+ - ๐Ÿš€ [Quick Start Guide](docs/guides/QUICK_START.md) - Get started in 5 minutes
662
+ - ๐Ÿ”„ [Migration Guide](docs/guides/MIGRATION.md) - Migrate from direct API calls
663
+ - ๐Ÿ› ๏ธ [Setup Guide](docs/guides/SETUP.md) - Development and publishing
664
+ - ๐Ÿค [Contributing](CONTRIBUTING.md) - Contribution guidelines
665
+
666
+ ### Examples
667
+
668
+ - [Basic Usage](examples/basic-usage.ts) - Common authentication patterns
669
+ - [Advanced Usage](examples/advanced-usage.ts) - Interceptors, error handling, utilities
670
+ - [File Uploads](examples/upload-example.ts) - File upload with progress tracking
671
+ - [Watch Together](examples/watch-together-example.ts) - Real-time synchronized playback
672
+
673
+ ### External Links
674
+
675
+ - [TypeScript Documentation](https://www.typescriptlang.org/docs/)
676
+ - [Vitest Documentation](https://vitest.dev/)
677
+ - [Changesets Documentation](https://github.com/changesets/changesets)
678
+
679
+ ## ๐Ÿ™ Acknowledgments
680
+
681
+ - Built with [TypeScript](https://www.typescriptlang.org/)
682
+ - Bundled with [tsup](https://tsup.egoist.dev/)
683
+ - Tested with [Vitest](https://vitest.dev/)
684
+ - HTTP client powered by [Axios](https://axios-http.com/)
685
+
686
+ ## ๐Ÿ“ž Support
687
+
688
+ - ๐Ÿ“ง Email: support@harmoni.dev
689
+ - ๐Ÿ› Issues: [GitHub Issues](https://github.com/yourusername/harmoni-sdk/issues)
690
+ - ๐Ÿ’ฌ Discussions: [GitHub Discussions](https://github.com/yourusername/harmoni-sdk/discussions)
691
+
692
+ ---
693
+
694
+ Made with โค๏ธ by the Harmoni Team