doboxa-js 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.
@@ -0,0 +1,15 @@
1
+ generatorName: typescript-fetch
2
+ inputSpec: api-doc.yaml
3
+ outputDir: src/types/generated
4
+ additionalProperties:
5
+ typescriptThreePlus: true
6
+ supportsES6: true
7
+ withInterfaces: true
8
+ enumPropertyNaming: original
9
+ stringEnums: true
10
+ modelPropertyNaming: original
11
+ npmName: doboxa-js
12
+ npmVersion: 1.0.0
13
+ withNodeImports: true
14
+ useSingleRequestParameter: false
15
+
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
3
+ "spaces": 2,
4
+ "generator-cli": {
5
+ "version": "7.17.0"
6
+ }
7
+ }
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "doboxa-js",
3
+ "version": "1.0.0",
4
+ "description": "A library for interacting with the Doboxa API v2.",
5
+ "main": "index.js",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/emmems/doboxa-js.git"
9
+ },
10
+ "scripts": {
11
+ "build": "tsup",
12
+ "build:publish": "pnpm build --env.publish",
13
+ "clean": "rimraf ./dist",
14
+ "dev": "tsup --watch",
15
+ "dev:publish": "pnpm dev --env.publish",
16
+ "generate:api": "openapi-generator-cli generate -i api-doc.yaml -g typescript-fetch -o src/types/generated -c openapi-generator-config.yaml"
17
+ },
18
+ "keywords": [],
19
+ "author": "",
20
+ "license": "MIT",
21
+ "devDependencies": {
22
+ "@openapitools/openapi-generator-cli": "^2.7.0",
23
+ "tsup": "^8.5.1",
24
+ "typescript": "^5.9.3"
25
+ },
26
+ "exports": {
27
+ ".": {
28
+ "import": {
29
+ "types": "./dist/index.d.mts",
30
+ "default": "./dist/index.mjs"
31
+ },
32
+ "require": {
33
+ "types": "./dist/index.d.ts",
34
+ "default": "./dist/index.js"
35
+ }
36
+ },
37
+ "./models": {
38
+ "import": {
39
+ "types": "./dist/models.d.mts",
40
+ "default": "./dist/models.mjs"
41
+ },
42
+ "require": {
43
+ "types": "./dist/models.d.ts",
44
+ "default": "./dist/models.js"
45
+ }
46
+ },
47
+ "./errors": {
48
+ "import": {
49
+ "types": "./dist/errors.d.mts",
50
+ "default": "./dist/errors.mjs"
51
+ },
52
+ "require": {
53
+ "types": "./dist/errors.d.ts",
54
+ "default": "./dist/errors.js"
55
+ }
56
+ },
57
+ "./client": {
58
+ "import": {
59
+ "types": "./dist/client.d.mts",
60
+ "default": "./dist/client.mjs"
61
+ },
62
+ "require": {
63
+ "types": "./dist/client.d.ts",
64
+ "default": "./dist/client.js"
65
+ }
66
+ },
67
+ "./package.json": "./package.json"
68
+ }
69
+ }
package/src/client.ts ADDED
@@ -0,0 +1,99 @@
1
+ import { Configuration } from './types/generated/src/runtime';
2
+ import { HTTPClient } from './http/client';
3
+ import { TokenManager } from './utils/tokenManager';
4
+ import { getBaseURL } from './utils/config';
5
+ import { AuthenticationResource } from './resources/authentication';
6
+ import { OrdersResource } from './resources/orders';
7
+ import { InvoicesResource } from './resources/invoices';
8
+ import { ClientsResource } from './resources/clients';
9
+ import { DiscountCodesResource } from './resources/discountCodes';
10
+ import { TrackingNumbersResource } from './resources/trackingNumbers';
11
+ import { OrderCommentsResource } from './resources/orderComments';
12
+ import { OrderStatusChangesResource } from './resources/orderStatusChanges';
13
+ import { UsersResource } from './resources/users';
14
+
15
+ export interface DoboxaConfig {
16
+ /**
17
+ * API key (access token). If not provided, you must call authentication.login() first.
18
+ */
19
+ apiKey?: string;
20
+ /**
21
+ * Base URL for the API. Defaults to DOBOXA_API_URL environment variable or http://localhost
22
+ */
23
+ baseURL?: string;
24
+ }
25
+
26
+ /**
27
+ * Main Doboxa client class
28
+ */
29
+ export class Doboxa {
30
+ public readonly authentication: AuthenticationResource;
31
+ public readonly orders: OrdersResource;
32
+ public readonly invoices: InvoicesResource;
33
+ public readonly clients: ClientsResource;
34
+ public readonly discountCodes: DiscountCodesResource;
35
+ public readonly trackingNumbers: TrackingNumbersResource;
36
+ public readonly orderComments: OrderCommentsResource;
37
+ public readonly orderStatusChanges: OrderStatusChangesResource;
38
+ public readonly users: UsersResource;
39
+
40
+ private tokenManager: TokenManager;
41
+ private httpClient: HTTPClient;
42
+ private config: Configuration;
43
+
44
+ constructor(config: DoboxaConfig = {}) {
45
+ const baseURL = config.baseURL || getBaseURL() || 'http://localhost';
46
+
47
+ this.tokenManager = new TokenManager();
48
+ if (config.apiKey) {
49
+ this.tokenManager.setTokens(config.apiKey, '', 3600);
50
+ }
51
+
52
+ // Create configuration for generated APIs
53
+ // Note: Generated APIs use apiKey for Bearer authentication
54
+ this.config = new Configuration({
55
+ basePath: baseURL,
56
+ apiKey: async (name: string) => {
57
+ if (name === 'Authorization') {
58
+ const token = this.tokenManager.getAccessToken();
59
+ if (!token) {
60
+ throw new Error('No access token available');
61
+ }
62
+ return `Bearer ${token}`;
63
+ }
64
+ throw new Error(`Unknown API key name: ${name}`);
65
+ },
66
+ });
67
+
68
+ // Create HTTP client with token refresh callback
69
+ // Note: We create a temporary HTTP client for token refresh to avoid circular dependency
70
+ const tempHttpClient = new HTTPClient({
71
+ baseURL,
72
+ tokenManager: this.tokenManager,
73
+ });
74
+
75
+ this.httpClient = new HTTPClient({
76
+ baseURL,
77
+ tokenManager: this.tokenManager,
78
+ onTokenRefresh: async (refreshToken: string) => {
79
+ // Use temporary HTTP client to refresh token (avoids circular dependency)
80
+ const tempAuth = new AuthenticationResource(tempHttpClient, this.tokenManager);
81
+ const response = await tempAuth.refreshToken(refreshToken);
82
+ this.tokenManager.setTokens(response.token!, response.refresh_token!, 3600);
83
+ return response.token!;
84
+ },
85
+ });
86
+
87
+ // Initialize resources
88
+ this.authentication = new AuthenticationResource(this.httpClient, this.tokenManager);
89
+ this.orders = new OrdersResource(this.httpClient, this.config);
90
+ this.invoices = new InvoicesResource(this.httpClient, this.config);
91
+ this.clients = new ClientsResource(this.httpClient, this.config);
92
+ this.discountCodes = new DiscountCodesResource(this.httpClient, this.config);
93
+ this.trackingNumbers = new TrackingNumbersResource(this.httpClient, this.config);
94
+ this.orderComments = new OrderCommentsResource(this.httpClient, this.config);
95
+ this.orderStatusChanges = new OrderStatusChangesResource(this.httpClient, this.config);
96
+ this.users = new UsersResource(this.httpClient, this.config);
97
+ }
98
+ }
99
+
package/src/errors.ts ADDED
@@ -0,0 +1,90 @@
1
+ import type { InvalidRequestDataResponse, ServerErrorResponse } from './types/generated/src/models';
2
+
3
+ /**
4
+ * Base error class for all Doboxa errors
5
+ */
6
+ export class DoboxaError extends Error {
7
+ constructor(message: string) {
8
+ super(message);
9
+ this.name = 'DoboxaError';
10
+ Object.setPrototypeOf(this, DoboxaError.prototype);
11
+ }
12
+ }
13
+
14
+ /**
15
+ * Error thrown when API returns an error response
16
+ */
17
+ export class DoboxaAPIError extends DoboxaError {
18
+ public readonly statusCode: number;
19
+ public readonly code?: number;
20
+ public readonly errorMessage?: string;
21
+ public readonly invalidParameters?: Array<{ parameter: string; error_cause: string }>;
22
+
23
+ constructor(
24
+ statusCode: number,
25
+ message: string,
26
+ body?: InvalidRequestDataResponse | ServerErrorResponse
27
+ ) {
28
+ super(message);
29
+ this.name = 'DoboxaAPIError';
30
+ this.statusCode = statusCode;
31
+
32
+ if (body) {
33
+ if ('result_code' in body) {
34
+ this.code = body.result_code;
35
+ }
36
+ if ('error_message' in body) {
37
+ this.errorMessage = body.error_message || undefined;
38
+ }
39
+ if ('invalid_parameters' in body && body.invalid_parameters) {
40
+ this.invalidParameters = body.invalid_parameters.map((param) => ({
41
+ parameter: param.parameter,
42
+ error_cause: param.error_cause,
43
+ }));
44
+ }
45
+ }
46
+
47
+ Object.setPrototypeOf(this, DoboxaAPIError.prototype);
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Error thrown when authentication fails
53
+ */
54
+ export class DoboxaAuthenticationError extends DoboxaError {
55
+ constructor(message: string = 'Authentication failed') {
56
+ super(message);
57
+ this.name = 'DoboxaAuthenticationError';
58
+ Object.setPrototypeOf(this, DoboxaAuthenticationError.prototype);
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Error thrown when request validation fails
64
+ */
65
+ export class DoboxaValidationError extends DoboxaAPIError {
66
+ constructor(statusCode: number, message: string, body?: InvalidRequestDataResponse) {
67
+ super(statusCode, message, body);
68
+ this.name = 'DoboxaValidationError';
69
+ Object.setPrototypeOf(this, DoboxaValidationError.prototype);
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Parse error response from API
75
+ */
76
+ export function parseErrorResponse(
77
+ statusCode: number,
78
+ body: unknown
79
+ ): InvalidRequestDataResponse | ServerErrorResponse | undefined {
80
+ if (typeof body === 'object' && body !== null) {
81
+ if ('invalid_parameters' in body || 'error_message' in body) {
82
+ return body as InvalidRequestDataResponse;
83
+ }
84
+ if ('error_message' in body && 'result_code' in body) {
85
+ return body as ServerErrorResponse;
86
+ }
87
+ }
88
+ return undefined;
89
+ }
90
+
@@ -0,0 +1,156 @@
1
+ import type { Configuration } from '../types/generated/src/runtime';
2
+ import { TokenManager } from '../utils/tokenManager';
3
+ import {
4
+ DoboxaAPIError,
5
+ DoboxaAuthenticationError,
6
+ parseErrorResponse,
7
+ } from '../errors';
8
+
9
+ export interface HTTPClientConfig {
10
+ baseURL?: string;
11
+ tokenManager: TokenManager;
12
+ onTokenRefresh?: (token: string) => Promise<string>;
13
+ }
14
+
15
+ /**
16
+ * HTTP client wrapper around fetch with Bearer token injection and error handling
17
+ */
18
+ export class HTTPClient {
19
+ private baseURL: string;
20
+ private tokenManager: TokenManager;
21
+ private onTokenRefresh?: (token: string) => Promise<string>;
22
+
23
+ constructor(config: HTTPClientConfig) {
24
+ this.baseURL = config.baseURL || 'http://localhost';
25
+ this.tokenManager = config.tokenManager;
26
+ this.onTokenRefresh = config.onTokenRefresh;
27
+ }
28
+
29
+ /**
30
+ * Make an HTTP request
31
+ */
32
+ async request<T>(
33
+ path: string,
34
+ options: RequestInit = {}
35
+ ): Promise<T> {
36
+ // Ensure path starts with /
37
+ const url = `${this.baseURL}${path.startsWith('/') ? path : `/${path}`}`;
38
+
39
+ // Get or refresh token
40
+ let accessToken = this.tokenManager.getAccessToken();
41
+
42
+ if (!accessToken || this.tokenManager.isTokenExpired()) {
43
+ if (this.onTokenRefresh) {
44
+ const refreshToken = this.tokenManager.getRefreshToken();
45
+ if (!refreshToken) {
46
+ throw new DoboxaAuthenticationError('No refresh token available');
47
+ }
48
+
49
+ // Check if refresh is already in progress
50
+ let refreshPromise = this.tokenManager.getRefreshPromise();
51
+ if (!refreshPromise) {
52
+ refreshPromise = this.onTokenRefresh(refreshToken);
53
+ this.tokenManager.setRefreshPromise(refreshPromise);
54
+ }
55
+
56
+ try {
57
+ accessToken = await refreshPromise;
58
+ } finally {
59
+ this.tokenManager.setRefreshPromise(null);
60
+ }
61
+ } else {
62
+ throw new DoboxaAuthenticationError('No access token available');
63
+ }
64
+ }
65
+
66
+ // Prepare headers
67
+ const headers = new Headers(options.headers);
68
+ headers.set('Content-Type', 'application/json');
69
+ headers.set('Authorization', `Bearer ${accessToken}`);
70
+
71
+ // Make request
72
+ const response = await fetch(url, {
73
+ ...options,
74
+ headers,
75
+ });
76
+
77
+ // Parse response
78
+ let responseBody: unknown;
79
+ const contentType = response.headers.get('content-type');
80
+ if (contentType && contentType.includes('application/json')) {
81
+ try {
82
+ responseBody = await response.json();
83
+ } catch {
84
+ // Ignore JSON parse errors
85
+ }
86
+ } else {
87
+ responseBody = await response.text();
88
+ }
89
+
90
+ // Handle errors
91
+ if (!response.ok) {
92
+ const errorBody = parseErrorResponse(response.status, responseBody);
93
+ const errorMessage =
94
+ (errorBody && 'error_message' in errorBody
95
+ ? errorBody.error_message
96
+ : undefined) || response.statusText || `HTTP ${response.status}`;
97
+
98
+ // Handle 401/403 as authentication errors
99
+ if (response.status === 401 || response.status === 403) {
100
+ throw new DoboxaAuthenticationError(errorMessage);
101
+ }
102
+
103
+ throw new DoboxaAPIError(response.status, errorMessage, errorBody);
104
+ }
105
+
106
+ return responseBody as T;
107
+ }
108
+
109
+ /**
110
+ * GET request
111
+ */
112
+ async get<T>(path: string, options?: RequestInit): Promise<T> {
113
+ return this.request<T>(path, { ...options, method: 'GET' });
114
+ }
115
+
116
+ /**
117
+ * POST request
118
+ */
119
+ async post<T>(path: string, body?: unknown, options?: RequestInit): Promise<T> {
120
+ return this.request<T>(path, {
121
+ ...options,
122
+ method: 'POST',
123
+ body: body ? JSON.stringify(body) : undefined,
124
+ });
125
+ }
126
+
127
+ /**
128
+ * PUT request
129
+ */
130
+ async put<T>(path: string, body?: unknown, options?: RequestInit): Promise<T> {
131
+ return this.request<T>(path, {
132
+ ...options,
133
+ method: 'PUT',
134
+ body: body ? JSON.stringify(body) : undefined,
135
+ });
136
+ }
137
+
138
+ /**
139
+ * DELETE request
140
+ */
141
+ async delete<T>(path: string, options?: RequestInit): Promise<T> {
142
+ return this.request<T>(path, { ...options, method: 'DELETE' });
143
+ }
144
+
145
+ /**
146
+ * PATCH request
147
+ */
148
+ async patch<T>(path: string, body?: unknown, options?: RequestInit): Promise<T> {
149
+ return this.request<T>(path, {
150
+ ...options,
151
+ method: 'PATCH',
152
+ body: body ? JSON.stringify(body) : undefined,
153
+ });
154
+ }
155
+ }
156
+
package/src/index.ts ADDED
@@ -0,0 +1,31 @@
1
+ // Main client export
2
+ import { Doboxa } from './client';
3
+ export { Doboxa } from './client';
4
+ export type { DoboxaConfig } from './client';
5
+
6
+ // Error exports
7
+ export {
8
+ DoboxaError,
9
+ DoboxaAPIError,
10
+ DoboxaAuthenticationError,
11
+ DoboxaValidationError,
12
+ parseErrorResponse,
13
+ } from './errors';
14
+
15
+ // Type exports
16
+ export * from './types';
17
+
18
+ // Resource exports (for advanced usage)
19
+ export { AuthenticationResource } from './resources/authentication';
20
+ export { OrdersResource } from './resources/orders';
21
+ export { InvoicesResource } from './resources/invoices';
22
+ export { ClientsResource } from './resources/clients';
23
+ export { DiscountCodesResource } from './resources/discountCodes';
24
+ export { TrackingNumbersResource } from './resources/trackingNumbers';
25
+ export { OrderCommentsResource } from './resources/orderComments';
26
+ export { OrderStatusChangesResource } from './resources/orderStatusChanges';
27
+ export { UsersResource } from './resources/users';
28
+
29
+ // Default export
30
+ export default Doboxa;
31
+
package/src/models.ts ADDED
File without changes
@@ -0,0 +1,73 @@
1
+ import { HTTPClient } from '../http/client';
2
+ import { TokenManager } from '../utils/tokenManager';
3
+ import type {
4
+ ApiV2AuthenticationTokenPostRequest,
5
+ ApiV2AuthenticationTokenPost200Response,
6
+ ApiV2AuthenticationRefreshTokenPostRequest,
7
+ ApiV2AuthenticationRefreshTokenPost200Response,
8
+ } from '../types/generated/src/models';
9
+
10
+ export interface LoginParams {
11
+ username: string;
12
+ password: string;
13
+ }
14
+
15
+ export interface RefreshTokenParams {
16
+ refresh_token: string;
17
+ }
18
+
19
+ /**
20
+ * Authentication resource
21
+ */
22
+ export class AuthenticationResource {
23
+ constructor(
24
+ private httpClient: HTTPClient,
25
+ private tokenManager: TokenManager
26
+ ) {}
27
+
28
+ /**
29
+ * Login with username and password
30
+ */
31
+ async login(params: LoginParams): Promise<ApiV2AuthenticationTokenPost200Response> {
32
+ const request: ApiV2AuthenticationTokenPostRequest = {
33
+ username: params.username,
34
+ password: params.password,
35
+ };
36
+
37
+ const response = await this.httpClient.post<ApiV2AuthenticationTokenPost200Response>(
38
+ '/api/v2/authentication/token',
39
+ request
40
+ );
41
+
42
+ // Store tokens
43
+ if (response.token && response.refresh_token) {
44
+ this.tokenManager.setTokens(response.token, response.refresh_token, 3600);
45
+ }
46
+
47
+ return response;
48
+ }
49
+
50
+ /**
51
+ * Refresh access token using refresh token
52
+ */
53
+ async refreshToken(
54
+ refreshToken: string
55
+ ): Promise<ApiV2AuthenticationRefreshTokenPost200Response> {
56
+ const request: ApiV2AuthenticationRefreshTokenPostRequest = {
57
+ refresh_token: refreshToken,
58
+ };
59
+
60
+ const response = await this.httpClient.post<ApiV2AuthenticationRefreshTokenPost200Response>(
61
+ '/api/v2/authentication/refresh_token',
62
+ request
63
+ );
64
+
65
+ // Store new tokens
66
+ if (response.token && response.refresh_token) {
67
+ this.tokenManager.setTokens(response.token, response.refresh_token, 3600);
68
+ }
69
+
70
+ return response;
71
+ }
72
+ }
73
+
@@ -0,0 +1,32 @@
1
+ import { HTTPClient } from '../http/client';
2
+ import { Configuration } from '../types/generated/src/runtime';
3
+ import { ClientApi } from '../types/generated/src/apis/ClientApi';
4
+ import type {
5
+ PageOfClients,
6
+ } from '../types/generated/src/models';
7
+
8
+ export interface ClientListParams {
9
+ page?: number;
10
+ }
11
+
12
+ /**
13
+ * Clients resource
14
+ */
15
+ export class ClientsResource {
16
+ private clientApi: ClientApi;
17
+
18
+ constructor(
19
+ private httpClient: HTTPClient,
20
+ config: Configuration
21
+ ) {
22
+ this.clientApi = new ClientApi(config);
23
+ }
24
+
25
+ /**
26
+ * List clients (only commission type)
27
+ */
28
+ async list(params?: ClientListParams): Promise<PageOfClients> {
29
+ return this.clientApi.getAppEcommerceApiV2GetclientsInvoke(params?.page);
30
+ }
31
+ }
32
+
@@ -0,0 +1,32 @@
1
+ import { HTTPClient } from '../http/client';
2
+ import { Configuration } from '../types/generated/src/runtime';
3
+ import { DiscountCodeApi } from '../types/generated/src/apis/DiscountCodeApi';
4
+ import type {
5
+ GetApiV2DiscountCodesExternal200Response,
6
+ } from '../types/generated/src/models';
7
+
8
+ export interface DiscountCodeListParams {
9
+ page?: number;
10
+ }
11
+
12
+ /**
13
+ * Discount Codes resource
14
+ */
15
+ export class DiscountCodesResource {
16
+ private discountCodeApi: DiscountCodeApi;
17
+
18
+ constructor(
19
+ private httpClient: HTTPClient,
20
+ config: Configuration
21
+ ) {
22
+ this.discountCodeApi = new DiscountCodeApi(config);
23
+ }
24
+
25
+ /**
26
+ * List discount codes (only commission type)
27
+ */
28
+ async list(params?: DiscountCodeListParams): Promise<GetApiV2DiscountCodesExternal200Response> {
29
+ return this.discountCodeApi.getApiV2DiscountCodesExternal(params?.page);
30
+ }
31
+ }
32
+
@@ -0,0 +1,46 @@
1
+ import { HTTPClient } from '../http/client';
2
+ import { Configuration } from '../types/generated/src/runtime';
3
+ import { InvoiceApi } from '../types/generated/src/apis/InvoiceApi';
4
+ import type {
5
+ Invoice,
6
+ PageOfInvoices,
7
+ } from '../types/generated/src/models';
8
+
9
+ export interface InvoiceListParams {
10
+ page?: number;
11
+ from?: string; // ISO date-time
12
+ to?: string; // ISO date-time
13
+ }
14
+
15
+ /**
16
+ * Invoices resource
17
+ */
18
+ export class InvoicesResource {
19
+ private invoiceApi: InvoiceApi;
20
+
21
+ constructor(
22
+ private httpClient: HTTPClient,
23
+ config: Configuration
24
+ ) {
25
+ this.invoiceApi = new InvoiceApi(config);
26
+ }
27
+
28
+ /**
29
+ * List invoices
30
+ */
31
+ async list(params?: InvoiceListParams): Promise<PageOfInvoices> {
32
+ return this.invoiceApi.getAppEcommerceApiV2GetinvoicesInvoke(
33
+ params?.page,
34
+ params?.from ? new Date(params.from) : undefined,
35
+ params?.to ? new Date(params.to) : undefined
36
+ );
37
+ }
38
+
39
+ /**
40
+ * Retrieve a specific invoice
41
+ */
42
+ async retrieve(invoiceId: string): Promise<Invoice> {
43
+ return this.invoiceApi.getAppEcommerceApiV2GetinvoiceInvoke(invoiceId);
44
+ }
45
+ }
46
+