Skip to content

Commit 30090f5

Browse files
committed
chore: bump version to 0.2.1 in package.json, enhance error handling in SDK with structured error types, and update README for improved guidance
1 parent dc5816d commit 30090f5

File tree

9 files changed

+777
-113
lines changed

9 files changed

+777
-113
lines changed

README.md

Lines changed: 88 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -451,30 +451,104 @@ try {
451451

452452
### Error Handling Best Practices
453453

454+
The SDK provides structured error handling with specific error types and helpful suggestions:
455+
454456
```typescript
455-
import { ValidationError } from '@pinata/grapevine-sdk';
457+
import {
458+
ValidationError,
459+
ContentError,
460+
AuthError,
461+
ApiError,
462+
ErrorCode
463+
} from '@pinata/grapevine-sdk';
456464

457465
try {
458-
const feed = await grapevine.feeds.create(feedData);
459-
console.log('Feed created successfully:', feed.id);
466+
const entry = await grapevine.entries.create('feed-id', {
467+
content_base64: base64Data,
468+
title: 'My Entry'
469+
});
470+
console.log('Entry created successfully:', entry.id);
460471
} catch (error) {
461-
if (error instanceof ValidationError) {
462-
// Client-side validation error - fix the input data
472+
if (error instanceof ContentError) {
473+
// Content validation or processing error
474+
console.error('Content error:', error.message);
475+
console.log('Suggestion:', error.suggestion);
476+
477+
if (error.code === ErrorCode.CONTENT_EMPTY) {
478+
// Handle empty content case
479+
console.log('Content was empty or undefined');
480+
} else if (error.code === ErrorCode.BASE64_INVALID) {
481+
// Handle invalid base64 format
482+
console.log('Base64 format is invalid');
483+
}
484+
} else if (error instanceof AuthError) {
485+
// Authentication/wallet error
486+
console.error('Auth error:', error.message);
487+
488+
if (error.code === ErrorCode.AUTH_NO_WALLET) {
489+
// Prompt user to connect wallet
490+
console.log('Please connect your wallet first');
491+
}
492+
} else if (error instanceof ApiError) {
493+
// API request failed
494+
console.error(`API error (${error.status}):`, error.message);
495+
496+
if (error.status === 402) {
497+
console.log('Payment required for this operation');
498+
} else if (error.status === 404) {
499+
console.log('Resource not found - check your IDs');
500+
}
501+
} else if (error instanceof ValidationError) {
502+
// Field validation error (from validation.ts)
463503
console.error('Validation error:', error.message);
464-
// Show user-friendly error message in UI
465-
} else if (error.message.includes('402')) {
466-
// Payment required
467-
console.error('Payment required for this operation');
468-
} else if (error.message.includes('Invalid category_id')) {
469-
// Server-side category validation (category doesn't exist)
470-
console.error('Category no longer exists, please select a different category');
471504
} else {
472-
// Other API or network errors
473-
console.error('API error:', error.message);
505+
// Unexpected error
506+
console.error('Unexpected error:', error.message);
507+
}
508+
509+
// All SDK errors include helpful context
510+
if (error.getDetailedMessage) {
511+
console.log('Detailed help:');
512+
console.log(error.getDetailedMessage());
474513
}
475514
}
476515
```
477516

517+
#### Error Types
518+
519+
- **`ContentError`**: Issues with content validation, base64 encoding, or processing
520+
- **`AuthError`**: Authentication, wallet, or private key issues
521+
- **`ConfigError`**: Configuration conflicts or invalid settings
522+
- **`ApiError`**: HTTP request failures, server errors, payment required
523+
- **`ValidationError`**: Field validation errors (legacy from validation.ts)
524+
525+
#### Common Content Error Solutions
526+
527+
```typescript
528+
// ❌ Wrong - undefined base64 content
529+
try {
530+
await grapevine.entries.create('feed-id', {
531+
content_base64: undefined // This will throw ContentError.CONTENT_EMPTY
532+
});
533+
} catch (error) {
534+
if (error instanceof ContentError && error.code === ErrorCode.CONTENT_EMPTY) {
535+
console.log(error.suggestion); // "Ensure your base64 conversion succeeded..."
536+
console.log(error.example); // Shows code example
537+
}
538+
}
539+
540+
// ✅ Correct - check conversion result
541+
const base64Data = await convertFileToBase64(file);
542+
if (!base64Data) {
543+
throw new Error('Failed to convert file to base64');
544+
}
545+
546+
await grapevine.entries.create('feed-id', {
547+
content_base64: base64Data,
548+
title: 'My File'
549+
});
550+
```
551+
478552
### Validation Rules Reference
479553

480554
| Field Type | Validation Rules | Example Error |

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pinata/grapevine-sdk",
3-
"version": "0.2.0",
3+
"version": "0.2.1",
44
"description": "Easy-to-use SDK for the Grapevine API - Create and manage content feeds with x402 micropayments",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

src/adapters/private-key-adapter.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { createWalletClient, http, type WalletClient } from 'viem';
22
import { privateKeyToAccount } from 'viem/accounts';
33
import { base, baseSepolia } from 'viem/chains';
44
import type { WalletAdapter } from './wallet-adapter.js';
5+
import { AuthError } from '../errors.js';
56

67
/**
78
* Wallet adapter implementation for private key authentication
@@ -13,7 +14,7 @@ export class PrivateKeyAdapter implements WalletAdapter {
1314

1415
constructor(privateKey: string, isTestnet: boolean) {
1516
if (!privateKey.startsWith('0x')) {
16-
throw new Error('Private key must start with 0x');
17+
throw AuthError.invalidPrivateKey(privateKey);
1718
}
1819

1920
this.account = privateKeyToAccount(privateKey as `0x${string}`);

src/adapters/wagmi-adapter.ts

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { WalletClient } from 'viem';
22
import type { WalletAdapter } from './wallet-adapter.js';
3+
import { AuthError, ErrorCode } from '../errors.js';
34

45
/**
56
* Wallet adapter implementation for wagmi wallet clients
@@ -11,13 +12,37 @@ export class WagmiAdapter implements WalletAdapter {
1112

1213
constructor(walletClient: WalletClient) {
1314
if (!walletClient) {
14-
throw new Error('WalletClient is required');
15+
throw new AuthError(
16+
'WalletClient is required for WagmiAdapter',
17+
ErrorCode.AUTH_INVALID_KEY,
18+
{
19+
suggestion: 'Provide a valid WalletClient instance from wagmi',
20+
example: `import { useWalletClient } from 'wagmi';
21+
import { WagmiAdapter } from '@pinata/grapevine-sdk';
22+
23+
const { data: walletClient } = useWalletClient();
24+
if (walletClient) {
25+
const adapter = new WagmiAdapter(walletClient);
26+
}`
27+
}
28+
);
1529
}
1630

1731
// Extract address from wallet client account
1832
const address = walletClient.account?.address;
1933
if (!address) {
20-
throw new Error('Wallet address not available from wallet client account');
34+
throw new AuthError(
35+
'Wallet address not available from wallet client',
36+
ErrorCode.AUTH_NO_WALLET,
37+
{
38+
suggestion: 'Ensure the wallet is connected and has an account',
39+
example: `// Make sure wallet is connected first
40+
if (!walletClient.account?.address) {
41+
// Connect wallet first
42+
await connect({ connector: ... });
43+
}`
44+
}
45+
);
2146
}
2247

2348
this.walletClient = walletClient;
@@ -26,7 +51,14 @@ export class WagmiAdapter implements WalletAdapter {
2651
// Get chain ID from wallet client
2752
const chainId = this.walletClient.chain?.id;
2853
if (!chainId) {
29-
throw new Error('Chain ID not available from wallet client');
54+
throw new AuthError(
55+
'Chain ID not available from wallet client',
56+
ErrorCode.CONFIG_INVALID,
57+
{
58+
suggestion: 'Ensure the wallet is connected to a supported network (Base or Base Sepolia)',
59+
context: { supportedChainIds: [8453, 84532] }
60+
}
61+
);
3062
}
3163

3264
// Map chain IDs: 84532 = Base Sepolia, 8453 = Base Mainnet

src/client.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { FeedsResource } from './resources/feeds.js';
44
import { EntriesResource } from './resources/entries.js';
55
import type { GrapevineConfig, Category } from './types.js';
66
import type { WalletAdapter } from './adapters/wallet-adapter.js';
7+
import { AuthError, ConfigError, ApiError } from './errors.js';
78

89
interface RequestOptions {
910
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
@@ -39,13 +40,13 @@ export class GrapevineClient {
3940

4041
// Initialize auth if private key or wallet adapter is provided
4142
if (config.privateKey && config.walletAdapter) {
42-
throw new Error('Cannot provide both privateKey and walletAdapter');
43+
throw ConfigError.conflictingConfig('privateKey', 'walletAdapter');
4344
}
4445

4546
// Validate private key format if provided
4647
if (config.privateKey) {
4748
if (typeof config.privateKey !== 'string' || !config.privateKey.startsWith('0x') || config.privateKey.length !== 66) {
48-
throw new Error('Invalid private key format. Must be 66 characters starting with 0x');
49+
throw AuthError.invalidPrivateKey(config.privateKey);
4950
}
5051
this.initializeAuth(config.privateKey);
5152
} else if (config.walletAdapter) {
@@ -71,7 +72,7 @@ export class GrapevineClient {
7172
*/
7273
initializeAuth(privateKey: string): void {
7374
if (!privateKey.startsWith('0x')) {
74-
throw new Error('Private key must start with 0x');
75+
throw AuthError.invalidPrivateKey(privateKey);
7576
}
7677

7778
this.authManager = new AuthManager(privateKey, this.apiUrl, this.isTestnet);
@@ -145,7 +146,7 @@ export class GrapevineClient {
145146
// Add auth headers if required
146147
if (options.requiresAuth) {
147148
if (!this.authManager) {
148-
throw new Error('Authentication required but no wallet configured. Use setWalletClient() to configure a wallet first.');
149+
throw AuthError.noWallet();
149150
}
150151
authHeaders = await this.authManager.getAuthHeaders();
151152
Object.assign(headers, authHeaders);
@@ -187,7 +188,7 @@ export class GrapevineClient {
187188
// Check for errors
188189
if (!response.ok && response.status !== 204) {
189190
const errorText = await response.text();
190-
throw new Error(`API request failed (${response.status}): ${errorText}`);
191+
throw ApiError.requestFailed(response.status, errorText, url);
191192
}
192193

193194
return response;
@@ -213,7 +214,7 @@ export class GrapevineClient {
213214
*/
214215
getWalletAddress(): string {
215216
if (!this.authManager) {
216-
throw new Error('No wallet configured. Use setWalletClient() to configure a wallet first.');
217+
throw AuthError.noWallet();
217218
}
218219
return this.authManager.walletAddress;
219220
}

0 commit comments

Comments
 (0)