@amaster.ai/http-client 1.0.0-beta.0 → 1.0.0-beta.2

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/README.md CHANGED
@@ -1,170 +1 @@
1
1
  # @amaster.ai/http-client
2
-
3
- Base HTTP client with error handling and response unwrapping for backend API communication.
4
-
5
- ## Features
6
-
7
- - 🔄 Automatic backend response unwrapping (`{ status: 0, data: {...} }` → `{...}`)
8
- - 📅 Backend datetime format conversion (`"2026-01-05 10:30:45"` → `"2026-01-05T10:30:45.000Z"`)
9
- - ⚠️ Consistent error handling with normalized error messages
10
- - 🔌 Built on Axios with full TypeScript support
11
- - 🌳 Tree-shakeable ESM/CJS builds
12
-
13
- ## Installation
14
-
15
- ```bash
16
- pnpm add @amaster.ai/http-client axios
17
- ```
18
-
19
- > **Note:** `axios` is a peer dependency and must be installed separately.
20
-
21
- ## Usage
22
-
23
- ### Basic Usage
24
-
25
- ```typescript
26
- import { createHttpClient } from "@amaster.ai/http-client";
27
-
28
- const client = createHttpClient();
29
-
30
- const result = await client.request({
31
- url: "/api/users",
32
- method: "get",
33
- });
34
-
35
- if (result.data) {
36
- console.log(result.data);
37
- } else {
38
- console.error(result.error.message);
39
- }
40
- ```
41
-
42
- ### With Custom Axios Instance
43
-
44
- ```typescript
45
- import axios from "axios";
46
- import { createHttpClient } from "@amaster.ai/http-client";
47
-
48
- const instance = axios.create({
49
- baseURL: "https://api.example.com",
50
- timeout: 5000,
51
- headers: {
52
- "Authorization": "Bearer token",
53
- },
54
- });
55
-
56
- const client = createHttpClient(instance);
57
- ```
58
-
59
- ### Response Format
60
-
61
- All requests return a `ClientResult<T>`:
62
-
63
- ```typescript
64
- type ClientResult<T> = {
65
- data: T | null; // Response data (null on error)
66
- error: ClientError | null; // Error details (null on success)
67
- status: number; // HTTP status code
68
- };
69
- ```
70
-
71
- ### Error Handling
72
-
73
- ```typescript
74
- const result = await client.request({
75
- url: "/api/users/123",
76
- method: "get",
77
- });
78
-
79
- if (result.error) {
80
- console.error("Error:", result.error.message);
81
- console.error("Status:", result.error.status);
82
- console.error("Details:", result.error.details);
83
- }
84
- ```
85
-
86
- ## Features in Detail
87
-
88
- ### Backend Response Unwrapping
89
-
90
- The client automatically unwraps backend standard response format:
91
-
92
- ```typescript
93
- // Backend returns:
94
- {
95
- "status": 0,
96
- "data": { "id": 1, "name": "John" }
97
- }
98
-
99
- // Client returns:
100
- {
101
- data: { "id": 1, "name": "John" },
102
- error: null,
103
- status: 200
104
- }
105
- ```
106
-
107
- ### Datetime Conversion
108
-
109
- Backend datetime strings are automatically converted to ISO 8601 format:
110
-
111
- ```typescript
112
- // Backend returns:
113
- {
114
- "createdAt": "2026-01-05 10:30:45"
115
- }
116
-
117
- // Client returns:
118
- {
119
- "createdAt": "2026-01-05T10:30:45.000Z"
120
- }
121
- ```
122
-
123
- This works recursively for nested objects and arrays.
124
-
125
- ### Error Normalization
126
-
127
- The client normalizes various backend error formats:
128
-
129
- ```typescript
130
- // Backend may return any of:
131
- { "message": "Error" }
132
- { "error": "Error" }
133
- { "msg": "Error" }
134
- { "detail": "Error" }
135
-
136
- // Client always returns:
137
- {
138
- error: {
139
- message: "Error",
140
- status: 400,
141
- details: { /* original response */ }
142
- }
143
- }
144
- ```
145
-
146
- ## TypeScript Support
147
-
148
- Full TypeScript support with generic types:
149
-
150
- ```typescript
151
- interface User {
152
- id: number;
153
- name: string;
154
- email: string;
155
- }
156
-
157
- const result = await client.request<User>({
158
- url: "/api/users/1",
159
- method: "get",
160
- });
161
-
162
- if (result.data) {
163
- // result.data is typed as User
164
- console.log(result.data.name);
165
- }
166
- ```
167
-
168
- ## License
169
-
170
- MIT
package/dist/index.cjs CHANGED
@@ -1,103 +1,2 @@
1
- 'use strict';
2
-
3
- // src/http.ts
4
- function normalizeErrorMessage(payload) {
5
- if (!payload) {
6
- return "Request failed";
7
- }
8
- if (typeof payload === "string") {
9
- return payload;
10
- }
11
- if (typeof payload === "object") {
12
- const errorPayload = payload;
13
- return errorPayload.message || errorPayload.error || errorPayload.msg || errorPayload.detail || "Request failed";
14
- }
15
- return "Request failed";
16
- }
17
- function isBackendStandardFormat(data) {
18
- if (!data || typeof data !== "object" || !("data" in data)) {
19
- return false;
20
- }
21
- if ("status" in data) {
22
- const status = data.status;
23
- return status === 0 || status === 1 || status === "0" || status === "1";
24
- }
25
- return false;
26
- }
27
- function unwrapBackendResponse(responseData) {
28
- if (isBackendStandardFormat(responseData)) {
29
- return responseData.data;
30
- }
31
- return responseData;
32
- }
33
- var BACKEND_DATETIME_REGEX = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
34
- function isBackendDatetime(value) {
35
- return BACKEND_DATETIME_REGEX.test(value);
36
- }
37
- function parseBackendDatetime(value) {
38
- return `${value.replace(" ", "T")}.000Z`;
39
- }
40
- function processResponseDates(data) {
41
- if (data === null || data === void 0) {
42
- return data;
43
- }
44
- if (Array.isArray(data)) {
45
- return data.map((item) => processResponseDates(item));
46
- }
47
- if (typeof data === "object") {
48
- const processed = {};
49
- for (const [key, value] of Object.entries(data)) {
50
- processed[key] = processResponseDates(value);
51
- }
52
- return processed;
53
- }
54
- if (typeof data === "string" && isBackendDatetime(data)) {
55
- return parseBackendDatetime(data);
56
- }
57
- return data;
58
- }
59
- function createHttpClient(axiosInstance) {
60
- let instance = axiosInstance;
61
- return {
62
- async request(config) {
63
- if (!instance) {
64
- const axios = await import('axios');
65
- instance = axios.default.create();
66
- }
67
- try {
68
- const resp = await instance(config);
69
- const status = resp?.status ?? 0;
70
- if (status >= 200 && status < 300) {
71
- const unwrappedData = unwrapBackendResponse(resp?.data);
72
- const processedData = processResponseDates(unwrappedData);
73
- return { data: processedData ?? null, error: null, status };
74
- }
75
- return {
76
- data: null,
77
- error: {
78
- status,
79
- message: normalizeErrorMessage(resp?.data) || `HTTP ${status}`,
80
- details: resp?.data
81
- },
82
- status
83
- };
84
- } catch (error_) {
85
- const error = error_;
86
- const status = error.response?.status || 0;
87
- return {
88
- data: null,
89
- error: {
90
- status,
91
- message: normalizeErrorMessage(error.response?.data) || error.message || "Network error",
92
- details: error.response?.data
93
- },
94
- status
95
- };
96
- }
97
- }
98
- };
99
- }
100
-
101
- exports.createHttpClient = createHttpClient;
102
- //# sourceMappingURL=index.cjs.map
1
+ 'use strict';function a(e){if(!e)return "Request failed";if(typeof e=="string")return e;if(typeof e=="object"){let t=e;return t.message||t.error||t.msg||t.detail||"Request failed"}return "Request failed"}function u(e){if(!e||typeof e!="object"||!("data"in e))return false;if("status"in e){let t=e.status;return t===0||t===1||t==="0"||t==="1"}return false}function l(e){return u(e)?e.data:e}var c=/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;function f(e){return c.test(e)}function d(e){return `${e.replace(" ","T")}.000Z`}function i(e){if(e==null)return e;if(Array.isArray(e))return e.map(t=>i(t));if(typeof e=="object"){let t={};for(let[o,r]of Object.entries(e))t[o]=i(r);return t}return typeof e=="string"&&f(e)?d(e):e}function p(e){let t=e;return {async request(o){t||(t=(await import('axios')).default.create());try{let r=await t(o),n=r?.status??0;if(n>=200&&n<300){let s=l(r?.data);return {data:i(s)??null,error:null,status:n}}return {data:null,error:{status:n,message:a(r?.data)||`HTTP ${n}`,details:r?.data},status:n}}catch(r){let n=r,s=n.response?.status||0;return {data:null,error:{status:s,message:a(n.response?.data)||n.message||"Network error",details:n.response?.data},status:s}}}}}exports.createHttpClient=p;//# sourceMappingURL=index.cjs.map
103
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/http.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import type { AxiosInstance, AxiosRequestConfig } from \"axios\";\n\nexport type ClientError = {\n message: string;\n status?: number;\n code?: string;\n details?: unknown;\n};\n\nexport type ClientResult<T> = {\n data: T | null;\n error: ClientError | null;\n status: number;\n};\n\nexport type HttpClient = {\n request<T>(\n config: AxiosRequestConfig & {\n url: string;\n method: NonNullable<AxiosRequestConfig[\"method\"]>;\n }\n ): Promise<ClientResult<T>>;\n};\n\ninterface ErrorPayload {\n message?: string;\n error?: string;\n msg?: string;\n detail?: string;\n}\n\nfunction normalizeErrorMessage(payload: unknown): string {\n if (!payload) {\n return \"Request failed\";\n }\n if (typeof payload === \"string\") {\n return payload;\n }\n if (typeof payload === \"object\") {\n const errorPayload = payload as ErrorPayload;\n return (\n errorPayload.message ||\n errorPayload.error ||\n errorPayload.msg ||\n errorPayload.detail ||\n \"Request failed\"\n );\n }\n return \"Request failed\";\n}\n\ninterface BackendStandardResponse {\n status: number | string;\n data: unknown;\n}\n\n/**\n * Check if response data matches backend standard format: { status: 0/1, data: {...} }\n * Handles both number and string status values\n */\nfunction isBackendStandardFormat(data: unknown): data is BackendStandardResponse {\n if (!data || typeof data !== \"object\" || !(\"data\" in data)) {\n return false;\n }\n\n // Check status field - handle both number and string values\n if (\"status\" in data) {\n const status = (data as BackendStandardResponse).status;\n // Accept 0, 1, \"0\", \"1\" as valid status values\n return status === 0 || status === 1 || status === \"0\" || status === \"1\";\n }\n\n return false;\n}\n\n/**\n * Unwrap backend standard format response\n * Backend returns: { status: 0, data: {...} }\n * We extract the inner 'data' field for cleaner client usage\n */\nfunction unwrapBackendResponse<T>(responseData: unknown): T {\n if (isBackendStandardFormat(responseData)) {\n // Extract inner data field\n return responseData.data as T;\n }\n // Return as-is if not standard format (e.g., BPM/Workflow APIs)\n return responseData as T;\n}\n\n/**\n * Backend datetime format regex: \"YYYY-MM-DD HH:MM:SS\"\n */\nconst BACKEND_DATETIME_REGEX = /^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$/;\n\n/**\n * Check if a string matches backend datetime format\n */\nfunction isBackendDatetime(value: string): boolean {\n return BACKEND_DATETIME_REGEX.test(value);\n}\n\n/**\n * Convert backend datetime string to ISO 8601 format\n * \"2026-01-05 10:30:45\" → \"2026-01-05T10:30:45.000Z\"\n */\nfunction parseBackendDatetime(value: string): string {\n // Replace space with T and append Z for UTC\n return `${value.replace(\" \", \"T\")}.000Z`;\n}\n\n/**\n * Recursively process response data to convert backend datetime strings to ISO format\n * Handles nested objects and arrays\n */\nfunction processResponseDates(data: unknown): unknown {\n if (data === null || data === undefined) {\n return data;\n }\n\n // Handle arrays\n if (Array.isArray(data)) {\n return data.map((item) => processResponseDates(item));\n }\n\n // Handle objects\n if (typeof data === \"object\") {\n const processed: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n processed[key] = processResponseDates(value);\n }\n return processed;\n }\n\n // Handle backend datetime strings\n if (typeof data === \"string\" && isBackendDatetime(data)) {\n return parseBackendDatetime(data);\n }\n\n return data;\n}\n\n/**\n * Create an HTTP client instance\n * \n * @param axiosInstance - Optional axios instance to use (defaults to a basic axios instance)\n * @returns HttpClient with request method\n * \n * @example\n * ```typescript\n * import axios from \"axios\";\n * import { createHttpClient } from \"@amaster.ai/http-client\";\n * \n * const instance = axios.create({ baseURL: \"https://api.example.com\" });\n * const client = createHttpClient(instance);\n * \n * const result = await client.request({\n * url: \"/users\",\n * method: \"get\",\n * });\n * ```\n */\nexport function createHttpClient(axiosInstance?: AxiosInstance): HttpClient {\n // Import axios dynamically to avoid bundling it\n let instance = axiosInstance;\n \n return {\n async request<T>(\n config: AxiosRequestConfig & {\n url: string;\n method: NonNullable<AxiosRequestConfig[\"method\"]>;\n }\n ) {\n // Lazy load axios if not provided\n if (!instance) {\n const axios = await import(\"axios\");\n instance = axios.default.create();\n }\n\n try {\n const resp = await instance(config);\n const status = resp?.status ?? 0;\n\n if (status >= 200 && status < 300) {\n // Unwrap backend standard format: { status: 0, data: {...} } -> {...}\n const unwrappedData = unwrapBackendResponse<T>(resp?.data);\n // Convert backend datetime strings to ISO format\n const processedData = processResponseDates(unwrappedData);\n return { data: (processedData ?? null) as T | null, error: null, status };\n }\n\n return {\n data: null,\n error: {\n status,\n message: normalizeErrorMessage(resp?.data) || `HTTP ${status}`,\n details: resp?.data,\n },\n status,\n };\n } catch (error_: unknown) {\n // Catch network errors, timeouts, and other axios exceptions\n const error = error_ as {\n response?: { status?: number; data?: unknown };\n message?: string;\n };\n const status = error.response?.status || 0;\n return {\n data: null,\n error: {\n status,\n message:\n normalizeErrorMessage(error.response?.data) || error.message || \"Network error\",\n details: error.response?.data,\n },\n status,\n };\n }\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/http.ts"],"names":["normalizeErrorMessage","payload","errorPayload","isBackendStandardFormat","data","status","unwrapBackendResponse","responseData","BACKEND_DATETIME_REGEX","isBackendDatetime","value","parseBackendDatetime","processResponseDates","item","processed","key","createHttpClient","axiosInstance","instance","config","resp","unwrappedData","error_","error"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { AxiosInstance, AxiosRequestConfig } from \"axios\";\n\nexport type ClientError = {\n message: string;\n status?: number;\n code?: string;\n details?: unknown;\n};\n\nexport type ClientResult<T> = {\n data: T | null;\n error: ClientError | null;\n status: number;\n};\n\nexport type HttpClient = {\n request<T>(\n config: AxiosRequestConfig & {\n url: string;\n method: NonNullable<AxiosRequestConfig[\"method\"]>;\n }\n ): Promise<ClientResult<T>>;\n};\n\ninterface ErrorPayload {\n message?: string;\n error?: string;\n msg?: string;\n detail?: string;\n}\n\nfunction normalizeErrorMessage(payload: unknown): string {\n if (!payload) {\n return \"Request failed\";\n }\n if (typeof payload === \"string\") {\n return payload;\n }\n if (typeof payload === \"object\") {\n const errorPayload = payload as ErrorPayload;\n return (\n errorPayload.message ||\n errorPayload.error ||\n errorPayload.msg ||\n errorPayload.detail ||\n \"Request failed\"\n );\n }\n return \"Request failed\";\n}\n\ninterface BackendStandardResponse {\n status: number | string;\n data: unknown;\n}\n\n/**\n * Check if response data matches backend standard format: { status: 0/1, data: {...} }\n * Handles both number and string status values\n */\nfunction isBackendStandardFormat(data: unknown): data is BackendStandardResponse {\n if (!data || typeof data !== \"object\" || !(\"data\" in data)) {\n return false;\n }\n\n // Check status field - handle both number and string values\n if (\"status\" in data) {\n const status = (data as BackendStandardResponse).status;\n // Accept 0, 1, \"0\", \"1\" as valid status values\n return status === 0 || status === 1 || status === \"0\" || status === \"1\";\n }\n\n return false;\n}\n\n/**\n * Unwrap backend standard format response\n * Backend returns: { status: 0, data: {...} }\n * We extract the inner 'data' field for cleaner client usage\n */\nfunction unwrapBackendResponse<T>(responseData: unknown): T {\n if (isBackendStandardFormat(responseData)) {\n // Extract inner data field\n return responseData.data as T;\n }\n // Return as-is if not standard format (e.g., BPM/Workflow APIs)\n return responseData as T;\n}\n\n/**\n * Backend datetime format regex: \"YYYY-MM-DD HH:MM:SS\"\n */\nconst BACKEND_DATETIME_REGEX = /^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$/;\n\n/**\n * Check if a string matches backend datetime format\n */\nfunction isBackendDatetime(value: string): boolean {\n return BACKEND_DATETIME_REGEX.test(value);\n}\n\n/**\n * Convert backend datetime string to ISO 8601 format\n * \"2026-01-05 10:30:45\" → \"2026-01-05T10:30:45.000Z\"\n */\nfunction parseBackendDatetime(value: string): string {\n // Replace space with T and append Z for UTC\n return `${value.replace(\" \", \"T\")}.000Z`;\n}\n\n/**\n * Recursively process response data to convert backend datetime strings to ISO format\n * Handles nested objects and arrays\n */\nfunction processResponseDates(data: unknown): unknown {\n if (data === null || data === undefined) {\n return data;\n }\n\n // Handle arrays\n if (Array.isArray(data)) {\n return data.map((item) => processResponseDates(item));\n }\n\n // Handle objects\n if (typeof data === \"object\") {\n const processed: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n processed[key] = processResponseDates(value);\n }\n return processed;\n }\n\n // Handle backend datetime strings\n if (typeof data === \"string\" && isBackendDatetime(data)) {\n return parseBackendDatetime(data);\n }\n\n return data;\n}\n\n/**\n * Create an HTTP client instance\n * \n * @param axiosInstance - Optional axios instance to use (defaults to a basic axios instance)\n * @returns HttpClient with request method\n * \n * @example\n * ```typescript\n * import axios from \"axios\";\n * import { createHttpClient } from \"@amaster.ai/http-client\";\n * \n * const instance = axios.create({ baseURL: \"https://api.example.com\" });\n * const client = createHttpClient(instance);\n * \n * const result = await client.request({\n * url: \"/users\",\n * method: \"get\",\n * });\n * ```\n */\nexport function createHttpClient(axiosInstance?: AxiosInstance): HttpClient {\n // Import axios dynamically to avoid bundling it\n let instance = axiosInstance;\n \n return {\n async request<T>(\n config: AxiosRequestConfig & {\n url: string;\n method: NonNullable<AxiosRequestConfig[\"method\"]>;\n }\n ) {\n // Lazy load axios if not provided\n if (!instance) {\n const axios = await import(\"axios\");\n instance = axios.default.create();\n }\n\n try {\n const resp = await instance(config);\n const status = resp?.status ?? 0;\n\n if (status >= 200 && status < 300) {\n // Unwrap backend standard format: { status: 0, data: {...} } -> {...}\n const unwrappedData = unwrapBackendResponse<T>(resp?.data);\n // Convert backend datetime strings to ISO format\n const processedData = processResponseDates(unwrappedData);\n return { data: (processedData ?? null) as T | null, error: null, status };\n }\n\n return {\n data: null,\n error: {\n status,\n message: normalizeErrorMessage(resp?.data) || `HTTP ${status}`,\n details: resp?.data,\n },\n status,\n };\n } catch (error_: unknown) {\n // Catch network errors, timeouts, and other axios exceptions\n const error = error_ as {\n response?: { status?: number; data?: unknown };\n message?: string;\n };\n const status = error.response?.status || 0;\n return {\n data: null,\n error: {\n status,\n message:\n normalizeErrorMessage(error.response?.data) || error.message || \"Network error\",\n details: error.response?.data,\n },\n status,\n };\n }\n },\n };\n}\n"]}
package/dist/index.js CHANGED
@@ -1,101 +1,2 @@
1
- // src/http.ts
2
- function normalizeErrorMessage(payload) {
3
- if (!payload) {
4
- return "Request failed";
5
- }
6
- if (typeof payload === "string") {
7
- return payload;
8
- }
9
- if (typeof payload === "object") {
10
- const errorPayload = payload;
11
- return errorPayload.message || errorPayload.error || errorPayload.msg || errorPayload.detail || "Request failed";
12
- }
13
- return "Request failed";
14
- }
15
- function isBackendStandardFormat(data) {
16
- if (!data || typeof data !== "object" || !("data" in data)) {
17
- return false;
18
- }
19
- if ("status" in data) {
20
- const status = data.status;
21
- return status === 0 || status === 1 || status === "0" || status === "1";
22
- }
23
- return false;
24
- }
25
- function unwrapBackendResponse(responseData) {
26
- if (isBackendStandardFormat(responseData)) {
27
- return responseData.data;
28
- }
29
- return responseData;
30
- }
31
- var BACKEND_DATETIME_REGEX = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
32
- function isBackendDatetime(value) {
33
- return BACKEND_DATETIME_REGEX.test(value);
34
- }
35
- function parseBackendDatetime(value) {
36
- return `${value.replace(" ", "T")}.000Z`;
37
- }
38
- function processResponseDates(data) {
39
- if (data === null || data === void 0) {
40
- return data;
41
- }
42
- if (Array.isArray(data)) {
43
- return data.map((item) => processResponseDates(item));
44
- }
45
- if (typeof data === "object") {
46
- const processed = {};
47
- for (const [key, value] of Object.entries(data)) {
48
- processed[key] = processResponseDates(value);
49
- }
50
- return processed;
51
- }
52
- if (typeof data === "string" && isBackendDatetime(data)) {
53
- return parseBackendDatetime(data);
54
- }
55
- return data;
56
- }
57
- function createHttpClient(axiosInstance) {
58
- let instance = axiosInstance;
59
- return {
60
- async request(config) {
61
- if (!instance) {
62
- const axios = await import('axios');
63
- instance = axios.default.create();
64
- }
65
- try {
66
- const resp = await instance(config);
67
- const status = resp?.status ?? 0;
68
- if (status >= 200 && status < 300) {
69
- const unwrappedData = unwrapBackendResponse(resp?.data);
70
- const processedData = processResponseDates(unwrappedData);
71
- return { data: processedData ?? null, error: null, status };
72
- }
73
- return {
74
- data: null,
75
- error: {
76
- status,
77
- message: normalizeErrorMessage(resp?.data) || `HTTP ${status}`,
78
- details: resp?.data
79
- },
80
- status
81
- };
82
- } catch (error_) {
83
- const error = error_;
84
- const status = error.response?.status || 0;
85
- return {
86
- data: null,
87
- error: {
88
- status,
89
- message: normalizeErrorMessage(error.response?.data) || error.message || "Network error",
90
- details: error.response?.data
91
- },
92
- status
93
- };
94
- }
95
- }
96
- };
97
- }
98
-
99
- export { createHttpClient };
100
- //# sourceMappingURL=index.js.map
1
+ function a(e){if(!e)return "Request failed";if(typeof e=="string")return e;if(typeof e=="object"){let t=e;return t.message||t.error||t.msg||t.detail||"Request failed"}return "Request failed"}function u(e){if(!e||typeof e!="object"||!("data"in e))return false;if("status"in e){let t=e.status;return t===0||t===1||t==="0"||t==="1"}return false}function l(e){return u(e)?e.data:e}var c=/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;function f(e){return c.test(e)}function d(e){return `${e.replace(" ","T")}.000Z`}function i(e){if(e==null)return e;if(Array.isArray(e))return e.map(t=>i(t));if(typeof e=="object"){let t={};for(let[o,r]of Object.entries(e))t[o]=i(r);return t}return typeof e=="string"&&f(e)?d(e):e}function p(e){let t=e;return {async request(o){t||(t=(await import('axios')).default.create());try{let r=await t(o),n=r?.status??0;if(n>=200&&n<300){let s=l(r?.data);return {data:i(s)??null,error:null,status:n}}return {data:null,error:{status:n,message:a(r?.data)||`HTTP ${n}`,details:r?.data},status:n}}catch(r){let n=r,s=n.response?.status||0;return {data:null,error:{status:s,message:a(n.response?.data)||n.message||"Network error",details:n.response?.data},status:s}}}}}export{p as createHttpClient};//# sourceMappingURL=index.js.map
101
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/http.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { AxiosInstance, AxiosRequestConfig } from \"axios\";\n\nexport type ClientError = {\n message: string;\n status?: number;\n code?: string;\n details?: unknown;\n};\n\nexport type ClientResult<T> = {\n data: T | null;\n error: ClientError | null;\n status: number;\n};\n\nexport type HttpClient = {\n request<T>(\n config: AxiosRequestConfig & {\n url: string;\n method: NonNullable<AxiosRequestConfig[\"method\"]>;\n }\n ): Promise<ClientResult<T>>;\n};\n\ninterface ErrorPayload {\n message?: string;\n error?: string;\n msg?: string;\n detail?: string;\n}\n\nfunction normalizeErrorMessage(payload: unknown): string {\n if (!payload) {\n return \"Request failed\";\n }\n if (typeof payload === \"string\") {\n return payload;\n }\n if (typeof payload === \"object\") {\n const errorPayload = payload as ErrorPayload;\n return (\n errorPayload.message ||\n errorPayload.error ||\n errorPayload.msg ||\n errorPayload.detail ||\n \"Request failed\"\n );\n }\n return \"Request failed\";\n}\n\ninterface BackendStandardResponse {\n status: number | string;\n data: unknown;\n}\n\n/**\n * Check if response data matches backend standard format: { status: 0/1, data: {...} }\n * Handles both number and string status values\n */\nfunction isBackendStandardFormat(data: unknown): data is BackendStandardResponse {\n if (!data || typeof data !== \"object\" || !(\"data\" in data)) {\n return false;\n }\n\n // Check status field - handle both number and string values\n if (\"status\" in data) {\n const status = (data as BackendStandardResponse).status;\n // Accept 0, 1, \"0\", \"1\" as valid status values\n return status === 0 || status === 1 || status === \"0\" || status === \"1\";\n }\n\n return false;\n}\n\n/**\n * Unwrap backend standard format response\n * Backend returns: { status: 0, data: {...} }\n * We extract the inner 'data' field for cleaner client usage\n */\nfunction unwrapBackendResponse<T>(responseData: unknown): T {\n if (isBackendStandardFormat(responseData)) {\n // Extract inner data field\n return responseData.data as T;\n }\n // Return as-is if not standard format (e.g., BPM/Workflow APIs)\n return responseData as T;\n}\n\n/**\n * Backend datetime format regex: \"YYYY-MM-DD HH:MM:SS\"\n */\nconst BACKEND_DATETIME_REGEX = /^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$/;\n\n/**\n * Check if a string matches backend datetime format\n */\nfunction isBackendDatetime(value: string): boolean {\n return BACKEND_DATETIME_REGEX.test(value);\n}\n\n/**\n * Convert backend datetime string to ISO 8601 format\n * \"2026-01-05 10:30:45\" → \"2026-01-05T10:30:45.000Z\"\n */\nfunction parseBackendDatetime(value: string): string {\n // Replace space with T and append Z for UTC\n return `${value.replace(\" \", \"T\")}.000Z`;\n}\n\n/**\n * Recursively process response data to convert backend datetime strings to ISO format\n * Handles nested objects and arrays\n */\nfunction processResponseDates(data: unknown): unknown {\n if (data === null || data === undefined) {\n return data;\n }\n\n // Handle arrays\n if (Array.isArray(data)) {\n return data.map((item) => processResponseDates(item));\n }\n\n // Handle objects\n if (typeof data === \"object\") {\n const processed: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n processed[key] = processResponseDates(value);\n }\n return processed;\n }\n\n // Handle backend datetime strings\n if (typeof data === \"string\" && isBackendDatetime(data)) {\n return parseBackendDatetime(data);\n }\n\n return data;\n}\n\n/**\n * Create an HTTP client instance\n * \n * @param axiosInstance - Optional axios instance to use (defaults to a basic axios instance)\n * @returns HttpClient with request method\n * \n * @example\n * ```typescript\n * import axios from \"axios\";\n * import { createHttpClient } from \"@amaster.ai/http-client\";\n * \n * const instance = axios.create({ baseURL: \"https://api.example.com\" });\n * const client = createHttpClient(instance);\n * \n * const result = await client.request({\n * url: \"/users\",\n * method: \"get\",\n * });\n * ```\n */\nexport function createHttpClient(axiosInstance?: AxiosInstance): HttpClient {\n // Import axios dynamically to avoid bundling it\n let instance = axiosInstance;\n \n return {\n async request<T>(\n config: AxiosRequestConfig & {\n url: string;\n method: NonNullable<AxiosRequestConfig[\"method\"]>;\n }\n ) {\n // Lazy load axios if not provided\n if (!instance) {\n const axios = await import(\"axios\");\n instance = axios.default.create();\n }\n\n try {\n const resp = await instance(config);\n const status = resp?.status ?? 0;\n\n if (status >= 200 && status < 300) {\n // Unwrap backend standard format: { status: 0, data: {...} } -> {...}\n const unwrappedData = unwrapBackendResponse<T>(resp?.data);\n // Convert backend datetime strings to ISO format\n const processedData = processResponseDates(unwrappedData);\n return { data: (processedData ?? null) as T | null, error: null, status };\n }\n\n return {\n data: null,\n error: {\n status,\n message: normalizeErrorMessage(resp?.data) || `HTTP ${status}`,\n details: resp?.data,\n },\n status,\n };\n } catch (error_: unknown) {\n // Catch network errors, timeouts, and other axios exceptions\n const error = error_ as {\n response?: { status?: number; data?: unknown };\n message?: string;\n };\n const status = error.response?.status || 0;\n return {\n data: null,\n error: {\n status,\n message:\n normalizeErrorMessage(error.response?.data) || error.message || \"Network error\",\n details: error.response?.data,\n },\n status,\n };\n }\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/http.ts"],"names":["normalizeErrorMessage","payload","errorPayload","isBackendStandardFormat","data","status","unwrapBackendResponse","responseData","BACKEND_DATETIME_REGEX","isBackendDatetime","value","parseBackendDatetime","processResponseDates","item","processed","key","createHttpClient","axiosInstance","instance","config","resp","unwrappedData","error_","error"],"mappings":"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","file":"index.js","sourcesContent":["import type { AxiosInstance, AxiosRequestConfig } from \"axios\";\n\nexport type ClientError = {\n message: string;\n status?: number;\n code?: string;\n details?: unknown;\n};\n\nexport type ClientResult<T> = {\n data: T | null;\n error: ClientError | null;\n status: number;\n};\n\nexport type HttpClient = {\n request<T>(\n config: AxiosRequestConfig & {\n url: string;\n method: NonNullable<AxiosRequestConfig[\"method\"]>;\n }\n ): Promise<ClientResult<T>>;\n};\n\ninterface ErrorPayload {\n message?: string;\n error?: string;\n msg?: string;\n detail?: string;\n}\n\nfunction normalizeErrorMessage(payload: unknown): string {\n if (!payload) {\n return \"Request failed\";\n }\n if (typeof payload === \"string\") {\n return payload;\n }\n if (typeof payload === \"object\") {\n const errorPayload = payload as ErrorPayload;\n return (\n errorPayload.message ||\n errorPayload.error ||\n errorPayload.msg ||\n errorPayload.detail ||\n \"Request failed\"\n );\n }\n return \"Request failed\";\n}\n\ninterface BackendStandardResponse {\n status: number | string;\n data: unknown;\n}\n\n/**\n * Check if response data matches backend standard format: { status: 0/1, data: {...} }\n * Handles both number and string status values\n */\nfunction isBackendStandardFormat(data: unknown): data is BackendStandardResponse {\n if (!data || typeof data !== \"object\" || !(\"data\" in data)) {\n return false;\n }\n\n // Check status field - handle both number and string values\n if (\"status\" in data) {\n const status = (data as BackendStandardResponse).status;\n // Accept 0, 1, \"0\", \"1\" as valid status values\n return status === 0 || status === 1 || status === \"0\" || status === \"1\";\n }\n\n return false;\n}\n\n/**\n * Unwrap backend standard format response\n * Backend returns: { status: 0, data: {...} }\n * We extract the inner 'data' field for cleaner client usage\n */\nfunction unwrapBackendResponse<T>(responseData: unknown): T {\n if (isBackendStandardFormat(responseData)) {\n // Extract inner data field\n return responseData.data as T;\n }\n // Return as-is if not standard format (e.g., BPM/Workflow APIs)\n return responseData as T;\n}\n\n/**\n * Backend datetime format regex: \"YYYY-MM-DD HH:MM:SS\"\n */\nconst BACKEND_DATETIME_REGEX = /^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$/;\n\n/**\n * Check if a string matches backend datetime format\n */\nfunction isBackendDatetime(value: string): boolean {\n return BACKEND_DATETIME_REGEX.test(value);\n}\n\n/**\n * Convert backend datetime string to ISO 8601 format\n * \"2026-01-05 10:30:45\" → \"2026-01-05T10:30:45.000Z\"\n */\nfunction parseBackendDatetime(value: string): string {\n // Replace space with T and append Z for UTC\n return `${value.replace(\" \", \"T\")}.000Z`;\n}\n\n/**\n * Recursively process response data to convert backend datetime strings to ISO format\n * Handles nested objects and arrays\n */\nfunction processResponseDates(data: unknown): unknown {\n if (data === null || data === undefined) {\n return data;\n }\n\n // Handle arrays\n if (Array.isArray(data)) {\n return data.map((item) => processResponseDates(item));\n }\n\n // Handle objects\n if (typeof data === \"object\") {\n const processed: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n processed[key] = processResponseDates(value);\n }\n return processed;\n }\n\n // Handle backend datetime strings\n if (typeof data === \"string\" && isBackendDatetime(data)) {\n return parseBackendDatetime(data);\n }\n\n return data;\n}\n\n/**\n * Create an HTTP client instance\n * \n * @param axiosInstance - Optional axios instance to use (defaults to a basic axios instance)\n * @returns HttpClient with request method\n * \n * @example\n * ```typescript\n * import axios from \"axios\";\n * import { createHttpClient } from \"@amaster.ai/http-client\";\n * \n * const instance = axios.create({ baseURL: \"https://api.example.com\" });\n * const client = createHttpClient(instance);\n * \n * const result = await client.request({\n * url: \"/users\",\n * method: \"get\",\n * });\n * ```\n */\nexport function createHttpClient(axiosInstance?: AxiosInstance): HttpClient {\n // Import axios dynamically to avoid bundling it\n let instance = axiosInstance;\n \n return {\n async request<T>(\n config: AxiosRequestConfig & {\n url: string;\n method: NonNullable<AxiosRequestConfig[\"method\"]>;\n }\n ) {\n // Lazy load axios if not provided\n if (!instance) {\n const axios = await import(\"axios\");\n instance = axios.default.create();\n }\n\n try {\n const resp = await instance(config);\n const status = resp?.status ?? 0;\n\n if (status >= 200 && status < 300) {\n // Unwrap backend standard format: { status: 0, data: {...} } -> {...}\n const unwrappedData = unwrapBackendResponse<T>(resp?.data);\n // Convert backend datetime strings to ISO format\n const processedData = processResponseDates(unwrappedData);\n return { data: (processedData ?? null) as T | null, error: null, status };\n }\n\n return {\n data: null,\n error: {\n status,\n message: normalizeErrorMessage(resp?.data) || `HTTP ${status}`,\n details: resp?.data,\n },\n status,\n };\n } catch (error_: unknown) {\n // Catch network errors, timeouts, and other axios exceptions\n const error = error_ as {\n response?: { status?: number; data?: unknown };\n message?: string;\n };\n const status = error.response?.status || 0;\n return {\n data: null,\n error: {\n status,\n message:\n normalizeErrorMessage(error.response?.data) || error.message || \"Network error\",\n details: error.response?.data,\n },\n status,\n };\n }\n },\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amaster.ai/http-client",
3
- "version": "1.0.0-beta.0",
3
+ "version": "1.0.0-beta.2",
4
4
  "description": "Base HTTP client with error handling and response unwrapping",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",