@scell/sdk 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/src/errors.ts ADDED
@@ -0,0 +1,256 @@
1
+ /**
2
+ * Scell SDK Error Classes
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+
7
+ import type { ApiErrorResponse } from './types/common.js';
8
+
9
+ /**
10
+ * Base error class for all Scell API errors
11
+ */
12
+ export class ScellError extends Error {
13
+ /** HTTP status code */
14
+ public readonly status: number;
15
+ /** Error code from API */
16
+ public readonly code: string | undefined;
17
+ /** Original response body */
18
+ public readonly body: unknown;
19
+
20
+ constructor(
21
+ message: string,
22
+ status: number,
23
+ code?: string,
24
+ body?: unknown
25
+ ) {
26
+ super(message);
27
+ this.name = 'ScellError';
28
+ this.status = status;
29
+ this.code = code;
30
+ this.body = body;
31
+
32
+ // Maintains proper stack trace in V8 environments
33
+ if (Error.captureStackTrace) {
34
+ Error.captureStackTrace(this, this.constructor);
35
+ }
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Error thrown when authentication fails (401)
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * try {
45
+ * await client.invoices.list();
46
+ * } catch (error) {
47
+ * if (error instanceof ScellAuthenticationError) {
48
+ * console.log('Invalid or expired token');
49
+ * }
50
+ * }
51
+ * ```
52
+ */
53
+ export class ScellAuthenticationError extends ScellError {
54
+ constructor(message = 'Authentication failed', body?: unknown) {
55
+ super(message, 401, 'AUTHENTICATION_ERROR', body);
56
+ this.name = 'ScellAuthenticationError';
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Error thrown when authorization fails (403)
62
+ *
63
+ * @example
64
+ * ```typescript
65
+ * try {
66
+ * await client.admin.users();
67
+ * } catch (error) {
68
+ * if (error instanceof ScellAuthorizationError) {
69
+ * console.log('Insufficient permissions');
70
+ * }
71
+ * }
72
+ * ```
73
+ */
74
+ export class ScellAuthorizationError extends ScellError {
75
+ constructor(message = 'Access denied', body?: unknown) {
76
+ super(message, 403, 'AUTHORIZATION_ERROR', body);
77
+ this.name = 'ScellAuthorizationError';
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Error thrown when validation fails (422)
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * try {
87
+ * await client.invoices.create(invalidData);
88
+ * } catch (error) {
89
+ * if (error instanceof ScellValidationError) {
90
+ * console.log('Validation errors:', error.errors);
91
+ * }
92
+ * }
93
+ * ```
94
+ */
95
+ export class ScellValidationError extends ScellError {
96
+ /** Field-level validation errors */
97
+ public readonly errors: Record<string, string[]>;
98
+
99
+ constructor(
100
+ message: string,
101
+ errors: Record<string, string[]> = {},
102
+ body?: unknown
103
+ ) {
104
+ super(message, 422, 'VALIDATION_ERROR', body);
105
+ this.name = 'ScellValidationError';
106
+ this.errors = errors;
107
+ }
108
+
109
+ /**
110
+ * Get all error messages as a flat array
111
+ */
112
+ getAllMessages(): string[] {
113
+ return Object.values(this.errors).flat();
114
+ }
115
+
116
+ /**
117
+ * Get error messages for a specific field
118
+ */
119
+ getFieldErrors(field: string): string[] {
120
+ return this.errors[field] ?? [];
121
+ }
122
+
123
+ /**
124
+ * Check if a specific field has errors
125
+ */
126
+ hasFieldError(field: string): boolean {
127
+ const fieldErrors = this.errors[field];
128
+ return fieldErrors !== undefined && fieldErrors.length > 0;
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Error thrown when rate limit is exceeded (429)
134
+ *
135
+ * @example
136
+ * ```typescript
137
+ * try {
138
+ * await client.invoices.create(data);
139
+ * } catch (error) {
140
+ * if (error instanceof ScellRateLimitError) {
141
+ * console.log(`Retry after ${error.retryAfter} seconds`);
142
+ * }
143
+ * }
144
+ * ```
145
+ */
146
+ export class ScellRateLimitError extends ScellError {
147
+ /** Seconds to wait before retrying */
148
+ public readonly retryAfter: number | undefined;
149
+
150
+ constructor(
151
+ message = 'Rate limit exceeded',
152
+ retryAfter?: number,
153
+ body?: unknown
154
+ ) {
155
+ super(message, 429, 'RATE_LIMIT_EXCEEDED', body);
156
+ this.name = 'ScellRateLimitError';
157
+ this.retryAfter = retryAfter;
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Error thrown when a resource is not found (404)
163
+ */
164
+ export class ScellNotFoundError extends ScellError {
165
+ constructor(message = 'Resource not found', body?: unknown) {
166
+ super(message, 404, 'NOT_FOUND', body);
167
+ this.name = 'ScellNotFoundError';
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Error thrown when the API returns a server error (5xx)
173
+ */
174
+ export class ScellServerError extends ScellError {
175
+ constructor(
176
+ message = 'Internal server error',
177
+ status = 500,
178
+ body?: unknown
179
+ ) {
180
+ super(message, status, 'SERVER_ERROR', body);
181
+ this.name = 'ScellServerError';
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Error thrown when insufficient balance for an operation
187
+ */
188
+ export class ScellInsufficientBalanceError extends ScellError {
189
+ constructor(message = 'Insufficient balance', body?: unknown) {
190
+ super(message, 402, 'INSUFFICIENT_BALANCE', body);
191
+ this.name = 'ScellInsufficientBalanceError';
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Error thrown for network-related issues
197
+ */
198
+ export class ScellNetworkError extends ScellError {
199
+ public readonly originalError: Error;
200
+
201
+ constructor(message: string, originalError: Error) {
202
+ super(message, 0, 'NETWORK_ERROR');
203
+ this.name = 'ScellNetworkError';
204
+ this.originalError = originalError;
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Error thrown when request times out
210
+ */
211
+ export class ScellTimeoutError extends ScellError {
212
+ constructor(message = 'Request timed out') {
213
+ super(message, 0, 'TIMEOUT');
214
+ this.name = 'ScellTimeoutError';
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Parse API error response and throw appropriate error
220
+ */
221
+ export function parseApiError(
222
+ status: number,
223
+ body: unknown,
224
+ headers?: Headers
225
+ ): never {
226
+ const errorBody = body as ApiErrorResponse | undefined;
227
+ const message = errorBody?.message ?? 'Unknown error';
228
+ const errors = errorBody?.errors ?? {};
229
+ const code = errorBody?.code;
230
+
231
+ switch (status) {
232
+ case 401:
233
+ throw new ScellAuthenticationError(message, body);
234
+ case 402:
235
+ throw new ScellInsufficientBalanceError(message, body);
236
+ case 403:
237
+ throw new ScellAuthorizationError(message, body);
238
+ case 404:
239
+ throw new ScellNotFoundError(message, body);
240
+ case 422:
241
+ throw new ScellValidationError(message, errors, body);
242
+ case 429: {
243
+ const retryAfter = headers?.get('Retry-After');
244
+ throw new ScellRateLimitError(
245
+ message,
246
+ retryAfter ? parseInt(retryAfter, 10) : undefined,
247
+ body
248
+ );
249
+ }
250
+ default:
251
+ if (status >= 500) {
252
+ throw new ScellServerError(message, status, body);
253
+ }
254
+ throw new ScellError(message, status, code, body);
255
+ }
256
+ }
package/src/index.ts ADDED
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Scell.io Official TypeScript SDK
3
+ *
4
+ * @packageDocumentation
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { ScellClient, ScellApiClient, ScellAuth, ScellWebhooks } from '@scell/sdk';
9
+ *
10
+ * // Dashboard client (Bearer token)
11
+ * const auth = await ScellAuth.login({ email, password });
12
+ * const client = new ScellClient(auth.token);
13
+ *
14
+ * // API client (X-API-Key)
15
+ * const apiClient = new ScellApiClient('your-api-key');
16
+ *
17
+ * // Create invoice
18
+ * const invoice = await apiClient.invoices.create({...});
19
+ *
20
+ * // Verify webhook
21
+ * const isValid = await ScellWebhooks.verifySignature(payload, signature, secret);
22
+ * ```
23
+ */
24
+
25
+ // Client
26
+ import { HttpClient, type ClientConfig } from './client.js';
27
+
28
+ // Resources
29
+ import { ApiKeysResource } from './resources/api-keys.js';
30
+ import { AuthResource, ScellAuth } from './resources/auth.js';
31
+ import { BalanceResource } from './resources/balance.js';
32
+ import { CompaniesResource } from './resources/companies.js';
33
+ import { InvoicesResource } from './resources/invoices.js';
34
+ import { SignaturesResource } from './resources/signatures.js';
35
+ import { WebhooksResource } from './resources/webhooks.js';
36
+
37
+ // Utilities
38
+ import { ScellWebhooks } from './utils/webhook-verify.js';
39
+ import { withRetry, createRetryWrapper } from './utils/retry.js';
40
+
41
+ /**
42
+ * Scell Dashboard Client
43
+ *
44
+ * Use this client for dashboard/user operations with Bearer token authentication.
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * import { ScellClient, ScellAuth } from '@scell/sdk';
49
+ *
50
+ * // Login first
51
+ * const auth = await ScellAuth.login({
52
+ * email: 'user@example.com',
53
+ * password: 'password'
54
+ * });
55
+ *
56
+ * // Create client
57
+ * const client = new ScellClient(auth.token);
58
+ *
59
+ * // Use the client
60
+ * const companies = await client.companies.list();
61
+ * const balance = await client.balance.get();
62
+ * ```
63
+ */
64
+ export class ScellClient {
65
+ private readonly http: HttpClient;
66
+
67
+ /** Authentication operations */
68
+ public readonly auth: AuthResource;
69
+ /** Company management */
70
+ public readonly companies: CompaniesResource;
71
+ /** API key management */
72
+ public readonly apiKeys: ApiKeysResource;
73
+ /** Balance and transactions */
74
+ public readonly balance: BalanceResource;
75
+ /** Webhook management */
76
+ public readonly webhooks: WebhooksResource;
77
+ /** Invoice listing (read-only via dashboard) */
78
+ public readonly invoices: InvoicesResource;
79
+ /** Signature listing (read-only via dashboard) */
80
+ public readonly signatures: SignaturesResource;
81
+
82
+ /**
83
+ * Create a new Scell Dashboard Client
84
+ *
85
+ * @param token - Bearer token from login
86
+ * @param config - Client configuration
87
+ *
88
+ * @example
89
+ * ```typescript
90
+ * const client = new ScellClient('your-bearer-token', {
91
+ * baseUrl: 'https://api.scell.io/api/v1',
92
+ * timeout: 30000,
93
+ * retry: { maxRetries: 3 }
94
+ * });
95
+ * ```
96
+ */
97
+ constructor(token: string, config: ClientConfig = {}) {
98
+ this.http = new HttpClient('bearer', token, config);
99
+
100
+ this.auth = new AuthResource(this.http);
101
+ this.companies = new CompaniesResource(this.http);
102
+ this.apiKeys = new ApiKeysResource(this.http);
103
+ this.balance = new BalanceResource(this.http);
104
+ this.webhooks = new WebhooksResource(this.http);
105
+ this.invoices = new InvoicesResource(this.http);
106
+ this.signatures = new SignaturesResource(this.http);
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Scell API Client
112
+ *
113
+ * Use this client for external API operations with X-API-Key authentication.
114
+ * This is the client you'll use for creating invoices and signatures.
115
+ *
116
+ * @example
117
+ * ```typescript
118
+ * import { ScellApiClient } from '@scell/sdk';
119
+ *
120
+ * const client = new ScellApiClient('your-api-key');
121
+ *
122
+ * // Create an invoice
123
+ * const invoice = await client.invoices.create({
124
+ * invoice_number: 'FACT-2024-001',
125
+ * direction: 'outgoing',
126
+ * output_format: 'facturx',
127
+ * // ...
128
+ * });
129
+ *
130
+ * // Create a signature request
131
+ * const signature = await client.signatures.create({
132
+ * title: 'Contract',
133
+ * document: btoa(pdfContent),
134
+ * document_name: 'contract.pdf',
135
+ * signers: [{...}]
136
+ * });
137
+ * ```
138
+ */
139
+ export class ScellApiClient {
140
+ private readonly http: HttpClient;
141
+
142
+ /** Invoice operations (create, download, convert) */
143
+ public readonly invoices: InvoicesResource;
144
+ /** Signature operations (create, download, remind, cancel) */
145
+ public readonly signatures: SignaturesResource;
146
+
147
+ /**
148
+ * Create a new Scell API Client
149
+ *
150
+ * @param apiKey - Your API key (from dashboard)
151
+ * @param config - Client configuration
152
+ *
153
+ * @example
154
+ * ```typescript
155
+ * // Production client
156
+ * const client = new ScellApiClient('sk_live_xxx');
157
+ *
158
+ * // Sandbox client
159
+ * const sandboxClient = new ScellApiClient('sk_test_xxx', {
160
+ * baseUrl: 'https://api.scell.io/api/v1/sandbox'
161
+ * });
162
+ * ```
163
+ */
164
+ constructor(apiKey: string, config: ClientConfig = {}) {
165
+ this.http = new HttpClient('api-key', apiKey, config);
166
+
167
+ this.invoices = new InvoicesResource(this.http);
168
+ this.signatures = new SignaturesResource(this.http);
169
+ }
170
+ }
171
+
172
+ // Re-export utilities
173
+ export { ScellAuth, ScellWebhooks, withRetry, createRetryWrapper };
174
+
175
+ // Re-export types
176
+ export type { ClientConfig } from './client.js';
177
+ export type { RetryOptions } from './utils/retry.js';
178
+ export type { VerifySignatureOptions } from './utils/webhook-verify.js';
179
+
180
+ // Re-export errors
181
+ export {
182
+ ScellError,
183
+ ScellAuthenticationError,
184
+ ScellAuthorizationError,
185
+ ScellValidationError,
186
+ ScellRateLimitError,
187
+ ScellNotFoundError,
188
+ ScellServerError,
189
+ ScellInsufficientBalanceError,
190
+ ScellNetworkError,
191
+ ScellTimeoutError,
192
+ } from './errors.js';
193
+
194
+ // Re-export all types
195
+ export * from './types/index.js';
@@ -0,0 +1,141 @@
1
+ /**
2
+ * API Keys Resource
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+
7
+ import type { HttpClient, RequestOptions } from '../client.js';
8
+ import type {
9
+ MessageResponse,
10
+ MessageWithDataResponse,
11
+ SingleResponse,
12
+ } from '../types/common.js';
13
+ import type {
14
+ ApiKey,
15
+ ApiKeyWithSecret,
16
+ CreateApiKeyInput,
17
+ } from '../types/api-keys.js';
18
+
19
+ /**
20
+ * API Keys resource
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * // Create an API key for a company
25
+ * const apiKey = await client.apiKeys.create({
26
+ * name: 'Production Key',
27
+ * company_id: 'company-uuid',
28
+ * environment: 'production'
29
+ * });
30
+ *
31
+ * // Store the key securely!
32
+ * console.log('API Key:', apiKey.key);
33
+ * ```
34
+ */
35
+ export class ApiKeysResource {
36
+ constructor(private readonly http: HttpClient) {}
37
+
38
+ /**
39
+ * List all API keys
40
+ *
41
+ * @param requestOptions - Request options
42
+ * @returns List of API keys (without secrets)
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * const { data: keys } = await client.apiKeys.list();
47
+ * keys.forEach(key => {
48
+ * console.log(`${key.name}: ${key.key_prefix}...`);
49
+ * });
50
+ * ```
51
+ */
52
+ async list(requestOptions?: RequestOptions): Promise<{ data: ApiKey[] }> {
53
+ return this.http.get<{ data: ApiKey[] }>(
54
+ '/api-keys',
55
+ undefined,
56
+ requestOptions
57
+ );
58
+ }
59
+
60
+ /**
61
+ * Get a specific API key
62
+ *
63
+ * @param id - API Key UUID
64
+ * @param requestOptions - Request options
65
+ * @returns API key details (without secret)
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * const { data: key } = await client.apiKeys.get('key-uuid');
70
+ * console.log(`Last used: ${key.last_used_at}`);
71
+ * ```
72
+ */
73
+ async get(
74
+ id: string,
75
+ requestOptions?: RequestOptions
76
+ ): Promise<SingleResponse<ApiKey>> {
77
+ return this.http.get<SingleResponse<ApiKey>>(
78
+ `/api-keys/${id}`,
79
+ undefined,
80
+ requestOptions
81
+ );
82
+ }
83
+
84
+ /**
85
+ * Create a new API key
86
+ *
87
+ * Important: The full key is only returned once during creation.
88
+ * Store it securely - you won't be able to retrieve it again.
89
+ *
90
+ * @param input - API key configuration
91
+ * @param requestOptions - Request options
92
+ * @returns Created API key with full key value
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * const { data: apiKey } = await client.apiKeys.create({
97
+ * name: 'Production Integration',
98
+ * company_id: 'company-uuid',
99
+ * environment: 'production',
100
+ * permissions: ['invoices:write', 'signatures:write']
101
+ * });
102
+ *
103
+ * // IMPORTANT: Store this key securely!
104
+ * // You won't be able to see it again.
105
+ * console.log('Save this key:', apiKey.key);
106
+ * ```
107
+ */
108
+ async create(
109
+ input: CreateApiKeyInput,
110
+ requestOptions?: RequestOptions
111
+ ): Promise<MessageWithDataResponse<ApiKeyWithSecret>> {
112
+ return this.http.post<MessageWithDataResponse<ApiKeyWithSecret>>(
113
+ '/api-keys',
114
+ input,
115
+ requestOptions
116
+ );
117
+ }
118
+
119
+ /**
120
+ * Delete an API key
121
+ *
122
+ * Warning: This will immediately revoke the key and all
123
+ * requests using it will fail.
124
+ *
125
+ * @param id - API Key UUID
126
+ * @param requestOptions - Request options
127
+ * @returns Deletion confirmation
128
+ *
129
+ * @example
130
+ * ```typescript
131
+ * await client.apiKeys.delete('key-uuid');
132
+ * console.log('API key revoked');
133
+ * ```
134
+ */
135
+ async delete(
136
+ id: string,
137
+ requestOptions?: RequestOptions
138
+ ): Promise<MessageResponse> {
139
+ return this.http.delete<MessageResponse>(`/api-keys/${id}`, requestOptions);
140
+ }
141
+ }