@debugg-ai/debugg-ai-mcp 1.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.
Files changed (40) hide show
  1. package/README.md +2 -0
  2. package/dist/e2e-agents/e2eRunner.js +169 -0
  3. package/dist/e2e-agents/recordingHandler.js +57 -0
  4. package/dist/e2e-agents/resultsFormatter.js +102 -0
  5. package/dist/index.js +177 -0
  6. package/dist/services/coverage.js +127 -0
  7. package/dist/services/e2es.js +170 -0
  8. package/dist/services/index.js +36 -0
  9. package/dist/services/issues.js +132 -0
  10. package/dist/services/repos.js +23 -0
  11. package/dist/services/types.js +1 -0
  12. package/dist/src/e2e-agents/e2eRunner.js +127 -0
  13. package/dist/src/e2e-agents/recordingHandler.js +57 -0
  14. package/dist/src/e2e-agents/resultsFormatter.js +102 -0
  15. package/dist/src/index.js +107 -0
  16. package/dist/src/services/coverage.js +127 -0
  17. package/dist/src/services/e2es.js +170 -0
  18. package/dist/src/services/index.js +36 -0
  19. package/dist/src/services/indexes.js +74 -0
  20. package/dist/src/services/issues.js +132 -0
  21. package/dist/src/services/repos.js +23 -0
  22. package/dist/src/services/types.js +1 -0
  23. package/dist/src/tunnels/ngrok/error.js +3 -0
  24. package/dist/src/tunnels/ngrok/index.js +154 -0
  25. package/dist/src/tunnels/ngrok/statusBarItem.js +25 -0
  26. package/dist/src/tunnels/ngrok/types.js +2 -0
  27. package/dist/src/utils/axios.js +35 -0
  28. package/dist/src/utils/axiosNaming.js +31 -0
  29. package/dist/src/utils/axiosTransport.js +57 -0
  30. package/dist/src/utils/objectNaming.js +47 -0
  31. package/dist/src/utils/transportConfig.js +1 -0
  32. package/dist/tunnels/ngrok/error.js +3 -0
  33. package/dist/tunnels/ngrok/index.js +159 -0
  34. package/dist/tunnels/ngrok/types.js +2 -0
  35. package/dist/utils/axios.js +35 -0
  36. package/dist/utils/axiosNaming.js +31 -0
  37. package/dist/utils/axiosTransport.js +57 -0
  38. package/dist/utils/objectNaming.js +47 -0
  39. package/dist/utils/transportConfig.js +1 -0
  40. package/package.json +45 -0
@@ -0,0 +1,154 @@
1
+ import { existsSync, promises } from 'fs';
2
+ import { join } from 'path';
3
+ import { isError } from './error.js';
4
+ const { readFile } = promises;
5
+ import { mkdirp } from 'mkdirp';
6
+ import { authtoken, connect, disconnect, getApi, kill, } from 'ngrok';
7
+ import download from 'ngrok/download';
8
+ import { parse } from 'yaml';
9
+ const basePath = join(__dirname, 'bin');
10
+ export const binPath = () => basePath;
11
+ const DEFAULT_CONFIG_PATH = join(__dirname, 'ngrok-config.yml');
12
+ const getConfigPath = () => {
13
+ return DEFAULT_CONFIG_PATH;
14
+ };
15
+ const getConfig = async () => {
16
+ const configPath = getConfigPath();
17
+ try {
18
+ const config = parse(await readFile(configPath, 'utf8'));
19
+ if (config && typeof config.authtoken !== 'undefined') {
20
+ await authtoken({ authtoken: config.authtoken, binPath });
21
+ }
22
+ return config;
23
+ }
24
+ catch (error) {
25
+ if (isError(error) && error.code === 'ENOENT') {
26
+ if (configPath !== DEFAULT_CONFIG_PATH) {
27
+ console.error(`Could not find config file at ${configPath}.`);
28
+ }
29
+ }
30
+ else {
31
+ console.error(`Could not parse config file at ${configPath}.`);
32
+ }
33
+ }
34
+ };
35
+ const tunnelsFromConfig = (tunnels) => {
36
+ return Object.keys(tunnels).map((tunnelName) => {
37
+ return {
38
+ label: tunnelName,
39
+ tunnelOptions: { name: tunnelName, ...tunnels[tunnelName] },
40
+ };
41
+ });
42
+ };
43
+ const getActiveTunnels = async (api) => {
44
+ const response = await api.listTunnels();
45
+ return response.tunnels;
46
+ };
47
+ export const start = async (options) => {
48
+ const config = await getConfig();
49
+ const tunnel = options;
50
+ if (!tunnel) {
51
+ console.error('No tunnel provided');
52
+ return;
53
+ }
54
+ if (typeof tunnel !== 'undefined') {
55
+ const configPath = getConfigPath();
56
+ if (existsSync(configPath)) {
57
+ tunnel.configPath = configPath;
58
+ }
59
+ try {
60
+ tunnel.binPath = binPath;
61
+ try {
62
+ const url = await connect(tunnel);
63
+ }
64
+ catch (error) {
65
+ console.error(`There was an error starting your tunnel.`);
66
+ console.error(error);
67
+ }
68
+ }
69
+ catch (error) {
70
+ console.error(`There was an error finding the bin path.`);
71
+ console.error(error);
72
+ }
73
+ }
74
+ };
75
+ export const stop = async (tunnel) => {
76
+ const api = getApi();
77
+ if (!api) {
78
+ console.error('ngrok is not currently running.');
79
+ return;
80
+ }
81
+ try {
82
+ const tunnels = await getActiveTunnels(api);
83
+ console.error('tunnels', tunnels);
84
+ console.error('attempting to stop tunnel', tunnel);
85
+ if (tunnels.length > 0) {
86
+ if (tunnel === 'All') {
87
+ await closeAllTunnels();
88
+ }
89
+ else if (typeof tunnel !== 'undefined') {
90
+ await closeTunnel(tunnel, api);
91
+ }
92
+ }
93
+ else {
94
+ console.error('There are no active ngrok tunnels.');
95
+ }
96
+ }
97
+ catch (error) {
98
+ console.error('Could not get active tunnels from ngrok.');
99
+ console.error(error);
100
+ }
101
+ };
102
+ const closeTunnel = async (tunnel, api) => {
103
+ try {
104
+ await disconnect(tunnel);
105
+ let message = `Debugg AI tunnel disconnected.`;
106
+ if ((await getActiveTunnels(api)).length === 0) {
107
+ await kill();
108
+ message = `${message} DebuggAI test runner completed.`;
109
+ // hideStatusBarItem();
110
+ }
111
+ console.error(message);
112
+ }
113
+ catch (error) {
114
+ // window.showErrorMessage(
115
+ // `There was a problem stopping the tunnel ${tunnel}, see the log for details.`
116
+ // );
117
+ console.error(error);
118
+ }
119
+ };
120
+ const closeAllTunnels = async () => {
121
+ try {
122
+ await disconnect();
123
+ await kill();
124
+ // window.showInformationMessage(
125
+ // 'All ngrok tunnels disconnected. ngrok has been shutdown.'
126
+ // );
127
+ // hideStatusBarItem();
128
+ }
129
+ catch (error) {
130
+ // window.showErrorMessage(
131
+ // 'There was an issue closing the ngrok tunnels, check the log for details.'
132
+ // );
133
+ console.error(error);
134
+ }
135
+ };
136
+ export const downloadBinary = async () => {
137
+ const binaryLocations = [
138
+ join(basePath, 'ngrok'),
139
+ join(basePath, 'ngrok.exe'),
140
+ ];
141
+ if (binaryLocations.some((path) => existsSync(path))) {
142
+ console.info('ngrok binary is already downloaded');
143
+ }
144
+ else {
145
+ await mkdirp(basePath);
146
+ try {
147
+ await new Promise((resolve, reject) => download((error) => (error ? reject(error) : resolve())));
148
+ }
149
+ catch (error) {
150
+ console.error(`Can't update local tunnel configuration. The tests may not work correctly.`);
151
+ console.error(error);
152
+ }
153
+ }
154
+ };
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hideStatusBarItem = exports.showStatusBarItem = exports.createStatusBarItem = void 0;
4
+ var vscode_1 = require("vscode");
5
+ var statusBarItem;
6
+ var createStatusBarItem = function (commandId, version) {
7
+ statusBarItem = vscode_1.window.createStatusBarItem(vscode_1.StatusBarAlignment.Left);
8
+ var statusBarText = '$(globe) ngrok';
9
+ if (version) {
10
+ statusBarText += " (v".concat(version, ")");
11
+ }
12
+ statusBarItem.text = statusBarText;
13
+ statusBarItem.tooltip = 'ngrok is running';
14
+ statusBarItem.command = commandId;
15
+ return statusBarItem;
16
+ };
17
+ exports.createStatusBarItem = createStatusBarItem;
18
+ var showStatusBarItem = function () {
19
+ statusBarItem.show();
20
+ };
21
+ exports.showStatusBarItem = showStatusBarItem;
22
+ var hideStatusBarItem = function () {
23
+ statusBarItem.hide();
24
+ };
25
+ exports.hideStatusBarItem = hideStatusBarItem;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ /* eslint-enable */
@@ -0,0 +1,35 @@
1
+ import axios from "axios";
2
+ const axiosServices = axios.create({
3
+ baseURL: process.env.REACT_APP_API_URL || "http://localhost:81",
4
+ headers: {
5
+ "Content-Type": "application/json",
6
+ "Accept": "application/json",
7
+ "Authorization": "Token 6f960ed60c88b5af7d1d7ecfabeee53f5068dc4d",
8
+ },
9
+ });
10
+ // ==============================|| AXIOS - FOR MOCK SERVICES ||============================== //
11
+ axiosServices.interceptors.response.use((response) => {
12
+ //console.error(`Response data....${response.data}`)
13
+ if (response.data) {
14
+ // response.data = objToCamelCase(response.data);
15
+ // response.data = response.data;
16
+ }
17
+ return response;
18
+ }, (error) => {
19
+ // let host = window.location.host;
20
+ // let parts = host.split(".");
21
+ // let subdomain = parts[0];
22
+ // if (error.response.status === 401 && subdomain == 'app' && !window.location.href.includes('/login')) {
23
+ // window.location = '/login';
24
+ // }
25
+ if (error) {
26
+ // error = objToCamelCase(error);
27
+ }
28
+ return Promise.reject((error.response && error.response.data) || "Wrong Services");
29
+ });
30
+ axiosServices.interceptors.request.use(async (config) => {
31
+ // Update naming convention from CamelCase to the underscored type
32
+ return config;
33
+ }, (error) => error);
34
+ export default axiosServices;
35
+ export const { get, post, put, delete: destroy } = axiosServices;
@@ -0,0 +1,31 @@
1
+ import { objToCamelCase, objToSnakeCase } from "../utils/objectNaming.js";
2
+ import { destroy as destroyAxios, get as getAxios, post as postAxios, put as putAxios } from "./axios.js";
3
+ export async function get(url, params) {
4
+ const fmtdParams = objToSnakeCase(params);
5
+ return getAxios(url, fmtdParams).then((response) => {
6
+ console.error("response", response);
7
+ const fmtdData = objToCamelCase(response.data);
8
+ response.data = fmtdData;
9
+ return response;
10
+ });
11
+ }
12
+ export async function post(url, data, config) {
13
+ const fmtdData = objToSnakeCase(data);
14
+ return postAxios(url, fmtdData, config).then((response) => {
15
+ response.data = objToCamelCase(response.data);
16
+ return response;
17
+ });
18
+ }
19
+ export async function put(url, data, config) {
20
+ const fmtdData = objToSnakeCase(data);
21
+ return putAxios(url, fmtdData, config).then((response) => {
22
+ response.data = objToCamelCase(response.data);
23
+ return response;
24
+ });
25
+ }
26
+ export async function destroy(url, config) {
27
+ return destroyAxios(url, config).then((response) => {
28
+ response.data = objToCamelCase(response.data);
29
+ return response;
30
+ });
31
+ }
@@ -0,0 +1,57 @@
1
+ // utils/axiosTransport.ts
2
+ import axios from "axios";
3
+ import { objToCamelCase, objToSnakeCase, } from "./objectNaming.js";
4
+ /**
5
+ * A tiny wrapper around axios that keeps all your interceptors
6
+ * but gives service factories a clean, typed surface.
7
+ */
8
+ export class AxiosTransport {
9
+ axios;
10
+ constructor({ baseUrl, apiKey, instance }) {
11
+ // Use an injected instance or create one that mimics `axiosServices`
12
+ // Use provided apiKey as the Token. Must be requested on the app.
13
+ this.axios =
14
+ instance ??
15
+ axios.create({
16
+ baseURL: baseUrl.replace(/\/+$/, "/"),
17
+ headers: {
18
+ Accept: "application/json",
19
+ "Content-Type": "application/json",
20
+ ...(apiKey ? { Authorization: `Token ${apiKey}` } : {}),
21
+ },
22
+ });
23
+ /* ---------- INTERCEPTORS ---------- */
24
+ // Response → camelCase
25
+ this.axios.interceptors.response.use((res) => {
26
+ res.data = objToCamelCase(res.data);
27
+ return res;
28
+ }, (err) => Promise.reject((err.response && err.response.data) || "Unknown Axios error"));
29
+ // Request → snake_case
30
+ this.axios.interceptors.request.use((cfg) => {
31
+ if (cfg.data && typeof cfg.data === "object") {
32
+ cfg.data = objToSnakeCase(cfg.data);
33
+ }
34
+ if (cfg.params && typeof cfg.params === "object") {
35
+ cfg.params = objToSnakeCase(cfg.params);
36
+ }
37
+ return cfg;
38
+ });
39
+ }
40
+ /* ---------- SHORTHAND METHODS ---------- */
41
+ async request(cfg) {
42
+ const res = await this.axios.request(cfg);
43
+ return res.data;
44
+ }
45
+ get(url, params) {
46
+ return this.request({ url, method: "GET", params });
47
+ }
48
+ post(url, data, cfg) {
49
+ return this.request({ url, method: "POST", data, ...cfg });
50
+ }
51
+ put(url, data, cfg) {
52
+ return this.request({ url, method: "PUT", data, ...cfg });
53
+ }
54
+ delete(url, cfg) {
55
+ return this.request({ url, method: "DELETE", ...cfg });
56
+ }
57
+ }
@@ -0,0 +1,47 @@
1
+ // Function to handle converting a string to camelCase
2
+ export function stringToCamelCase(str) {
3
+ return str
4
+ .toLowerCase()
5
+ .split("_")
6
+ .map((s, i) => i === 0 ? s : s.slice(0, 1).toUpperCase() + s.slice(1, s.length))
7
+ .join("");
8
+ }
9
+ // Function to handle converting a string to snake_case
10
+ export function stringToSnakeCase(str) {
11
+ return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
12
+ }
13
+ // Function to handle switching API data response objects into camelCase
14
+ export function objToCamelCase(obj) {
15
+ if (!obj) {
16
+ return obj;
17
+ }
18
+ if (Array.isArray(obj)) {
19
+ return obj.map((entry) => typeof entry !== "object" ? entry : objToCamelCase(entry));
20
+ }
21
+ const newObj = Object.entries(obj).reduce((acc, [key, value]) => {
22
+ const newKey = stringToCamelCase(key);
23
+ const newValue = typeof value !== "object" ? value : objToCamelCase(value);
24
+ acc[newKey] = newValue;
25
+ return acc;
26
+ }, {});
27
+ return newObj;
28
+ }
29
+ // Function to handle switching objects into snake_case
30
+ export function objToSnakeCase(obj) {
31
+ if (!obj) {
32
+ return obj;
33
+ }
34
+ if (obj instanceof File || obj instanceof FormData) {
35
+ return obj;
36
+ }
37
+ if (Array.isArray(obj)) {
38
+ return obj.map((entry) => typeof entry !== "object" ? entry : objToSnakeCase(entry));
39
+ }
40
+ const newObj = Object.entries(obj).reduce((acc, [key, value]) => {
41
+ const newKey = stringToSnakeCase(key);
42
+ const newValue = typeof value !== "object" ? value : objToSnakeCase(value);
43
+ acc[newKey] = newValue;
44
+ return acc;
45
+ }, {});
46
+ return newObj;
47
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ export function isError(error) {
2
+ return error instanceof Error;
3
+ }
@@ -0,0 +1,159 @@
1
+ import { existsSync, promises } from 'fs';
2
+ import { join } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname } from 'path';
5
+ import { isError } from './error.js';
6
+ const { readFile } = promises;
7
+ import { mkdirp } from 'mkdirp';
8
+ import { authtoken, connect, disconnect, getApi, kill, } from 'ngrok';
9
+ import download from 'ngrok/download';
10
+ import { parse } from 'yaml';
11
+ // Get the equivalent of __dirname in ESM
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+ const basePath = join(__dirname, 'bin');
15
+ export const binPath = () => basePath;
16
+ const DEFAULT_CONFIG_PATH = join(__dirname, 'ngrok-config.yml');
17
+ const getConfigPath = () => {
18
+ return DEFAULT_CONFIG_PATH;
19
+ };
20
+ const getConfig = async () => {
21
+ const configPath = getConfigPath();
22
+ try {
23
+ const config = parse(await readFile(configPath, 'utf8'));
24
+ if (config && typeof config.authtoken !== 'undefined') {
25
+ await authtoken({ authtoken: config.authtoken, binPath });
26
+ }
27
+ return config;
28
+ }
29
+ catch (error) {
30
+ if (isError(error) && error.code === 'ENOENT') {
31
+ if (configPath !== DEFAULT_CONFIG_PATH) {
32
+ console.error(`Could not find config file at ${configPath}.`);
33
+ }
34
+ }
35
+ else {
36
+ console.error(`Could not parse config file at ${configPath}.`);
37
+ }
38
+ }
39
+ };
40
+ const tunnelsFromConfig = (tunnels) => {
41
+ return Object.keys(tunnels).map((tunnelName) => {
42
+ return {
43
+ label: tunnelName,
44
+ tunnelOptions: { name: tunnelName, ...tunnels[tunnelName] },
45
+ };
46
+ });
47
+ };
48
+ const getActiveTunnels = async (api) => {
49
+ const response = await api.listTunnels();
50
+ return response.tunnels;
51
+ };
52
+ export const start = async (options) => {
53
+ const config = await getConfig();
54
+ const tunnel = options;
55
+ if (!tunnel) {
56
+ console.error('No tunnel provided');
57
+ return;
58
+ }
59
+ if (typeof tunnel !== 'undefined') {
60
+ const configPath = getConfigPath();
61
+ if (existsSync(configPath)) {
62
+ tunnel.configPath = configPath;
63
+ }
64
+ try {
65
+ tunnel.binPath = binPath;
66
+ try {
67
+ const url = await connect(tunnel);
68
+ }
69
+ catch (error) {
70
+ console.error(`There was an error starting your tunnel.`);
71
+ console.error(error);
72
+ }
73
+ }
74
+ catch (error) {
75
+ console.error(`There was an error finding the bin path.`);
76
+ console.error(error);
77
+ }
78
+ }
79
+ };
80
+ export const stop = async (tunnel) => {
81
+ const api = getApi();
82
+ if (!api) {
83
+ console.error('ngrok is not currently running.');
84
+ return;
85
+ }
86
+ try {
87
+ const tunnels = await getActiveTunnels(api);
88
+ console.error('tunnels', tunnels);
89
+ console.error('attempting to stop tunnel', tunnel);
90
+ if (tunnels.length > 0) {
91
+ if (tunnel === 'All') {
92
+ await closeAllTunnels();
93
+ }
94
+ else if (typeof tunnel !== 'undefined') {
95
+ await closeTunnel(tunnel, api);
96
+ }
97
+ }
98
+ else {
99
+ console.error('There are no active ngrok tunnels.');
100
+ }
101
+ }
102
+ catch (error) {
103
+ console.error('Could not get active tunnels from ngrok.');
104
+ console.error(error);
105
+ }
106
+ };
107
+ const closeTunnel = async (tunnel, api) => {
108
+ try {
109
+ await disconnect(tunnel);
110
+ let message = `Debugg AI tunnel disconnected.`;
111
+ if ((await getActiveTunnels(api)).length === 0) {
112
+ await kill();
113
+ message = `${message} DebuggAI test runner completed.`;
114
+ // hideStatusBarItem();
115
+ }
116
+ console.error(message);
117
+ }
118
+ catch (error) {
119
+ // window.showErrorMessage(
120
+ // `There was a problem stopping the tunnel ${tunnel}, see the log for details.`
121
+ // );
122
+ console.error(error);
123
+ }
124
+ };
125
+ const closeAllTunnels = async () => {
126
+ try {
127
+ await disconnect();
128
+ await kill();
129
+ // window.showInformationMessage(
130
+ // 'All ngrok tunnels disconnected. ngrok has been shutdown.'
131
+ // );
132
+ // hideStatusBarItem();
133
+ }
134
+ catch (error) {
135
+ // window.showErrorMessage(
136
+ // 'There was an issue closing the ngrok tunnels, check the log for details.'
137
+ // );
138
+ console.error(error);
139
+ }
140
+ };
141
+ export const downloadBinary = async () => {
142
+ const binaryLocations = [
143
+ join(basePath, 'ngrok'),
144
+ join(basePath, 'ngrok.exe'),
145
+ ];
146
+ if (binaryLocations.some((path) => existsSync(path))) {
147
+ console.info('ngrok binary is already downloaded');
148
+ }
149
+ else {
150
+ await mkdirp(basePath);
151
+ try {
152
+ await new Promise((resolve, reject) => download((error) => (error ? reject(error) : resolve())));
153
+ }
154
+ catch (error) {
155
+ console.error(`Can't update local tunnel configuration. The tests may not work correctly.`);
156
+ console.error(error);
157
+ }
158
+ }
159
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ /* eslint-enable */
@@ -0,0 +1,35 @@
1
+ import axios from "axios";
2
+ const axiosServices = axios.create({
3
+ baseURL: process.env.REACT_APP_API_URL || "http://localhost:81",
4
+ headers: {
5
+ "Content-Type": "application/json",
6
+ "Accept": "application/json",
7
+ "Authorization": "Token 6f960ed60c88b5af7d1d7ecfabeee53f5068dc4d",
8
+ },
9
+ });
10
+ // ==============================|| AXIOS - FOR MOCK SERVICES ||============================== //
11
+ axiosServices.interceptors.response.use((response) => {
12
+ //console.error(`Response data....${response.data}`)
13
+ if (response.data) {
14
+ // response.data = objToCamelCase(response.data);
15
+ // response.data = response.data;
16
+ }
17
+ return response;
18
+ }, (error) => {
19
+ // let host = window.location.host;
20
+ // let parts = host.split(".");
21
+ // let subdomain = parts[0];
22
+ // if (error.response.status === 401 && subdomain == 'app' && !window.location.href.includes('/login')) {
23
+ // window.location = '/login';
24
+ // }
25
+ if (error) {
26
+ // error = objToCamelCase(error);
27
+ }
28
+ return Promise.reject((error.response && error.response.data) || "Wrong Services");
29
+ });
30
+ axiosServices.interceptors.request.use(async (config) => {
31
+ // Update naming convention from CamelCase to the underscored type
32
+ return config;
33
+ }, (error) => error);
34
+ export default axiosServices;
35
+ export const { get, post, put, delete: destroy } = axiosServices;
@@ -0,0 +1,31 @@
1
+ import { objToCamelCase, objToSnakeCase } from "../utils/objectNaming.js";
2
+ import { destroy as destroyAxios, get as getAxios, post as postAxios, put as putAxios } from "./axios.js";
3
+ export async function get(url, params) {
4
+ const fmtdParams = objToSnakeCase(params);
5
+ return getAxios(url, fmtdParams).then((response) => {
6
+ console.error("response", response);
7
+ const fmtdData = objToCamelCase(response.data);
8
+ response.data = fmtdData;
9
+ return response;
10
+ });
11
+ }
12
+ export async function post(url, data, config) {
13
+ const fmtdData = objToSnakeCase(data);
14
+ return postAxios(url, fmtdData, config).then((response) => {
15
+ response.data = objToCamelCase(response.data);
16
+ return response;
17
+ });
18
+ }
19
+ export async function put(url, data, config) {
20
+ const fmtdData = objToSnakeCase(data);
21
+ return putAxios(url, fmtdData, config).then((response) => {
22
+ response.data = objToCamelCase(response.data);
23
+ return response;
24
+ });
25
+ }
26
+ export async function destroy(url, config) {
27
+ return destroyAxios(url, config).then((response) => {
28
+ response.data = objToCamelCase(response.data);
29
+ return response;
30
+ });
31
+ }
@@ -0,0 +1,57 @@
1
+ // utils/axiosTransport.ts
2
+ import axios from "axios";
3
+ import { objToCamelCase, objToSnakeCase, } from "./objectNaming.js";
4
+ /**
5
+ * A tiny wrapper around axios that keeps all your interceptors
6
+ * but gives service factories a clean, typed surface.
7
+ */
8
+ export class AxiosTransport {
9
+ axios;
10
+ constructor({ baseUrl, apiKey, instance }) {
11
+ // Use an injected instance or create one that mimics `axiosServices`
12
+ // Use provided apiKey as the Token. Must be requested on the app.
13
+ this.axios =
14
+ instance ??
15
+ axios.create({
16
+ baseURL: baseUrl.replace(/\/+$/, "/"),
17
+ headers: {
18
+ Accept: "application/json",
19
+ "Content-Type": "application/json",
20
+ ...(apiKey ? { Authorization: `Token ${apiKey}` } : {}),
21
+ },
22
+ });
23
+ /* ---------- INTERCEPTORS ---------- */
24
+ // Response → camelCase
25
+ this.axios.interceptors.response.use((res) => {
26
+ res.data = objToCamelCase(res.data);
27
+ return res;
28
+ }, (err) => Promise.reject((err.response && err.response.data) || "Unknown Axios error"));
29
+ // Request → snake_case
30
+ this.axios.interceptors.request.use((cfg) => {
31
+ if (cfg.data && typeof cfg.data === "object") {
32
+ cfg.data = objToSnakeCase(cfg.data);
33
+ }
34
+ if (cfg.params && typeof cfg.params === "object") {
35
+ cfg.params = objToSnakeCase(cfg.params);
36
+ }
37
+ return cfg;
38
+ });
39
+ }
40
+ /* ---------- SHORTHAND METHODS ---------- */
41
+ async request(cfg) {
42
+ const res = await this.axios.request(cfg);
43
+ return res.data;
44
+ }
45
+ get(url, params) {
46
+ return this.request({ url, method: "GET", params });
47
+ }
48
+ post(url, data, cfg) {
49
+ return this.request({ url, method: "POST", data, ...cfg });
50
+ }
51
+ put(url, data, cfg) {
52
+ return this.request({ url, method: "PUT", data, ...cfg });
53
+ }
54
+ delete(url, cfg) {
55
+ return this.request({ url, method: "DELETE", ...cfg });
56
+ }
57
+ }